diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 50e6e2c6..6ad4e6ce 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -6,8 +6,8 @@ /AGENTS.md @NanmiCoder **/AGENTS.md @NanmiCoder /CONTRIBUTING.md @NanmiCoder -/docs/en/guide/contributing.md @NanmiCoder -/docs/guide/contributing.md @NanmiCoder +/docs/en/internals/contributing.md @NanmiCoder +/docs/internals/contributing.md @NanmiCoder /scripts/pr/ @NanmiCoder /scripts/quality-gate/ @NanmiCoder diff --git a/AGENTS.md b/AGENTS.md index a1eec78f..7b732b88 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -61,7 +61,7 @@ Additional invariants: ## Deeper Guides -- Contributor workflow and quality lanes: `CONTRIBUTING.md` and `docs/guide/contributing.md` +- Contributor workflow and quality lanes: `CONTRIBUTING.md` and `docs/internals/contributing.md` - Package scripts and path routing: `package.json` and `scripts/pr/change-policy.ts` - PR evidence contract: `.github/pull_request_template.md` - Desktop release and auto-update runbook: `docs/desktop/10-release-auto-update.md` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8b22f62c..b37039cf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,4 +66,4 @@ bun run quality:smoke --provider-model ## 更多 -完整质量门禁和覆盖率说明见 [贡献指南](docs/guide/contributing.md);`AGENTS.md` 仅保留 Agent 的高信号入口与路由。 +完整质量门禁和覆盖率说明见 [贡献指南](docs/internals/contributing.md);`AGENTS.md` 仅保留 Agent 的高信号入口与路由。 diff --git a/README.en.md b/README.en.md index 12febf01..8910894d 100644 --- a/README.en.md +++ b/README.en.md @@ -20,7 +20,7 @@ -A Claude Code build repaired from the source leaked from Anthropic's npm registry on 2026-03-31. Claude Code Haha is now primarily a **desktop Claude Code workspace** for macOS, Windows, and Linux: sessions, projects, branch / Worktree launch, right-side file changes, code diffs, permission review, provider setup, Computer Use, H5 remote access, IM integration, and scheduled tasks in one app. +A Claude Code build repaired from the source leaked from Anthropic's npm registry on 2026-03-31. Claude Code Haha is now primarily a **desktop Claude Code workspace** for macOS, Windows, and Linux: sessions, projects, branch / Worktree launch, workspace changes and diff review, permission approval, model setup, Computer Use, H5 remote access, IM integration, and scheduled tasks in one app.

Desktop Preview · Install · Highlights · Sponsorship · More Docs @@ -30,26 +30,24 @@ A Claude Code build repaired from the source leaked from Anthropic's npm registr ## Desktop Preview -The Claude Code Haha desktop app brings sessions, multi-project navigation, branch / Worktree controls, right-side file changes, code diffs, permission review, provider setup, and remote access into one graphical workspace for daily development flows beyond the terminal. +The Claude Code Haha desktop app brings sessions, multi-project navigation, branch / Worktree controls, file changes, diff review, permission approval, model setup, and remote access into one graphical workspace for daily development beyond the terminal. + +v0.5.0 shipped a full UI redesign — six colour themes that can follow your system's light/dark setting. All four shots below are from a real v0.5.0 build.

Download Desktop   - Install Guide + Install Guide

- - - - + + - - - - + +
Desktop workspace
Desktop Workspace
Right-side changes and Worktree
Right-side Changes & Worktree
Code editing
Code Editing & Diff View
Permission control
Permission Review & AI Questions
Desktop session view
Session, tool calls & inline diff
Workspace diff review
Review every edit, file by file
H5 remote access
H5 Remote Access
Token usage
Token Usage
Computer Use
Computer Use
Scheduled tasks
Scheduled Tasks
Session view in the dark theme
Six themes, optionally following the system
Skill marketplace
Skill marketplace with source and safety labels
@@ -59,7 +57,7 @@ The Claude Code Haha desktop app brings sessions, multi-project navigation, bran 1. Download the macOS / Windows / Linux desktop installer from [Releases](https://github.com/NanmiCoder/cc-haha/releases). 2. On first launch, configure your model provider, API key, and default model in Settings. -3. Public macOS releases require signing and notarization. Draft or unsigned temporary builds may still need one-time manual approval. Unsigned Windows installers may show SmartScreen; click "More info" -> "Run anyway". See the [desktop installation guide](docs/desktop/04-installation.md). +3. Public macOS releases require signing and notarization. Draft or unsigned temporary builds may still need one-time manual approval. Unsigned Windows installers may show SmartScreen; click "More info" -> "Run anyway". See the [desktop installation guide](docs/en/start/install.md). ## Run the CLI from Source @@ -71,44 +69,39 @@ cp .env.example .env ./bin/claude-haha ``` -See [environment variables](docs/en/guide/env-vars.md) and [global usage](docs/en/guide/global-usage.md) for more configuration options. +See [environment variables](docs/en/cli/env.md) and [CLI setup](docs/en/cli/index.md) for more configuration options. --- ## Desktop Highlights -- **Multi-session workspace**: tabs, project switching, terminal entry, and session history in one place. +- **Multi-session workspace**: tabs, project switching, terminal entry, and session history in one place, with a resizable sidebar. - **Branch / Worktree launch**: choose a repository branch and decide whether to use the current working tree or an isolated Worktree. -- **Right-side file changes**: review changed files, added/removed lines, and current workspace state while chatting. -- **Visual code changes**: inspect edits, file writes, and diffs directly in the desktop app. -- **Permission review**: approve risky commands, tool calls, and model follow-up questions in the GUI. -- **Multi-provider setup**: configure Anthropic-compatible APIs, third-party models, WebSearch fallback, and local options. -- **Skill Marketplace**: discover, preview, install, and manage third-party skills from ClawHub / SkillHub in the desktop app. -- **Session Activity Panel**: review tasks, background tasks, SubAgents, team activity, and sources in one side panel. +- **Review edits file by file**: the workspace lists this turn's changes; open any file for a syntax-highlighted diff, or undo the whole turn. +- **Five permission modes**: from "ask every time" to "skip permissions" — risky commands, tool calls, and follow-up questions are all approved in the GUI. +- **Bring your own model**: sign in to Claude, ChatGPT, or Grok; use presets for DeepSeek, Kimi, Zhipu GLM and others; or point it at LM Studio and Ollama running locally. +- **Six colour themes**: white, paper, warm classic, celadon, ink night, and ink blue — optionally following your system's light/dark setting. +- **Skill marketplace**: discover, preview, and install third-party skills from ClawHub / SkillHub, with source and safety status shown up front. +- **Session activity panel**: track task progress, background tasks, SubAgents, and sources in one side panel. - **Computer Use**: let the agent take screenshots, click, type, and control desktop apps after authorization. -- **H5 remote access**: open the current desktop session from a phone or another device with a one-time token. -- **IM integration**: chat, switch projects, and approve actions through Telegram / Feishu / WeChat / DingTalk. -- **Scheduled tasks and usage stats**: create planned tasks and track local token usage trends. +- **Desktop pets**: Dada, Huhu, Bubu, and Huihui change what they do with the task at hand — or raise one of your own (off by default). +- **H5 remote access**: scan a QR code to continue the session in your phone browser; locking the screen won't kill a running task. +- **IM integration**: chat, switch projects, and approve actions through Telegram / Feishu / WeChat / DingTalk / WhatsApp. +- **Scheduled tasks and usage stats**: run planned tasks in their own sessions and track local token usage trends. --- ## More Documentation -| Document | Description | +Full documentation site: + +| Section | Documents | |------|------| -| [Environment Variables](docs/en/guide/env-vars.md) | Full env var reference and configuration methods | -| [Third-Party Models](docs/en/guide/third-party-models.md) | Using OpenAI / DeepSeek / Ollama and other non-Anthropic models | -| [Contributing](docs/en/guide/contributing.md) | Local tests, live model baselines, PR gates, and release gates | -| [Memory System](docs/memory/01-usage-guide.md) | Cross-session persistent memory usage and implementation | -| [Multi-Agent System](docs/agent/01-usage-guide.md) | Agent orchestration, parallel tasks and Teams collaboration | -| [Skills System](docs/skills/01-usage-guide.md) | Extensible capability plugins, custom workflows and conditional activation | -| [IM Integration](docs/im/) | Remote chat, project switching, and permission approval via Telegram / Feishu / WeChat / DingTalk | -| [Computer Use](docs/en/features/computer-use.md) | Desktop control (screenshots, mouse, keyboard) — [Architecture](docs/en/features/computer-use-architecture.md) | -| [Desktop App](docs/desktop/) | Electron + React GUI client — [Quick Start](docs/desktop/01-quick-start.md) \| [Architecture](docs/desktop/02-architecture.md) \| [Installation](docs/desktop/04-installation.md) | -| [Global Usage](docs/en/guide/global-usage.md) | Run claude-haha from any directory | -| [FAQ](docs/en/guide/faq.md) | Common error troubleshooting | -| [Source Fixes](docs/en/reference/fixes.md) | Fixes compared with the original leaked source | -| [Project Structure](docs/en/reference/project-structure.md) | Code directory structure | +| **Getting started** | [What this is](docs/en/start/index.md) · [Download and install](docs/en/start/install.md) · [Connect a model provider](docs/en/start/models.md) · [Your first session](docs/en/start/first-session.md) · [Troubleshooting](docs/en/start/troubleshooting.md) | +| **Desktop features** | [Feature overview](docs/en/desktop/index.md) · [Computer Use](docs/en/desktop/computer-use.md) · [Desktop pets](docs/en/desktop/pets.md) · [Phone H5 and IM relay](docs/en/desktop/remote.md) | +| **IM integrations** | [Overview and pairing](docs/en/im/index.md) · [Feishu](docs/en/im/feishu.md) · [Telegram](docs/en/im/telegram.md) · [WeChat](docs/en/im/wechat.md) · [DingTalk](docs/en/im/dingtalk.md) · [WhatsApp](docs/en/im/whatsapp.md) | +| **CLI** | [Install and run](docs/en/cli/index.md) · [Command reference](docs/en/cli/reference.md) · [Environment variables](docs/en/cli/env.md) | +| **Internals** | [Multi-agent system](docs/en/internals/agent.md) · [Skills system](docs/en/internals/skills.md) · [Memory system](docs/en/internals/memory.md) · [Computer Use architecture](docs/en/internals/computer-use.md) · [Local server and API](docs/en/internals/server.md) · [Channel system](docs/en/internals/channel.md) · [Project structure](docs/en/internals/structure.md) · [Contributing and quality gates](docs/en/internals/contributing.md) | --- diff --git a/README.md b/README.md index c51a6387..840fa176 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ -Claude Code Haha 基于 2026-03-31 从 Anthropic npm registry 泄露的 Claude Code 源码修复而来,现在主要是一个**桌面端 Claude Code 工作台**:把会话、多项目、分支 / Worktree、右侧代码改动、代码 Diff、权限审批、模型提供商、Computer Use、H5 远程访问、IM 接入和定时任务集中到一个 macOS / Windows / Linux APP 里。 +Claude Code Haha 基于 2026-03-31 从 Anthropic npm registry 泄露的 Claude Code 源码修复而来,现在主要是一个**桌面端 Claude Code 工作台**:把会话、多项目、分支 / Worktree、工作区改动与 Diff 审阅、权限审批、模型配置、Computer Use、H5 远程访问、IM 接入和定时任务集中到一个 macOS / Windows / Linux APP 里。

桌面端预览 · 安装桌面端 · 桌面端亮点 · 赞助与合作 · 更多文档 @@ -30,26 +30,24 @@ Claude Code Haha 基于 2026-03-31 从 Anthropic npm registry 泄露的 Claude C ## 桌面端预览 -Claude Code Haha 的桌面端把会话、多项目、分支 / Worktree、右侧代码改动、代码 Diff、权限确认、提供商配置和远程入口集中到一个图形化工作台里,适合不想长期停留在终端里的日常开发工作流。 +Claude Code Haha 的桌面端把会话、多项目、分支 / Worktree、代码改动、Diff 评审、权限确认、模型配置和远程入口收进一个图形化工作台,适合不想长期停留在终端里的日常开发。 + +v0.5.0 做了一次全量 UI 重设计(「纸·墨·印」),六套配色可跟随系统深浅色切换。下面四张都拍自 v0.5.0 真机。

下载桌面端   - 安装指南 + 安装指南

- - - - + + - - - - + +
桌面端工作台
桌面端工作台
右侧代码改动与 Worktree
右侧代码改动 & Worktree
代码编辑
代码编辑 & Diff 视图
权限控制
权限控制 & AI 提问
桌面端会话主界面
会话、工具调用与内联 Diff
工作区 Diff 评审
改动逐个文件审阅
H5 访问
H5 远程访问
Token 用量
Token 用量统计
Computer Use
Computer Use
定时任务
定时任务
墨夜主题下的会话界面
六套配色,可跟随系统深浅色
技能市场
技能市场,装前先看来源与安全状态
@@ -59,7 +57,7 @@ Claude Code Haha 的桌面端把会话、多项目、分支 / Worktree、右侧 1. 前往 [Releases](https://github.com/NanmiCoder/cc-haha/releases) 下载 macOS / Windows / Linux 桌面端安装包。 2. 首次启动后,在桌面端设置里配置模型提供商、API Key 和默认模型。 -3. 正式 macOS Release 需要经过签名和公证;如果安装的是 draft/unsigned 临时包,首次打开可能仍需手动放行。Windows 未签名安装包可能出现 SmartScreen 提示,点「更多信息」→「仍要运行」即可。详见 [桌面端安装指南](docs/desktop/04-installation.md)。 +3. 正式 macOS Release 需要经过签名和公证;如果安装的是 draft/unsigned 临时包,首次打开可能仍需手动放行。Windows 未签名安装包可能出现 SmartScreen 提示,点「更多信息」→「仍要运行」即可。详见 [桌面端安装指南](docs/start/install.md)。 ## 从源码启动 CLI @@ -71,44 +69,39 @@ cp .env.example .env ./bin/claude-haha ``` -更多配置见 [环境变量](docs/guide/env-vars.md) 和 [全局使用](docs/guide/global-usage.md)。 +更多配置见 [环境变量](docs/cli/env.md) 和 [命令行安装与启动](docs/cli/index.md)。 --- ## 桌面端亮点 -- **多会话工作台**:标签页、项目切换、终端入口和会话历史集中管理。 -- **分支 / Worktree 启动**:新会话可以选择仓库分支,并决定使用当前工作树还是隔离 Worktree。 -- **右侧代码改动面板**:聊天时直接在右侧查看已更改文件、增删行和当前工作区状态。 -- **代码修改可视化**:直接查看 AI 对文件的编辑、Diff 和执行过程。 -- **权限与确认流**:危险命令、工具调用和 AI 反问可以在桌面端集中审批。 -- **多模型提供商**:支持 Anthropic 兼容 API、第三方模型、WebSearch fallback 和本地配置。 -- **技能市场**:在桌面端发现、预览、安装和管理 ClawHub / SkillHub 第三方技能。 -- **会话活动面板**:集中查看任务、后台任务、SubAgent、团队活动和 sources。 +- **多会话工作台**:标签页、项目切换、终端入口和会话历史集中管理,侧边栏宽度可拖拽。 +- **分支 / Worktree 启动**:新会话可以选择仓库分支,并决定用当前工作树还是隔离 Worktree。 +- **改动逐个文件审阅**:右侧工作区列出本轮改动,点开就是带语法高亮的 Diff,整轮可撤销。 +- **五档权限模式**:从「询问权限」到「跳过权限」,危险命令、工具调用和 AI 反问都在桌面端审批。 +- **模型自选**:Claude / ChatGPT / Grok 官方账号可直接登录;DeepSeek、Kimi、智谱 GLM 等第三方 API 有现成预设;LM Studio、Ollama 的本地模型也接得上。 +- **六套配色主题**:纯白、纸墨、经典暖色、青瓷、墨夜、墨夜蓝,可跟随系统深浅色自动切换。 +- **技能市场**:发现、预览、安装 ClawHub / SkillHub 的第三方技能,来源和安全状态摆在明处。 +- **会话活动面板**:集中查看任务进度、后台任务、SubAgent 与来源。 - **Computer Use**:让 Agent 在授权后截图、点击、输入并控制桌面应用。 -- **H5 远程访问**:用一次性令牌在手机或其他设备上接入当前桌面端会话。 -- **IM 接入**:通过 Telegram / 飞书 / 微信 / 钉钉远程对话、切换项目和审批权限。 -- **定时任务与用量统计**:在桌面端创建计划任务,并查看本机 Token 使用趋势。 +- **桌面宠物**:搭搭、弧弧、补补、回回随任务状态换动作,也能自己做一只(默认关闭)。 +- **H5 远程访问**:扫码用手机浏览器接入当前会话,锁屏切后台都不打断正在跑的任务。 +- **IM 接入**:通过 Telegram / 飞书 / 微信 / 钉钉 / WhatsApp 远程对话、切换项目和审批权限。 +- **定时任务与用量统计**:创建计划任务在独立会话执行,并查看本机 Token 使用趋势。 --- ## 更多文档 -| 文档 | 说明 | +完整文档站: + +| 分区 | 文档 | |------|------| -| [环境变量](docs/guide/env-vars.md) | 完整环境变量参考和配置方式 | -| [第三方模型](docs/guide/third-party-models.md) | 接入 OpenAI / DeepSeek / Ollama 等非 Anthropic 模型 | -| [贡献与质量门禁](docs/guide/contributing.md) | 本地测试、真实模型 baseline、PR 和 release 门禁 | -| [记忆系统](docs/memory/01-usage-guide.md) | 跨会话持久化记忆的使用与实现 | -| [多 Agent 系统](docs/agent/01-usage-guide.md) | 多代理编排、并行任务执行与 Teams 协作 | -| [Skills 系统](docs/skills/01-usage-guide.md) | 可扩展能力插件、自定义工作流与条件激活 | -| [IM 接入](docs/im/) | 通过 Telegram / 飞书 / 微信 / 钉钉远程对话、切换项目和审批权限 | -| [Computer Use](docs/features/computer-use.md) | 桌面控制功能(截屏、鼠标、键盘)— [架构解析](docs/features/computer-use-architecture.md) | -| [桌面端](docs/desktop/) | Electron + React 图形化客户端 — [快速上手](docs/desktop/01-quick-start.md) \| [架构设计](docs/desktop/02-architecture.md) \| [安装指南](docs/desktop/04-installation.md) | -| [全局使用](docs/guide/global-usage.md) | 在任意目录启动 claude-haha | -| [常见问题](docs/guide/faq.md) | 常见错误排查 | -| [源码修复记录](docs/reference/fixes.md) | 相对于原始泄露源码的修复内容 | -| [项目结构](docs/reference/project-structure.md) | 代码目录结构说明 | +| **开始使用** | [这是什么](docs/start/index.md) · [下载与安装](docs/start/install.md) · [连接模型服务](docs/start/models.md) · [跑通第一条会话](docs/start/first-session.md) · [故障排查](docs/start/troubleshooting.md) | +| **桌面端功能** | [功能总览](docs/desktop/index.md) · [Computer Use](docs/desktop/computer-use.md) · [桌面宠物](docs/desktop/pets.md) · [手机 H5 与 IM 接力](docs/desktop/remote.md) | +| **IM 接入** | [总览与配对流程](docs/im/index.md) · [飞书](docs/im/feishu.md) · [Telegram](docs/im/telegram.md) · [微信](docs/im/wechat.md) · [钉钉](docs/im/dingtalk.md) · [WhatsApp](docs/im/whatsapp.md) | +| **命令行** | [安装与启动](docs/cli/index.md) · [命令参考](docs/cli/reference.md) · [环境变量](docs/cli/env.md) | +| **深入原理** | [桌面端架构](docs/internals/desktop.md) · [多 Agent 系统](docs/internals/agent.md) · [Skills 系统](docs/internals/skills.md) · [记忆系统](docs/internals/memory.md) · [Computer Use 架构](docs/internals/computer-use.md) · [本地 Server 与 API](docs/internals/server.md) · [Channel 系统](docs/internals/channel.md) · [项目结构](docs/internals/structure.md) · [参与贡献与质量门禁](docs/internals/contributing.md) | --- diff --git a/docs/AGENTS.md b/docs/AGENTS.md index 813b5bc9..8bae1a14 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -2,8 +2,51 @@ These rules apply to `docs/` changes in addition to the root instructions. -- Keep Chinese pages and their `docs/en/` counterparts aligned when both versions exist. -- Treat `docs/` as the bilingual content and media source for the React site in `site/`; preserve existing terminology and do not replace reference screenshots or media unless the task requires it. -- Run `bun run check:docs` when selected by `bun run check:impact`. -- `check:docs` installs the isolated `site/` dependency tree, then builds and validates the React site. +## Who the docs are for + +Two audiences, nothing else. If a page serves neither, it does not belong here. + +- **People using the app** — `start/`, `desktop/`, `im/`. No prior code knowledge assumed. +- **People reading the source** — `internals/`, `cli/`. Architecture, implementation, contributing. + +Internal process artefacts (migration task lists, validation checklists, design proposals, release runbooks) are not documentation. Keep them out of `docs/`, or fold the durable part into `internals/contributing.md`. + +## Structure + +Five top-level sections: `start/`, `desktop/`, `im/`, `cli/`, `internals/`. Adding a sixth means registering it in the `sections` array of `site/scripts/generate-docs-manifest.mjs`. + +`docs/en/` mirrors the Chinese tree file for file. Keep Chinese pages and their `docs/en/` counterparts aligned. + +## Frontmatter (required on every page) + +```yaml +--- +title: 连接模型服务 +nav_title: 连接模型 # sidebar label, ≤12 CJK characters +description: 一句话说明这篇讲什么。 # feeds search and SEO meta +order: 2 # position inside the section; index.md uses 0 +--- +``` + +The site renders the first paragraph after the H1 as a lede — write it as a normal paragraph, not a blockquote. + +## Chinese typography + +Put a space between Chinese characters and adjacent Latin letters or digits: 「一枚 6 位码」, 「回填 App ID」. Nothing in the render pipeline inserts it for you, so it has to be in the source. Punctuation needs no space. + +## Screenshots + +Product screenshots live in `docs/images/app/` as WebP at 2000px wide, captured from a real build. Never ship a screenshot from an older UI generation; re-capture instead. Redact tokens, QR codes, API keys and paired account names before committing. Cap feature pages at roughly three screenshots. + +Step-by-step setup walkthroughs are the exception: `docs/im/` embeds console screenshots from `docs/images/im//` and needs one per step. Judge those by whether a reader could follow along without them. + +## Routes are file paths + +`docs/start/install.md` publishes to `/start/install`. Renaming a file changes a public URL, so add the old path to `LEGACY_ROUTES` in `site/src/content/docs.js` and to `legacyRoutes` in `site/scripts/prepare-static-output.mjs`. + +`/im/` is linked from inside the desktop app (`desktop/src/pages/AdapterSettings.tsx`). That route cannot move. + +## Checks + +- Run `bun run check:docs` when selected by `bun run check:impact`. It installs the isolated `site/` dependency tree, builds the React site, then validates every local link and image. - Release instructions must stay consistent with `scripts/release.ts`, `.github/workflows/release-desktop.yml`, and the versioned release-notes convention. diff --git a/docs/agent/index.md b/docs/agent/index.md deleted file mode 100644 index 1622facb..00000000 --- a/docs/agent/index.md +++ /dev/null @@ -1,129 +0,0 @@ -# Claude Code 多 Agent 系统文档 - -> 完整的多 Agent 编排使用指南和实现原理文档 - ---- - -## 📚 文档目录 - -### [01-usage-guide.md](./01-usage-guide.md) — 使用指南 - -面向用户的完整使用手册,涵盖: - -- **Agent 工具**:参数详解、生成方式、后台运行 -- **六种内置 Agent**:general-purpose、Explore、Plan、verification、claude-code-guide、statusline-setup -- **后台任务**:异步执行、进度追踪、完成通知 -- **Agent Teams**:团队创建、成员协作、消息通信 -- **Worktree 隔离**:独立环境、分支管理、安全上下文 -- **自定义 Agent**:定义格式、工具池配置、系统提示词 - -**适合人群**:所有 Claude Code 用户 - ---- - -### [02-implementation.md](./02-implementation.md) — 实现原理 - -面向开发者的技术深度解析,涵盖: - -- **架构总览**:5 大 Agent 类别、4 条生成路径 -- **Agent 生成流程**:同步/异步/Fork/Teammate 四种路径详解 -- **工具池系统**:三层过滤、常量定义、权限映射 -- **上下文传递**:CacheSafeParams、系统提示词构建、Fork 缓存优化 -- **Teams 内部机制**:TeamFile 结构、邮箱系统、收件箱轮询、消息路由 -- **后台任务引擎**:LocalAgentTask 生命周期、进度追踪、通知队列 -- **权限同步**:团队级权限、模式传播、bubble 模式 -- **完整数据流**:从 Agent Tool 调用到结果回传 - -**适合人群**:贡献者、架构师、想深入了解实现的开发者 - ---- - -### [03-agent-framework.md](./03-agent-framework.md) — Agent 框架深度解析 - -从源码视角剖析 Claude Code 底层 Agent 架构的设计哲学,涵盖: - -- **核心 Agent 循环**:AsyncGenerator 状态机、五阶段 while(true) 循环 -- **系统提示词工程**:分层构建、缓存边界、CLAUDE.md 加载机制 -- **工具系统设计**:完整生命周期管理、三阶段注册、七步执行管道 -- **上下文管理与压缩**:四级渐进式压缩、系统上下文注入、系统提醒 -- **技能与插件生态**:技能定义与发现、插件系统、钩子系统、MCP 集成 -- **权限与安全体系**:分层权限模型、规则模式匹配 -- **故障恢复机制**:6 种内置恢复策略、模型降级 -- **与 LangChain/ReAct 对比**:架构范式差异、为什么不用 ReAct -- **为什么 Claude Code 能做到这么好**:7 大核心设计原则 - -**适合人群**:想理解 AI Agent 框架设计的架构师、AI 应用开发者、技术研究者 - ---- - -## 🖼️ 配图说明 - -所有配图采用深色背景(#1a1a2e)+ Claude Code Haha 橙蓝品牌色(#FF7A00)风格,与 Claude Code 官方文档一致。 - -| 图片 | 说明 | 所属文档 | -|------|------|----------| -| `01-agent-overview.png` | 多 Agent 系统概览 — 架构全景 | 使用指南 | -| `02-agent-types.png` | 六种内置 Agent — 类型对比矩阵 | 使用指南 | -| `03-spawn-flow.png` | Agent 生成流程 — 四条路径决策树 | 使用指南 | -| `04-agent-teams.png` | Agent Teams 协作 — 团队通信拓扑 | 使用指南 | -| `05-architecture.png` | 实现架构总览 — 核心模块关系 | 实现原理 | -| `06-context-passing.png` | 上下文传递 — CacheSafeParams 数据流 | 实现原理 | -| `07-tool-pool.png` | 工具池系统 — 三层过滤流程 | 实现原理 | -| `08-background-task.png` | 后台任务引擎 — 生命周期状态机 | 实现原理 | -| `09-teams-mailbox.png` | Teams 邮箱系统 — 消息路由拓扑 | 实现原理 | -| `10-fork-cache.png` | Fork 缓存优化 — 字节级一致共享 | 实现原理 | -| `11-agent-framework-overview.png` | Agent 框架架构总览 — 核心组件关系 | 框架解析 | -| `12-agent-core-loop.png` | 核心 Agent 循环 — 五阶段状态机 | 框架解析 | -| `13-system-prompt-pipeline.png` | 系统提示词构建 — 分层缓存流水线 | 框架解析 | -| `14-context-compression.png` | 上下文压缩 — 四级渐进式策略 | 框架解析 | - ---- - -## 🚀 快速开始 - -### 用户 - -1. 阅读 [使用指南](./01-usage-guide.md) -2. 了解六种内置 Agent 及其适用场景 -3. 尝试在对话中使用 Agent 工具生成子代理 -4. 探索 Agent Teams 多代理协作 - -### 开发者 - -1. 阅读 [实现原理](./02-implementation.md) -2. 查看源码位置: - - `src/tools/AgentTool/` — Agent 工具实现 - - `src/tools/TeamCreateTool/` — 团队创建 - - `src/tools/SendMessageTool/` — 代理间通信 - - `src/utils/swarm/` — Swarm 协作基础设施 - - `src/utils/forkedAgent.ts` — Fork 代理上下文 - - `src/tasks/` — 任务管理系统 -3. 理解四条生成路径和上下文传递机制 - ---- - -## 📝 核心概念速查 - -| 概念 | 说明 | -|------|------| -| **Agent Tool** | 主入口工具,接受 prompt + subagent_type 生成子代理 | -| **Subagent** | 独立执行任务的子代理,有自己的工具池和权限 | -| **Fork Agent** | 继承父代理完整上下文的分叉代理,共享 prompt cache | -| **Teammate** | Agent Teams 中的协作成员,通过邮箱通信 | -| **Worktree** | Git worktree 隔离模式,独立文件环境 | -| **LocalAgentTask** | 本地代理任务状态,追踪 running/completed/failed | -| **DreamTask** | 自动记忆整合任务,定期后台运行 | -| **CacheSafeParams** | 缓存安全参数,确保 API 请求前缀字节级一致 | -| **TeamFile** | 团队配置文件,存储成员列表和权限 | -| **Mailbox** | 基于文件的消息队列,支持队友间异步通信 | - ---- - -## 🔗 相关资源 - -- [Claude Code Haha 主页](/) -- [记忆系统文档](/memory/01-usage-guide) -- [Agent Tool 源码](https://github.com/NanmiCoder/cc-haha/tree/main/src/tools/AgentTool/) -- [Swarm 基础设施](https://github.com/NanmiCoder/cc-haha/tree/main/src/utils/swarm/) -- [任务管理系统](https://github.com/NanmiCoder/cc-haha/tree/main/src/tasks/) -- [GitHub Issues](https://github.com/NanmiCoder/cc-haha/issues) diff --git a/docs/channel/02-im-gateway-proposal.md b/docs/channel/02-im-gateway-proposal.md deleted file mode 100644 index f47db0d2..00000000 --- a/docs/channel/02-im-gateway-proposal.md +++ /dev/null @@ -1,506 +0,0 @@ -# IM Gateway 方案设计 `[历史设计稿]` - -> 像 OpenClaw 一样,让 Claude Code Desktop 快速接入任意 IM 平台 -> -> 状态更新:当前实际可用的接入方式请看 [`docs/im/`](../im/)。 -> 本文保留为方案演进记录,不再作为接入说明。 - -

-背景 · -OpenClaw · -方案 · -协议 · -消息流 · -Adapter · -文件清单 · -验证 · -对比 · -开放问题 -

- ---- - -## 一、背景与动机 - -### 现状 - -Claude Code 源码中已有完整的 **Channel 系统**(详见 [01-channel-system.md](./01-channel-system.md)),支持通过 MCP 协议接入 IM 平台。但该系统被六层访问控制锁死: - -1. **编译时门控** — `feature('KAIROS')` / `feature('KAIROS_CHANNELS')` 编译标志 -2. **运行时门控** — GrowthBook `tengu_harbor`(默认 false) -3. **OAuth 门控** — 需要 claude.ai OAuth 登录 -4. **组织策略门控** — Team/Enterprise 必须显式启用 `channelsEnabled: true` -5. **会话白名单** — 需要 `--channels` CLI 参数指定 -6. **插件市场审批** — 插件必须通过 Anthropic 市场审批 - -这些限制使得在我们自托管的桌面端 App 中无法直接使用 Channel 功能。 - -### 目标 - -参考 [OpenClaw](https://github.com/openclaw/openclaw) 的 IM Gateway 架构,在现有桌面端服务器基础上,以**最小改动量**实现 IM 平台接入,让用户可以从 Telegram、飞书、Slack、Discord 等 IM 直接与 Claude 对话并审批权限请求。 - -### 核心策略 - -**不拆解 MCP Channel 的门控**,而是在服务端新增 `/im/` WebSocket 入口,直接复用 `conversationService`(CLI 子进程管理),绕过整个 MCP 层。 - ---- - -## 二、OpenClaw 参考分析 - -[OpenClaw](https://github.com/openclaw/openclaw) 是 GitHub 上 351k+ star 的开源 AI 助手项目,其最大特色是 IM 集成。 - -### 支持的 IM 平台(23+) - -WhatsApp、Telegram、Slack、Discord、Google Chat、Signal、iMessage、IRC、Microsoft Teams、Matrix、飞书(Feishu)、LINE、Mattermost、Nextcloud Talk、Nostr、Synology Chat、Tlon、Twitch、Zalo、微信(WeChat)、WebChat 等。 - -### 架构模式 - -``` -IM 平台 (Telegram / WeChat / Slack / ...) - | - v - ┌─────────────────────┐ - │ Gateway │ - │ (WebSocket 控制面) │ - │ ws://127.0.0.1:18789│ - └──────────┬──────────┘ - | - ├── AI Agent (RPC) — 模型调用 - ├── CLI - ├── WebChat UI - └── 移动端 App -``` - -### 关键设计 - -- **Gateway 是控制面**:所有 IM 消息通过 Gateway 路由到 AI Agent -- **Adapter 模式**:每个 IM 平台一个独立 Adapter,连接到 Gateway -- **多用户隔离**:不同用户/聊天对应不同 Agent 会话 -- **安全机制**:DM 配对码验证未知发送者 - -### 中国 IM 生态 - -社区插件仓库 `openclaw-china` 额外支持:飞书、钉钉、QQ、企业微信。 - ---- - -## 三、方案设计 - -### 架构总览 - -``` -Telegram / 飞书 / Slack / Discord / 微信 ... - | - | (各平台 SDK) - v - IM Adapter(独立进程) ← 每个平台一个 - | - | ws://localhost:3456/im/ - v - ┌──────────────────────┐ - │ IM Gateway │ ← 新增模块 src/server/im/ - │ (WebSocket handler) │ - └──────────┬───────────┘ - | - | 复用 conversationService - v - ┌──────────────────────┐ - │ CLI 子进程 (Claude) │ ← 已有基础设施,零改动 - └──────────────────────┘ -``` - -### 设计决策 - -#### 为什么不直接解锁 MCP Channel 门控? - -MCP Channel 系统设计用于 CLI 交互模式(React/Ink 渲染),需要修改编译标志、GrowthBook 配置、OAuth 逻辑等 6 层代码。而我们的桌面端服务器有完全独立的架构(REST + WS + CLI 子进程),直接在服务端接入更简洁。 - -#### 为什么 Adapter 独立进程? - -- IM SDK 很重(telegraf ~50 deps,wechaty 更多),不应污染服务端 -- 可以独立重启/更新,不影响服务器 -- 自然隔离,一个 Adapter 崩溃不影响其他 -- 与 OpenClaw 架构模式一致 - -#### 为什么用 WebSocket? - -双向实时通信是核心需求(流式回复、权限请求/回复),服务器已有 WebSocket 基础设施,HTTP 轮询会增加延迟和复杂度。 - -### 复用清单 - -以下已有代码可直接复用,**无需修改**: - -| 函数 / 模块 | 文件位置 | 用途 | -|-------------|---------|------| -| `conversationService.*` | `src/server/services/conversationService.ts` | CLI 子进程管理全套 API | -| `shortRequestId()` | `src/services/mcp/channelPermissions.ts:140` | 生成 IM 友好的 5 字母权限 ID | -| `truncateForPreview()` | `src/services/mcp/channelPermissions.ts:160` | 工具输入截断为手机预览大小 | -| `PERMISSION_REPLY_RE` | `src/services/mcp/channelPermissions.ts:75` | 权限回复格式正则匹配 | -| `translateCliMessage()` | `src/server/ws/handler.ts:277` | CLI 消息翻译逻辑(作为参考) | -| `sessionService` | `src/server/services/sessionService.ts` | 会话文件管理 | - ---- - -## 四、消息协议 - -### Adapter -> Gateway - -```typescript -// 注册 -{ type: 'register'; platform: string; adapterId: string; secret?: string } - -// IM 消息 -{ type: 'im_message'; chatId: string; userId: string; userName?: string; content: string; meta?: Record } - -// 权限回复 -{ type: 'permission_reply'; sessionId: string; requestId: string; allowed: boolean } - -// 停止生成 -{ type: 'stop'; chatId: string } - -// 新建会话(用户 /new 命令) -{ type: 'new_session'; chatId: string } -``` - -### Gateway -> Adapter - -```typescript -// 注册确认 -{ type: 'registered'; adapterId: string } - -// 文本回复(流式) -{ type: 'text'; chatId: string; content: string; isComplete: boolean } - -// 思考过程 -{ type: 'thinking'; chatId: string; content: string } - -// 工具调用 -{ type: 'tool_use'; chatId: string; toolName: string; toolUseId: string; input: any } - -// 工具结果 -{ type: 'tool_result'; chatId: string; toolUseId: string; content: any; isError: boolean } - -// 权限请求 -{ - type: 'permission_request'; - chatId: string; - sessionId: string; - requestId: string; - shortId: string; // 5 字母 ID,如 "tbxkq" - toolName: string; - description?: string; - inputPreview: string; // 截断后的工具输入 -} - -// 状态变更 -{ type: 'status'; chatId: string; state: 'thinking' | 'streaming' | 'tool_executing' | 'idle' } - -// 错误 -{ type: 'error'; chatId: string; message: string; code?: string } - -// 完成 -{ type: 'complete'; chatId: string; usage: { input_tokens: number; output_tokens: number } } -``` - ---- - -## 五、消息流详解 - -### 正常对话流 - -``` -用户在 Telegram 发送 "检查 main.ts 有没有 bug" - → Telegram Bot 收到消息 - → Adapter 通过 WebSocket 发送: - { type: "im_message", chatId: "12345", userId: "alice", content: "检查 main.ts" } - → Gateway 收到 - → 查找 chatId->sessionId 映射(不存在则创建新 session) - → conversationService.startSession(sessionId, workDir, sdkUrl) - → conversationService.sendMessage(sessionId, content) - → CLI 子进程启动,调用 Claude API - → CLI 输出 stream_event - → conversationService.onOutput() 回调触发 - → Gateway 翻译为 GatewayMessage - → WebSocket 发送: { type: "text", chatId: "12345", content: "让我看看...", isComplete: false } - → Adapter 调用 ctx.reply("让我看看...") - → 用户在 Telegram 看到回复 -``` - -### 权限审批流 - -``` -CLI 发送 control_request: - { subtype: "can_use_tool", tool_name: "Bash", input: { command: "npm test" } } - → Gateway 收到 - → 生成 shortId = shortRequestId(toolUseId) → "tbxkq" - → WebSocket 发送: - { type: "permission_request", chatId: "12345", shortId: "tbxkq", - toolName: "Bash", inputPreview: '{"command":"npm test"}' } - → Adapter 在 Telegram 发送: - "需要权限确认 - 工具: Bash - 内容: npm test - [允许] [拒绝]" - → 用户点击 [允许] - → Adapter 发送: { type: "permission_reply", requestId: "tbxkq", allowed: true } - → Gateway 调用 conversationService.respondToPermission(sessionId, requestId, true) - → CLI 继续执行 -``` - ---- - -## 六、Adapter 实现 - -### 目录结构 - -``` -adapters/ - telegram/ - index.ts — Telegram Bot Adapter(基于 telegraf) - package.json — 依赖声明 - feishu/ - index.ts — 飞书 Adapter(基于 @larksuiteoapi/node-sdk) - package.json - slack/ - index.ts — Slack Adapter(基于 Bolt) - package.json - discord/ - index.ts — Discord Adapter(基于 discord.js) - package.json - wechat/ - index.ts — 微信 Adapter(基于 wechaty) - package.json -``` - -### Telegram Adapter 示例 - -```typescript -// 伪代码 — 核心逻辑 -import { Telegraf } from 'telegraf' -import WebSocket from 'ws' - -const bot = new Telegraf(process.env.BOT_TOKEN) -const ws = new WebSocket('ws://localhost:3456/im/telegram-001') - -// IM -> Gateway -bot.on('text', (ctx) => { - ws.send(JSON.stringify({ - type: 'im_message', - chatId: String(ctx.chat.id), - userId: String(ctx.from.id), - userName: ctx.from.first_name, - content: ctx.message.text, - })) -}) - -// Gateway -> IM -ws.on('message', (data) => { - const msg = JSON.parse(data) - switch (msg.type) { - case 'text': - bot.telegram.sendMessage(msg.chatId, msg.content, { parse_mode: 'Markdown' }) - break - case 'permission_request': - bot.telegram.sendMessage(msg.chatId, - `需要权限: ${msg.toolName}\n${msg.inputPreview}`, - { reply_markup: { - inline_keyboard: [[ - { text: '允许', callback_data: `permit:${msg.requestId}:yes` }, - { text: '拒绝', callback_data: `permit:${msg.requestId}:no` }, - ]] - }} - ) - break - } -}) - -// 处理按钮回调 -bot.on('callback_query', (ctx) => { - const [, requestId, decision] = ctx.callbackQuery.data.split(':') - ws.send(JSON.stringify({ - type: 'permission_reply', - requestId, - allowed: decision === 'yes', - })) -}) -``` - -### 飞书 Adapter 要点 - -- 使用飞书事件订阅(HTTP 回调模式)接收消息 -- 权限请求使用**交互式卡片**(Message Card),按钮体验更好 -- 支持富文本回复 - -### 各平台消息限制 - -| 平台 | 消息长度限制 | 处理方式 | -|------|------------|---------| -| Telegram | 4096 字符 | 自动分段发送 | -| 飞书 | 无硬限制(建议 < 30KB) | 长消息可折叠 | -| Slack | 40000 字符 | 分 Block 发送 | -| Discord | 2000 字符 | 分段 + Embed | -| 微信 | 2048 字符 | 分段发送 | - ---- - -## 七、文件清单 - -### 需要修改的现有文件(约 35 行改动) - -| 文件 | 改动量 | 内容 | -|------|--------|------| -| `src/server/index.ts` | ~20 行 | 新增 `/im/` WebSocket 升级路径 | -| `src/server/ws/handler.ts` | ~15 行 | WebSocketData 类型扩展为 `'client' \| 'sdk' \| 'im'`,open/message/close 中委托 IM 消息给 gateway | - -### 需要新建的文件 - -| 文件 | 说明 | -|------|------| -| `src/server/im/types.ts` | IM 消息协议类型定义 | -| `src/server/im/gateway.ts` | Gateway 核心 — Adapter 连接管理、chatId->session 路由、消息翻译 | -| `src/server/im/sessionMap.ts` | chatId-sessionId 映射管理、可选持久化 | -| `src/server/im/config.ts` | `~/.claude/im-gateway.json` 配置读取 | -| `adapters/telegram/index.ts` | Telegram Bot Adapter | -| `adapters/telegram/package.json` | 依赖 telegraf + ws | -| `adapters/feishu/index.ts` | 飞书 Adapter | -| `adapters/feishu/package.json` | 依赖 @larksuiteoapi/node-sdk + ws | - -### 配置文件 - -`~/.claude/im-gateway.json`: - -```json -{ - "enabled": true, - "defaultWorkDir": "/path/to/default/project", - "defaultPermissionMode": "default", - "adapters": { - "telegram": { - "secret": "optional-shared-secret", - "allowedUsers": ["123456789"], - "workDir": "/path/to/project" - }, - "feishu": { - "secret": "optional-shared-secret", - "allowedUsers": ["ou_xxx"], - "workDir": "/path/to/another/project" - } - } -} -``` - ---- - -## 八、验证方案 - -### 1. WebSocket 连通性测试 - -```bash -# 启动服务器 -bun run server - -# 用 wscat 模拟 Adapter -wscat -c ws://localhost:3456/im/test-adapter -> {"type":"register","platform":"test","adapterId":"test-001"} -# 期望收到: {"type":"registered","adapterId":"test-001"} - -> {"type":"im_message","chatId":"chat-1","userId":"user-1","content":"hello"} -# 期望收到: 一系列 text/thinking/status/complete 消息 -``` - -### 2. Telegram 端到端测试 - -1. 创建 Telegram Bot(@BotFather) -2. 配置 Bot Token 到 Adapter -3. 启动服务器 + Telegram Adapter -4. 在 Telegram 发送 "hello" -5. 验证收到 Claude 回复 -6. 发送 "读取 package.json" 触发工具调用 -7. 验证权限请求按钮出现 -8. 点击允许,验证工具执行和结果返回 - -### 3. 多会话隔离测试 - -- 从不同 Telegram 聊天发消息,验证会话独立 -- 发送 `/new` 命令,验证新会话创建 -- 验证旧会话不受影响 - -### 4. 异常场景测试 - -- Adapter 断开重连,验证会话恢复 -- CLI 进程崩溃,验证错误消息发送到 IM -- 权限请求超时,验证优雅处理 - ---- - -## 九、与 OpenClaw 对比 - -| 特性 | OpenClaw | 我们的方案 | -|------|----------|-----------| -| 架构 | Gateway + Agent (RPC) | Gateway + CLI 子进程 (SDK WS) | -| IM 平台 | 23+ 内置 | Adapter 模式,按需添加 | -| 权限审批 | 无(直接执行) | 5 字母 ID 审批(复用已有系统) | -| 多用户 | 全局单 Agent | 每 chatId 独立 session | -| AI 模型 | OpenAI / Claude / Gemini 等 | Claude(通过现有 API key) | -| 工具能力 | 浏览器控制、语音等 | 完整 Claude Code 工具集(文件读写、终端、搜索等) | -| 部署方式 | 单体 + 插件 | 服务端 + 独立 Adapter 进程 | -| 改动量 | 全新项目 | 现有服务端 ~35 行改动 + 3 个新模块 | - -### 我们的优势 - -- **Claude Code 原生工具链**:Adapter 连接的不是普通聊天机器人,而是完整的 Claude Code Agent,具备文件读写、代码搜索、Git 操作、终端执行等全部能力 -- **权限安全**:内置权限审批机制,从 IM 端可以审批敏感操作 -- **改动量极小**:核心只改 2 个文件 ~35 行,新增 3 个模块 - -### OpenClaw 的优势 - -- **IM 平台覆盖**:23+ 平台开箱即用 -- **社区生态**:5400+ 社区 Skills -- **语音交互**:支持语音唤醒和对话 -- **多模型**:支持 OpenAI、Gemini 等多种模型 - ---- - -## 十、开放问题 - -> 以下问题在实现前需要进一步思考和决策。 - -### 10.1 安全性 - -- Adapter 连接到 Gateway 时是否需要认证?(shared secret / API key) -- 如何防止未授权的 IM 用户与 Claude 对话?(allowedUsers 白名单是否足够?) -- 是否需要 IP 白名单或 Tailscale 等网络层隔离? - -### 10.2 多用户 / 多项目 - -- 一个 IM 用户是否能切换不同项目目录?(如 `/project /path/to/repo`) -- 是否支持多个用户同时使用同一个 Bot? -- 会话上限和资源管理(同时运行多少个 CLI 子进程?) - -### 10.3 消息体验 - -- 长回复如何处理?逐段发送 vs 等待完成后发送? -- 用户快速连发多条消息,是否需要聚合? -- 代码块在 IM 中的渲染质量(Telegram Markdown vs 飞书富文本 vs 微信纯文本) - -### 10.4 运维 - -- Adapter 进程管理(systemd / pm2 / Docker?) -- 日志和监控 -- 服务器重启后 session 恢复 - -### 10.5 功能边界 - -- 是否支持文件/图片上传?(用户在 IM 中发送截图给 Claude) -- 是否支持 Claude 返回的图片/文件?(如截图、生成的文件) -- 是否支持 slash commands(`/help`、`/status`、`/new`)? - ---- - -## 参考资料 - -- [OpenClaw GitHub](https://github.com/openclaw/openclaw) — 351k star 开源 AI 助手 -- [OpenClaw China](https://github.com/BytePioneer-AI/openclaw-china) — 中国 IM 社区插件 -- [Channel 系统架构解析](./01-channel-system.md) — 源码 Channel 系统文档 -- [Telegraf](https://github.com/telegraf/telegraf) — Telegram Bot Framework -- [@larksuiteoapi/node-sdk](https://github.com/larksuite/oapi-sdk-nodejs) — 飞书 SDK diff --git a/docs/channel/index.md b/docs/channel/index.md deleted file mode 100644 index 491ca71d..00000000 --- a/docs/channel/index.md +++ /dev/null @@ -1,47 +0,0 @@ -# Channel 源码研究 - -> 这里讲的是 Claude Code 原生的 Channel / MCP 体系。 -> 如果你要配置当前桌面版的 Telegram / 飞书接入,请先看 [IM 接入文档](../im/)。 - -## 这组文档是干什么的 - -这个仓库当前实际可用的 IM 接入方案,是 `Desktop Webapp + adapters/* + /api/adapters + /ws/:sessionId`。 - -`docs/channel/` 保留的价值主要是: - -- 解释上游 Claude Code 的原生 Channel 机制 -- 记录历史上为什么没有直接沿用那套机制做当前 IM 接入 -- 作为后续架构演进时的参考资料 - -## 文档目录 - -### [01-channel-system.md](./01-channel-system.md) - -从源码视角分析 Claude Code 原始 Channel 系统,包括: - -- Channel 的概念模型 -- MCP 通知和工具出入站协议 -- 六层门控与权限中继 -- Plugin Channel 的注册和安全边界 - -### [02-im-gateway-proposal.md](./02-im-gateway-proposal.md) - -这是历史方案设计文档,记录了从 `IM Gateway` 设想演进到“独立 Adapter 直连 `/ws/:sessionId`”的过程。 - -它适合回答: - -- 为什么最后没有走完整 Gateway -- 为什么当前实现选择了 `adapters/*` -- 设计阶段曾经考虑过哪些替代方案 - -## 相关入口 - -- [IM 接入总览](../im/) -- [Telegram 接入](../im/telegram) -- [飞书接入](../im/feishu) - -## 适合谁看 - -- 想研究 Claude Code 原生 IM / Channel 思路的开发者 -- 想理解当前仓库 IM 实现为什么没有直接复用 Channel 的贡献者 -- 想做架构对比和二次设计的人 diff --git a/docs/guide/env-vars.md b/docs/cli/env.md similarity index 92% rename from docs/guide/env-vars.md rename to docs/cli/env.md index d470fd49..9a389008 100644 --- a/docs/guide/env-vars.md +++ b/docs/cli/env.md @@ -1,8 +1,15 @@ +--- +title: 环境变量 +nav_title: 环境变量 +description: 认证、模型、Azure、本地运行相关的环境变量与实际生效顺序。 +order: 2 +--- + # 环境变量 Claude Code Haha 有两条配置路径: -- 桌面端用户优先在 **设置 → Providers** 中选择、测试并激活提供商。应用会管理对应的认证、模型映射和协议代理。 +- 桌面端用户优先在 **设置 → 服务商** 中选择、测试并激活提供商。应用会管理对应的认证、模型映射和协议代理。 - 从源码运行 CLI 时,可以使用 `.env`、Shell 环境变量或 Claude Code 的 `settings.json`。 不要在多个位置重复保存同一把 API Key。排查问题时,先确认当前是否激活了桌面端 Provider。 @@ -39,7 +46,7 @@ Azure OpenAI 使用独立的 Responses API 路径: 示例: -```bash +```ini CLAUDE_CODE_USE_AZURE_OPENAI=1 AZURE_OPENAI_BASE_URL=https://your-resource.cognitiveservices.azure.com AZURE_OPENAI_API_VERSION=2025-04-01-preview @@ -57,7 +64,7 @@ AZURE_OPENAI_CODEX_DEPLOYMENT=your_codex_deployment | `DISABLE_TELEMETRY` | 设为 `1` 禁用遥测 | | `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` | 设为 `1` 禁用非必要网络请求 | -本地 Server 的 `SERVER_HOST`、`SERVER_PORT`、`SERVER_AUTH_REQUIRED` 等变量见 [本地 Server](../reference/local-server.md)。 +本地 Server 的 `SERVER_HOST`、`SERVER_PORT`、`SERVER_AUTH_REQUIRED` 等变量见 [本地 Server](../internals/server.md)。 ## 配置方式 @@ -71,7 +78,7 @@ AZURE_OPENAI_CODEX_DEPLOYMENT=your_codex_deployment 应用管理的 Provider 环境写入隔离的 Haha 配置,不需要手工复制到 `~/.claude/settings.json`。当 CLI 读取到已激活的 Provider 时,会复用其认证、模型和协议设置;`openai_chat` 与 `openai_responses` Provider 会自动使用本机回环代理。 -详细流程见 [第三方模型](./third-party-models.md)。 +详细流程见 [第三方模型](../start/models.md)。 ### `.env` 文件 @@ -83,7 +90,7 @@ cp .env.example .env 一个 Anthropic 兼容接口的最小示例: -```bash +```ini ANTHROPIC_AUTH_TOKEN=sk-example ANTHROPIC_BASE_URL=https://provider.example.com/anthropic ANTHROPIC_MODEL=provider-model @@ -127,5 +134,5 @@ ANTHROPIC_DEFAULT_OPUS_MODEL=provider-model - 不要提交 `.env`、Provider 配置或包含密钥的 `settings.json`。 - 不要在截图、Issue、日志或诊断包中暴露完整 Token。 - 使用 `CLAUDE_CONFIG_DIR` 做测试隔离,避免读写真实用户配置。 -- `--print` 会跳过工作区信任对话框,只能在可信目录运行。更多限制见 [CLI 参考](./cli-reference.md)。 +- `--print` 会跳过工作区信任对话框,只能在可信目录运行。更多限制见 [CLI 参考](./reference.md)。 - 远程访问本地 Server 时,不要把 CORS 当成身份认证;请启用 H5 Token 或显式鉴权。 diff --git a/docs/cli/index.md b/docs/cli/index.md new file mode 100644 index 00000000..43d5c593 --- /dev/null +++ b/docs/cli/index.md @@ -0,0 +1,115 @@ +--- +title: 安装与启动 +nav_title: 安装与启动 +description: 从源码运行 CLI:安装依赖、配置模型服务、在任意目录启动。 +order: 0 +--- + +# 安装与启动 + +CLI 是 Claude Code Haha 的内核,桌面端每个会话背后跑的都是它。如果你只想用图形界面,装桌面端就够了,见 [下载与安装](../start/install.md);下面这些步骤是给需要终端交互、`--print` 脚本自动化,或者准备读源码、提 PR 的人看的。 + +CLI 目前只从源码运行,没有单独的安装包。 + +## 获取源码 + +先装好 [Git](https://git-scm.com/downloads) 和 [Bun](https://bun.sh),然后: + +```bash +git clone https://github.com/NanmiCoder/cc-haha.git +cd cc-haha +bun install +``` + +## 配置模型服务 + +```bash +cp .env.example .env +``` + +编辑 `.env`,至少给出一种可用的认证方式、接口地址和模型。一个 Anthropic 兼容接口的最小配置是这样: + +```ini +ANTHROPIC_AUTH_TOKEN=sk-example +ANTHROPIC_BASE_URL=https://provider.example.com/anthropic +ANTHROPIC_MODEL=provider-model +``` + +变量含义、认证头的区别、Azure 与其他协议的写法见 [环境变量](./env.md)。如果你已经在桌面端配好了服务商,CLI 会直接复用那份配置,不需要再写 `.env`。 + +不要把真实 API Key 提交到 Git,也不要在 Issue、截图或诊断附件里公开它。 + +## 启动并验证 + +macOS、Linux 或 Git Bash: + +```bash +./bin/claude-haha +./bin/claude-haha -p "概括当前项目的目录结构" +``` + +Windows PowerShell 或 cmd: + +```powershell +bun --env-file=.env ./src/entrypoints/cli.tsx +``` + +看到流式回复和工具调用,说明模型服务、项目目录和 CLI 已经连通。 + +## 在任意目录启动 + +`./bin/claude-haha` 只在仓库里能用。把它加进 `PATH`,就能在任何项目目录直接敲 `claude-haha`,CLI 会自动把当前工作目录当成项目根。 + +macOS 和 Linux 在 `~/.bashrc` 或 `~/.zshrc` 中添加: + +```bash +# 方式一:加入 PATH(推荐) +export PATH="$HOME/path/to/claude-code-haha/bin:$PATH" + +# 方式二:alias +alias claude-haha="$HOME/path/to/claude-code-haha/bin/claude-haha" +``` + +改完重新加载: + +```bash +source ~/.zshrc # 或 source ~/.bashrc +``` + +Windows 的 Git Bash 同样在 `~/.bashrc` 中加 `PATH`: + +```bash +export PATH="$HOME/path/to/claude-code-haha/bin:$PATH" +``` + +验证方式是换个目录再启动,然后问它「当前目录是什么」: + +```bash +cd ~/your-other-project +claude-haha +``` + +### Windows 配 WSL 工具链 + +如果 `claude-haha` 跑在 Windows 或 Git Bash 里,而 Node、Python、uv、bun 这些工具装在 WSL 中,可以显式经 WSL 调用: + +```bash +wsl -e bash -lc 'node --version && python3 --version' +``` + +检测到 `wsl` / `wsl.exe` 调用时,CLI 会自动设置 `MSYS2_ARG_CONV_EXCL=*`,避免 Git Bash 把 `/home/...` 这类 WSL 路径错误转换成 `C:/Program Files/Git/home/...`。 + +想让 Bash 工具默认进 WSL,启动前设置: + +```bash +export CLAUDE_CODE_SHELL_PREFIX='wsl -e bash -lc' +``` + +Computer Use 控制的仍然是 Windows 桌面应用,WSL 内的命令行工具不需要写进 `computer-use-config.json`。只用 WSL 工具链、不需要桌面控制时,加 `--no-computer-use`,或在 设置 → Computer Use 里关掉它。 + +## 下一步 + +- [命令参考](./reference.md):命令行参数、无头模式与恢复模式 +- [环境变量](./env.md):认证、模型与本地运行变量的完整清单 +- [架构总览](../internals/index.md):CLI、Server、桌面壳与 Adapter 的分工 +- [参与贡献与质量门禁](../internals/contributing.md):提 PR 之前要跑哪些检查 diff --git a/docs/guide/cli-reference.md b/docs/cli/reference.md similarity index 97% rename from docs/guide/cli-reference.md rename to docs/cli/reference.md index ae0a52b9..0e7c3cf6 100644 --- a/docs/guide/cli-reference.md +++ b/docs/cli/reference.md @@ -1,4 +1,11 @@ -# CLI 参考 +--- +title: 命令参考 +nav_title: 命令参考 +description: 交互式会话、--print 无头模式、恢复模式与常用命令行参数。 +order: 1 +--- + +# 命令参考 Claude Code Haha 默认启动交互式会话,也可以用 `--print` 作为脚本、CI 或其他程序中的非交互式 Agent。源码仓库中的命令是 `./bin/claude-haha`;安装后的可执行文件名称以安装方式为准。 diff --git a/docs/desktop/01-quick-start.md b/docs/desktop/01-quick-start.md deleted file mode 100644 index e5425edb..00000000 --- a/docs/desktop/01-quick-start.md +++ /dev/null @@ -1,201 +0,0 @@ -# Claude Code Haha Desktop 快速上手 - -这篇指南从安装后的第一次打开开始,带你完成服务商配置、创建会话、审阅代码和查看后台任务。建议先用「询问权限」完成一轮小任务,再逐步启用 Auto、H5 或 Computer Use。 - -![完整展开项目与历史侧栏的 Main Session,中央会话包含输入、模型和权限入口](../images/desktop_ui/25_main_session.png) - -## 一、首次启动:先让一个模型可用 - -打开「设置 → 服务商」。桌面端提供两类接入方式。 - -### 官方登录 - -不想手动填写 API Key 时,可以选择: - -- **Claude 官方**:浏览器登录 Claude.ai。 -- **ChatGPT 官方**:通过 OpenAI OAuth 登录。 -- **Grok 官方**:通过 xAI OAuth 登录。 - -授权完成后回到桌面端,再从模型选择器中选择账号实际开放的模型。模型目录会随账号和服务端能力变化;文档中出现某个模型名称,不代表所有账号都一定可用。 - -### API 服务商 - -使用 API Key 时: - -1. 从内置预设中选择服务商;常用预设随桌面端提供,不依赖临时的远程列表。 -2. 填写 API Key 和 Base URL。 -3. 确认 API 格式与鉴权方式: - - Anthropic Messages - - OpenAI Chat Completions - - OpenAI Responses -4. 核对主模型及 Haiku、Sonnet、Opus 等角色模型映射。 -5. 先测试连接,成功后再保存并设为默认服务商。 - -不在预设中的服务可以使用 Custom。不要把 API Key、OAuth 凭证或完整设置文件贴到公开 Issue。 - -当前内置能力包括 Claude 官方模型目录、OpenAI GPT-5.6 系列、Grok 官方模型,以及 Kimi Code K3 等预设。它们的上下文、thinking 和 effort 支持以当前服务商返回的能力为准。 - -## 二、创建第一条会话 - -1. 点击侧边栏的新建按钮,或按 `Ctrl/Cmd + N`。 -2. 选择真实的项目目录。Agent 的文件搜索、Git 状态、Diff 和工具操作都会以它为边界。 -3. 选择模型、思考强度和权限模式。 -4. 输入一个结果可验证的小任务,例如: - - ```text - 先阅读这个项目的 README 和 package.json,告诉我本地开发如何启动。不要修改文件。 - ``` - -5. 观察流式回复、工具调用和权限请求。需要停止时按 `Ctrl/Cmd + .`。 - -每个标签对应一个会话。正在运行、失败和空闲状态会显示在标签或会话列表中;关闭仍在运行的标签前,桌面端会要求确认。 - -## 三、选择正确的权限模式 - -桌面端提供五种会话权限模式。 - -![在新建会话中展开权限菜单,查看询问权限、接受编辑、自动、计划和跳过权限五种模式](../images/desktop_ui/18_permission_modes.png) - -| 模式 | 行为 | 建议场景 | -|------|------|----------| -| **询问权限** (`default`) | 工具执行前逐项询问 | 第一次进入项目、陌生仓库、风险不明确 | -| **接受编辑** (`acceptEdits`) | 自动允许文件编辑,其他操作仍询问 | 已确认修改范围,仍希望审查命令 | -| **自动模式** (`auto`) | Claude 审查工具调用,执行其认为安全的操作并阻止高风险操作 | 熟悉项目后减少重复审批 | -| **计划模式** (`plan`) | 只研究和规划,不执行修改 | 先评审方案、做只读调查 | -| **跳过全部** (`bypassPermissions`) | 跳过所有权限检查 | 仅限完全可信、可恢复的隔离环境 | - -第一次启用自动模式会显示风险确认。Auto 能减少权限询问,但分类并不等于绝对安全;仍应限制工作目录、检查 Diff,并保留版本控制或备份。 - -跳过全部的风险高于 Auto。不要在含生产凭证、个人资料或不可恢复数据的目录中使用。 - -活跃任务运行期间不能切换权限模式。请先等待当前轮结束或主动停止,再修改模式。 - -## 四、对话、附件与长内容定位 - -### 输入与附件 - -- `Enter` 默认发送,`Shift + Enter` 换行;也可以在通用设置中改为 `Ctrl/Cmd + Enter` 发送。 -- 输入 `/` 打开斜杠命令。 -- 输入 `@` 搜索并引用工作区文件。 -- 可粘贴图片、拖入文件或通过文件选择器添加文件、目录和 PDF。 -- 聊天中的本地附件可以用系统默认应用打开,或从「打开方式」中选择外部应用。 - -附件内容会随会话恢复。若原文件已经移动、删除或当前系统无法访问,打开操作会给出失败提示;这不代表历史消息被删除。 - -### 查找与导航 - -- `Ctrl/Cmd + F`:在当前页面或长对话中查找,并在匹配项之间跳转。 -- 对话导航:从用户消息和 AI 回复生成目录,适合快速回到长任务的关键节点。 -- 消息操作:复制或从某条消息分叉,触屏 H5 中也会保持可发现。 - -侧边栏的会话搜索、对话内查找和工作区文件搜索是三种不同入口:一个找会话,一个找当前对话内容,一个找项目文件。 - -## 五、在工作区审阅改动 - -![展开后的工作台列出真实 Git 变更文件、文件类型和增删行统计](../images/desktop_ui/22_workspace_changed_files.png) - -打开右侧工作区后,可以: - -- 浏览 Git 工作区与变更文件。 -- 搜索尚未在目录树中展开的文件;正式桌面安装包已内置对应平台的 ripgrep。 -- 预览文本、图片和常见文件。 -- 在 Diff 中切换文件,查看旧行、新行和语法高亮。 -- 点击单行添加评论,或按住 `Shift` 选择同一侧的连续行。 -- 把评论和代码引用带回聊天输入框,让 Agent 根据明确位置继续修改。 - -被拒绝的 Write/Edit 不会因为工具“尝试过”就算作真实文件变更。最终仍应以磁盘内容和 Git Diff 为准。 - -![打开真实代码 Diff,并在新侧代码行上展开本地评论输入框](../images/desktop_ui/23_workspace_diff_review.png) - -## 六、查看任务、SubAgent 与团队 - -当会话启动 Task、后台任务、SubAgent 或 Agent Team 后,活动面板会集中展示状态。 - -- 运行中的 SubAgent 可以立即打开,不必等到结束。 -- 详情会继续刷新运行记录和工具调用。 -- 后台任务支持停止,并区分完成、失败与已停止。 -- 完全退出再启动后,已经持久化的终态不会被简单恢复成“进行中”。 - -短暂的轮询失败可能只影响面板刷新,不等于任务数据已经丢失。遇到持续异常时,先刷新,再到「设置 → 诊断」收集信息。 - -## 七、安装技能与管理 Agent - -### 技能市场 - -从技能市场可以搜索、筛选、查看来源与安全提示,并安装支持的第三方技能。安装前应阅读技能内容、来源和免责声明;技能本质上可能扩大 Agent 可执行的工作流。 - -![技能市场总览,展示来源状态、安全免责声明、筛选器和安装状态](../images/desktop_ui/21_skill_marketplace.png) - -### Agent 管理 - -进入「设置 → Agents」可以: - -- 查看内置、用户、项目、插件、策略和 CLI 参数等来源。 -- 判断同名 Agent 当前哪个定义生效、哪个被覆盖。 -- 创建、编辑或删除用户级和项目级 Agent。 -- 为每个 Agent 分别配置模型、思考强度、允许工具和系统提示词。 - -![创建 Agent:选择用户或项目范围,并配置模型、effort、工具与系统提示词](../images/desktop_ui/17_agent_create.png) - -用户 Agent 写入 `~/.claude/agents/`,项目 Agent 写入当前项目的 `.claude/agents/`。内置、插件与策略来源保持只读。 - -模型和 effort 可以继承主会话,也可以单独指定;不受所选模型支持的 effort 可能被降低或忽略。完整规则见[多 Agent 使用指南](../agent/01-usage-guide.md#六自定义-agent)。 - -## 八、使用桌面宠物 - -![桌面宠物设置:选择角色、开启悬浮窗口并调整外观](../images/desktop_ui/14_pet_settings_overview.png) - -按下面的顺序即可开始使用: - -1. 进入「设置 → 宠物」。 -2. 从搭搭 Dada、弧弧 Huhu、补补 Bubu、回回 Huihui 中选择一个内置角色。 -3. 打开「显示桌面宠物」,透明悬浮窗口会立即出现。 -4. 按需要调整 96–192px 大小、动画和「显示运行中的任务面板」。 -5. 在桌面上拖动宠物调整位置;单击宠物会唤起主窗口,单击任务行会返回对应会话,右键可以关闭宠物。 - -任务面板只显示正在工作、等待处理或失败的活跃任务;没有活跃任务时会自动隐藏。右键关闭宠物不会停止正在执行的任务,下次可以回到「设置 → 宠物」重新开启。 - -还可以通过「添加宠物」做一只自己的:用一张透明 PNG / WebP 现成图,或按弹窗里的提示词让任意画图 AI 生成一张动作表再选进来(尺寸会自动对齐)。全程在本地完成,不会调用当前聊天模型,也不消耗对话额度。提示词、动作表模板、图片要求和故障排查见[桌面宠物使用指南](./pets.md)。 - -宠物仅在 Electron 桌面端运行,不会出现在 H5 页面中。窗口置顶、拖动与多显示器行为仍受操作系统窗口管理影响。 - -## 九、从手机或 IM 继续使用 - -### H5 - -在「设置 → H5 访问」开启后,桌面服务会提供带 Token 的二维码和连接地址。普通局域网访问使用同一个 H5 页面、REST API 和 WebSocket 服务;需要稳定书签或反向代理时可固定端口。 - -H5 会暴露当前桌面服务的核心能力,只应在可信网络和自己的代理中使用。详细步骤见[H5 访问](./06-h5-access.md)。 - -### IM - -「设置 → IM 接入」支持: - -- 微信 -- 钉钉 -- WhatsApp -- Telegram -- 飞书 - -扫码绑定平台账号并不等于授权所有联系人。用户仍需发送一次性配对码,或被加入允许列表。详见[IM 接入总览](../im/index.md)。 - -## 十、快捷键 - -| 快捷键 | 功能 | -|--------|------| -| `Ctrl/Cmd + N` | 新建会话 | -| `Ctrl/Cmd + F` | 当前页面或对话内查找 | -| `Ctrl/Cmd + K` | 打开全局会话搜索 | -| `Ctrl/Cmd + .` | 停止当前生成 | -| `Enter` | 按当前发送设置发送 | -| `Shift + Enter` | 默认设置下换行 | -| `/` | 打开斜杠命令 | -| `@` | 搜索工作区文件 | -| `Escape` | 关闭当前弹层 | - -## 下一步 - -- 系统了解能力:[功能详解](./03-features.md) -- 安装、更新和 Web UI:[安装指南](./04-installation.md) -- 连接、索引或恢复异常:[常见问题](./05-FAQ.md) -- 手机访问与安全部署:[H5 访问](./06-h5-access.md) diff --git a/docs/desktop/03-features.md b/docs/desktop/03-features.md deleted file mode 100644 index 5748226a..00000000 --- a/docs/desktop/03-features.md +++ /dev/null @@ -1,260 +0,0 @@ -# 桌面端功能详解 - -本页按用户工作流说明当前桌面端的主要能力和边界。第一次使用建议先读[快速上手](./01-quick-start.md)。 - -## 会话与项目 - -每个会话绑定一个工作目录。桌面端会把会话列表、标签页、模型、权限模式和项目上下文组织到同一个工作台中。 - -- 新建会话时选择目录、模型、思考强度与权限模式。 -- 侧边栏按项目和时间组织历史会话。 -- 多标签支持切换、排序和批量关闭。 -- 会话恢复会保留已提交的消息、附件上下文、任务终态和阅读位置。 -- 异常结束的当前轮可以基于服务端记录的 checkpoint 撤销,不依赖界面猜测文件状态。 - -如果工作目录已经移动或删除,历史消息仍可阅读,但文件搜索、预览和工具操作会受到限制。 - -## 对话与内容导航 - -对话区支持 Markdown、代码高亮、Mermaid、思考块、工具调用、权限请求和 AI 提问。 - -### 长对话 - -- `Ctrl/Cmd + F` 在当前对话中查找,并支持上一个、下一个匹配项。 -- 对话导航从用户消息和 AI 回复生成可扫描目录。 -- 虚拟化长列表会恢复阅读位置,避免重新挂载后直接跳到底部。 -- 可以从历史节点继续或分叉任务;最终状态以服务端会话记录为准。 - -### 附件 - -输入框支持粘贴图片、拖放上传和文件选择,可添加图片、普通文件、目录与 PDF。 - -- 本地附件可以直接用默认应用打开,也可以选择「打开方式」。 -- 完全退出或恢复会话后,已经提交的附件仍会作为上下文恢复。 -- 文件被移动或删除时,桌面端会报告无法打开,但不会删除历史消息。 -- 外部反馈或诊断报告不会主动包含附件正文。 - -## 工作区、搜索与 Diff 评审 - -右侧工作区面向“查看真实文件和审阅改动”,不是聊天消息的装饰视图。 - -### 文件与搜索 - -- 浏览项目目录、Git 状态和变更文件。 -- 搜索尚未展开或加载的目录。 -- 正式桌面安装包内置对应平台与架构的 ripgrep,不要求用户单独安装 `rg`。 -- 搜索结果可以继续预览或加入聊天上下文。 -- 文件不存在、越过工作目录或结果过大时会返回明确状态。 - -### Diff 与评论 - -![真实工作台中的代码 Diff,可从新侧或旧侧代码行打开本地评论](../images/desktop_ui/23_workspace_diff_review.png) - -- 变更文件按状态展示,并支持在预览标签之间切换。 -- Diff 保留旧行、新行和语法高亮。 -- 点击一行可添加评论。 -- 按住 `Shift` 可选择同一侧连续多行。 -- 评论会连同文件路径、行号和代码引用回到聊天输入框。 - -工具调用被拒绝后,未落盘的 Write/Edit 不应显示成真实变更。交付前仍建议检查 Git Diff。 - -### Browser Preview - -工作台可以从「文件」切换到「浏览器」,在应用内预览网页: - -![内置 Browser Preview 打开用于文档演示的 example.com,并显示地址栏、截图和元素选择入口](../images/desktop_ui/24_browser_preview.png) - -- 地址栏支持本地开发地址和普通 HTTPS 页面。 -- 「截图」会把当前页面画面带回会话。 -- 「选择元素」会把页面元素、选择器和截图作为明确上下文交给 Agent。 -- 网页登录态、Cookie 和外部站点权限仍属于敏感数据;公开截图应使用无登录的演示页面。 - -## 五种权限模式 - -| 模式 | 自动执行范围 | 风险提示 | -|------|--------------|----------| -| 询问权限 | 每次工具执行前确认 | 默认且最适合陌生项目 | -| 接受编辑 | 自动批准文件编辑,其他操作仍询问 | 仍需审查命令与外部访问 | -| 自动模式 | 由 Claude 审查工具调用并执行其认为安全的操作 | 不能保证绝对安全,首次启用需确认 | -| 计划模式 | 研究与规划,不执行修改 | 适合先评审方案 | -| 跳过全部 | 跳过所有权限检查 | 只用于完全可信、可恢复的隔离环境 | - -同一会话中的多个权限请求会分别保留。活跃轮次期间权限模式保持锁定,避免界面选择与实际运行状态不一致。 - -## 服务商、官方登录与模型 - -「设置 → 服务商」同时覆盖官方账号和 API Provider。 - -### 官方登录 - -- Claude 官方:使用 Claude.ai 账号。 -- ChatGPT 官方:使用 OpenAI OAuth,模型目录包括账号实际开放的 GPT 系列。 -- Grok 官方:使用 xAI OAuth,支持登录、刷新和动态模型目录。 - -桌面端会展示当前账号与 Provider 实际返回的模型目录,也支持 Claude、OpenAI、Grok 官方登录和第三方自定义模型。名称出现在目录中不等于当前账号一定拥有调用权限。 - -### API Provider - -- 可选 Anthropic Messages、OpenAI Chat Completions 或 OpenAI Responses。 -- 内置常用预设,也保留可编辑的 Custom。 -- 自定义 Provider 支持主模型和 Haiku、Sonnet、Opus 角色映射。 -- 可按模型配置上下文、thinking 和 effort 能力。 -- Kimi Code 预设使用 K3 Coding API;具体模型、上下文和鉴权仍以当前预设及服务端为准。 - -系统代理会按请求目标动态解析,模型请求、OAuth 与适配器不必永久沿用应用启动瞬间的代理结果。代理抖动后仍可能需要等待连接恢复;不要把“支持系统代理”理解成所有代理软件和规则都已完成实机覆盖。 - -## 思考强度 - -模型选择器会根据当前模型与 Provider 显示可用的 effort 档位,并尽量在会话恢复后保留选择。 - -- 档位可能包括 `low`、`medium`、`high`、`xhigh` 和 `max`。 -- 不支持某档的模型可能向下调整或忽略该值。 -- effort 不是固定的 thinking token 数。 -- 某些模型会强制 required 或 adaptive thinking,优先于通用开关。 - -## 技能市场 - -技能市场提供搜索、筛选、详情、来源、安装状态和安全提示。 - -![技能市场展示第三方安全免责声明、来源状态、筛选条件和安装状态](../images/desktop_ui/21_skill_marketplace.png) - -- 安装前阅读技能内容和来源。 -- 第三方技能可能带来新的工具、脚本或外部依赖。 -- 暗色主题和窄窗口下仍可浏览详情。 -- 设置页中的技能列表会与市场安装结果同步。 - -不要仅凭名称安装技能,也不要把实验性技能当作桌面端内置承诺。 - -## Agent 管理 - -「设置 → Agents」展示内置、用户、项目、插件、策略和 CLI 参数等来源,以及同名定义的覆盖关系。 - -![创建 Agent:设置作用范围、模型、effort、工具与系统提示词](../images/desktop_ui/17_agent_create.png) - -### 可编辑范围 - -- **用户 Agent**:对所有项目可用,写入 `~/.claude/agents/*.md`。 -- **项目 Agent**:只在当前项目可用,写入 `<项目>/.claude/agents/*.md`。 -- 内置、插件、策略和 CLI 参数来源只读。 - -### 每个 Agent 的设置 - -- 名称、描述与系统提示词。 -- 继承或指定模型,包括自定义模型 ID。 -- 单独设置 effort。 -- 使用全部工具、禁用工具,或选择内置、MCP 与自定义工具规则。 - -保存成功后,桌面端会尝试刷新当前会话。刷新失败不会回滚已经写入的 Agent 文件;下一次启动仍会重新读取。模型或 effort 是否完整生效取决于最终解析出的 Provider 能力。 - -完整格式、来源优先级和继承规则见[多 Agent 使用指南](../agent/01-usage-guide.md#六自定义-agent)。 - -## 活动、SubAgent 与 Task - -会话活动面板汇总: - -- 后台任务 -- SubAgent -- Task -- Agent Team -- 相关来源和状态 - -运行中的 SubAgent 可以立即打开,详情会持续刷新执行记录与工具调用。Task 状态会防止旧响应覆盖较新的完成状态;短暂轮询失败时,列表会保留最近的可信数据。 - -完成、失败和停止等终态会写入会话历史。恢复后仍应以服务端保存的状态为准,而不是只看某次页面刷新。 - -## 定时任务 - -![新建定时任务表单,展示任务说明、模型、工作目录、完整权限警告、频率和通知](../images/desktop_ui/20_scheduled_task.png) - -定时任务可以保存提示词、工作目录、模型和 Cron 计划,并查看运行历史或手动触发。当前新建任务固定使用「所有权限」运行,创建表单不会让用户选择权限模式,因此应使用最小范围工作目录并先审查提示词。 - -定时任务依赖本机桌面服务运行。关机、应用未启动或网络不可用时,不应把它当作云端永久在线调度器。 - -## 桌面宠物 - -「设置 → 宠物」控制 Electron 透明悬浮窗口。选择搭搭、弧弧、补补或回回后,打开「显示桌面宠物」即可使用;大小可在 96–192px 之间调整,也可以单独关闭动画。 - -- 内置宠物会响应空闲、工作、等待处理和失败状态。 -- 空闲时会注视鼠标,鼠标移入会跳跃;单击会挥手并唤起主窗口。 -- 可直接拖动宠物改变位置,窗口会在下次启动时恢复;右键选择「关闭宠物」只关闭悬浮窗口,不会停止任务。 -- 开启「显示运行中的任务面板」后,可以查看最近的活跃任务并单击返回对应会话;没有活跃任务时面板自动隐藏。 -- 关闭任务面板后,有活跃任务时会保留数字角标,单击角标可以再次展开。 - -### 添加自己的宠物 - -![添加宠物:三种做法](../images/desktop_ui/15_pet_create_methods.png) - -点击「添加宠物」,填写宠物 ID、显示名称和描述,再选择做法;三种都可用: - -- **用一张现成的图**:导入透明 PNG / WebP,由应用在本地添加呼吸、漂浮和任务状态等轻动画。这种宠物不会跑动,也不会跟随鼠标。 -- **用 AI 画一只会跑会跳的**:弹窗内给出可复制的提示词、对照模板和检查清单,用任意能画图的 AI 产出一张 8 列 × 9 行的动作表,再选进来。 -- **我已经有动作图了**:跳过教程,直接导入已经画好的动作表或成品图集。 - -后两种路径导入时会在本地自动切格、缩放对齐、镜像补出向左跑的一帧,因此源图尺寸不必精确;已经是 `1536 × 2288` 的成品图集会原样保留。 - -创建成功后,新宠物会自动被选中。自定义宠物保存在本机 `~/.claude/cc-haha/pets`(设置 `CLAUDE_CONFIG_DIR` 时跟随该目录);当前没有界面内删除按钮,需要通过「打开文件夹」移除对应宠物包后再刷新。 - -宠物仅在桌面端运行,不在 H5 中显示,也不能直接批准权限或回答交互问题。窗口置顶、拖动与多显示器行为仍受不同操作系统约束。完整图片规格、安全限制与故障排查见[桌面宠物使用指南](./pets.md)。 - -## IM 接入 - -「设置 → IM 接入」支持五个平台: - -| 平台 | 连接方式 | -|------|----------| -| 微信 | 扫码绑定账号 | -| 钉钉 | 扫码或凭证接入 Stream | -| WhatsApp | 通过已连接设备扫码 | -| Telegram | Bot Token | -| 飞书 | App ID / App Secret 与长连接 | - -平台账号绑定完成后,实际联系人仍需要一次性配对码或允许列表授权。两者都为空时默认拒绝访问。 - -发布版桌面端会随本地服务启动 adapter sidecar,并在保存、绑定或解绑后应用新配置。详细平台差异见[IM 接入总览](../im/index.md)。 - -## H5 访问 - -H5 把当前桌面服务的浏览器界面开放到可信局域网或自己的反向代理。 - -- H5 页面、REST API 与 WebSocket 可以由同一个服务和端口提供。 -- 设置页会生成带 Token 的二维码与连接地址。 -- 可配置固定端口和手机断连后的空闲保活时间。 -- 手机锁屏或切后台时,正在运行的任务不会因为短暂断连被直接停止;空闲且无人连接后才按保活时间清理。 - -H5 默认关闭,也不是公开 SaaS。详情见[H5 访问](./06-h5-access.md)。 - -## Computer Use - -Computer Use 在获得系统与会话权限后,可以截图、点击和输入。设置页会显示平台、运行环境、依赖和权限状态。 - -操作系统权限、桌面会话状态和应用可访问性都会影响结果。安装包构建成功不等于当前机器已经完成真实桌面控制验证。使用前请阅读[Computer Use 指南](../features/computer-use.md)。 - -## 本地索引与诊断 - -桌面端使用 SQLite 派生索引加速会话列表、全局搜索、活动统计、定时任务记录和 Trace 读取。 - -- 原始 JSON / JSONL 会话与设置仍是事实源。 -- 索引构建时,界面可以等待或回退到文件读取。 -- 索引损坏或部分来源降级时,核心数据不会因为重建索引而删除。 -- 「设置 → 诊断」会显示索引状态、数据库/WAL 大小、降级来源和错误代码。 - -诊断页还可以: - -- 查看最近事件和事件 ID。 -- 复制错误摘要或经过尽力脱敏的 Issue 报告。 -- 导出诊断包、打开日志目录或清理日志。 -- 运行 Doctor 检查用户与当前项目状态。 -- 只在明确确认后重置可再生的桌面 UI 状态。 - -Doctor 和安全 UI 重置不会自动修改聊天历史、模型配置、Skills、MCP、IM 或 OAuth。分享任何诊断材料前仍应人工检查隐私内容。 - -## 更新与平台边界 - -正式安装包通过 GitHub Releases 检查更新。更新前应保存未提交工作并完全退出旧版本。 - -- macOS 正式 Release 使用签名和公证产物。 -- Windows 未签名安装包可能显示 SmartScreen;应以当前用户运行,不要主动提升为管理员。 -- Windows 覆盖升级包含旧数据识别与恢复保护,但遇到进程占用、来源歧义或复制失败时会停止,而不是猜测迁移。 -- Linux 提供 AppImage 与 deb,桌面环境、FUSE 和系统库差异仍可能影响运行。 - -安装与升级步骤见[安装指南](./04-installation.md),异常排查见[常见问题](./05-FAQ.md)。 diff --git a/docs/desktop/04-installation.md b/docs/desktop/04-installation.md deleted file mode 100644 index 6d8260d1..00000000 --- a/docs/desktop/04-installation.md +++ /dev/null @@ -1,169 +0,0 @@ -# 安装、升级与 Web UI - -Claude Code Haha Desktop 提供 macOS、Windows 和 Linux 安装包。普通用户应优先使用 GitHub Release,而不是从源码启动。 - -## 下载正确的安装包 - -前往 [GitHub Releases](https://github.com/NanmiCoder/cc-haha/releases) 下载最新正式版本: - -| 平台 | 安装文件 | -|------|----------| -| macOS Apple Silicon(M 系列) | `Claude-Code-Haha-<版本>-mac-arm64.dmg` | -| macOS Intel | `Claude-Code-Haha-<版本>-mac-x64.dmg` | -| Windows x64 | `Claude-Code-Haha-<版本>-win-x64.exe` | -| Windows ARM64 | `Claude-Code-Haha-<版本>-win-arm64.exe` | -| Linux x64 | `Claude-Code-Haha-<版本>-linux-x86_64.AppImage` 或 `...-linux-amd64.deb` | -| Linux ARM64 | `Claude-Code-Haha-<版本>-linux-arm64.AppImage` 或 `...-linux-arm64.deb` | - -macOS 可从「关于本机」查看芯片;Windows 可在「设置 → 系统 → 系统信息」查看系统类型。不要仅凭设备品牌猜测架构。 - -## macOS - -1. 打开 DMG。 -2. 把 Claude Code Haha 拖到「应用程序」。 -3. 从「应用程序」打开。 - -正式 macOS Release 使用 Developer ID 签名和公证,通常只会显示标准的下载来源确认。 - -只有旧版或明确标注为 unsigned 的临时构建才可能需要手动放行。确认安装包确实来自本仓库后,可以在「系统设置 → 隐私与安全性」选择仍要打开。不要对来源不明的应用运行解除隔离命令。 - -## Windows - -1. 完全退出正在运行的旧版 Claude Code Haha。 -2. 直接双击 `.exe`,以当前用户安装。 -3. 不要主动选择「以管理员身份运行」。 - -未签名构建可能触发 SmartScreen。确认文件来自本仓库 Release 后,可选择「更多信息 → 仍要运行」。 - -覆盖升级时,安装器会检查相关进程和旧安装目录中的用户数据。它会在可以确认来源与用户身份时恢复历史数据;遇到进程占用、来源歧义或复制失败时会停止安装,而不是猜测迁移。 - -如果安装器提示程序仍在运行: - -1. 退出主窗口和托盘中的应用。 -2. 等待几秒,让 sidecar 和 adapter 退出。 -3. 仍失败时,在任务管理器中检查 Claude Code Haha 相关进程。 -4. 重新运行安装器,不要先删除旧数据目录。 - -若你曾把重要资料放进安装目录,升级前仍建议单独备份。安装器的恢复保护不是通用备份方案。 - -## Linux - -### AppImage - -```bash -chmod +x Claude-Code-Haha-<版本>-linux-x86_64.AppImage -./Claude-Code-Haha-<版本>-linux-x86_64.AppImage -``` - -ARM64 机器请换用 `linux-arm64.AppImage`。 - -Ubuntu 22.04 及更早版本缺少 FUSE 时可安装 `libfuse2`;Ubuntu 24.04 及更新版本通常使用 `libfuse2t64`。 - -### deb - -```bash -sudo apt install ./Claude-Code-Haha-<版本>-linux-amd64.deb -``` - -ARM64 机器请换用对应的 `linux-arm64.deb`。 - -## 首次启动检查 - -安装完成后,按这个顺序验证: - -1. 打开「设置 → 服务商」,完成官方登录或 API Provider 配置。 -2. 新建会话并选择一个可读写的测试项目。 -3. 使用「询问权限」发送一个只读任务。 -4. 确认回复、工具调用和停止按钮正常。 -5. 再测试文件修改、Diff 和权限拒绝。 - -不要把 H5、Auto、跳过权限、Computer Use 和 IM 一次全部打开。逐项启用更容易判断故障位于模型、桌面服务还是外部入口。 - -## 应用内更新 - -正式安装包会通过 GitHub Releases 检查更新,并下载当前平台的更新资产。 - -更新前: - -- 保存项目中的未提交工作。 -- 等待或停止正在运行的会话、定时任务和 SubAgent。 -- 完全退出应用后再安装。 - -覆盖安装和应用内更新不应删除正常用户目录中的会话、Provider、Skills、Agents、记忆和自定义宠物。若升级后列表暂时为空,先不要删除配置;打开「设置 → 诊断」检查本地索引状态,必要时只重建派生索引。 - -Windows 与 Linux 的真实安装行为依赖对应 Release 产物;其他平台上的本地构建通过不等于当前平台已经完成安装验证。 - -## 从源码运行同源 Web UI - -服务端可以直接提供构建后的 Web UI。这样页面、REST API 和 WebSocket 使用同一个 `3456` 端口,最接近 H5 与发布版的同源路径。 - -```bash -# 项目根目录 -bun install - -# 构建 Web UI -cd desktop -bun install -bun run build -cd .. - -# 只允许本机访问 -SERVER_HOST=127.0.0.1 SERVER_PORT=3456 bun run src/server/index.ts -``` - -打开: - -```text -http://127.0.0.1:3456/ -``` - -本机回环访问不需要开启 H5,也不需要 H5 Token。这个豁免只适用于真正的本地访问,不会自动扩展到局域网地址或反向代理。 - -如果服务不是从项目根目录启动,设置 `CLAUDE_H5_DIST_DIR` 指向 `desktop/dist` 的绝对路径。缺少构建产物时 API 仍可能启动,但浏览器根路径会返回 404。 - -## 前端热更新开发模式 - -需要修改 React 页面时,可以把前端开发服务器和后端分开运行: - -```bash -# 终端 1:项目根目录 -SERVER_HOST=127.0.0.1 SERVER_PORT=3456 bun run src/server/index.ts - -# 终端 2 -cd desktop -bun run dev --host 127.0.0.1 --port 2024 -``` - -打开: - -```text -http://127.0.0.1:2024/?serverUrl=http%3A%2F%2F127.0.0.1%3A3456 -``` - -这条双端口路径只用于开发热更新。手机访问、反向代理和部署排查应优先使用构建后的 `3456` 同源路径,减少 CORS 与 WebSocket 配置变量。 - -## 无桌面环境的 Linux 主机 - -最安全的个人访问方式是 SSH 端口转发: - -```bash -# 服务器:按上一节构建后,只监听回环地址 -SERVER_HOST=127.0.0.1 SERVER_PORT=3456 bun run src/server/index.ts - -# 自己的电脑 -ssh -L 3456:127.0.0.1:3456 user@example.com -``` - -随后在自己的电脑打开 `http://127.0.0.1:3456/`。因为服务没有暴露到局域网,这条路径不需要 H5 Token。 - -需要让手机或反向代理直接访问时,必须显式启用 H5、生成 Token,并理解公网暴露边界。参见[H5 访问](./06-h5-access.md)。 - -## 安装后仍无法启动 - -按以下顺序排查: - -1. 确认下载的平台和 CPU 架构正确。 -2. 确认旧版本进程已经退出。 -3. 重新启动应用,而不是反复覆盖安装。 -4. 打开「设置 → 诊断」复制错误摘要或导出诊断包。 -5. 查看[常见问题](./05-FAQ.md),再携带版本、系统、复现步骤和脱敏诊断信息求助。 diff --git a/docs/desktop/05-FAQ.md b/docs/desktop/05-FAQ.md deleted file mode 100644 index 77d719b4..00000000 --- a/docs/desktop/05-FAQ.md +++ /dev/null @@ -1,270 +0,0 @@ -# Desktop 常见问题 - -先确认你使用的是 [GitHub Releases](https://github.com/NanmiCoder/cc-haha/releases) 中的最新正式版本。下面的排查顺序尽量避免直接修改 `~/.claude`、删除会话或重装应用。 - -## 服务商与模型 - -### 应该选择官方登录还是 API Provider? - -- 已有 Claude.ai、ChatGPT 或 Grok 账号,并希望使用官方账号授权:选择对应的官方登录。 -- 已有 API Key、企业网关、本地模型或第三方兼容服务:选择内置预设或 Custom。 -- 服务只支持 OpenAI 协议:在桌面端选择 OpenAI Chat Completions 或 OpenAI Responses,不必先假设必须部署 LiteLLM。 -- 从源码使用 CLI 环境变量:参见[第三方模型指南](../guide/third-party-models.md),不要把 CLI 配置步骤和桌面 Provider 设置混在一起。 - -### Provider 返回 401 或 “API Key invalid” 怎么办? - -不要先编辑原始 `settings.json`。在「设置 → 服务商」依次检查: - -1. Base URL 是否是 API 根地址,而不是网页地址。 -2. API 格式是否与服务商一致。 -3. 鉴权方式是否正确: - - `x-api-key` 常用于 Anthropic 官方 API。 - - `Authorization: Bearer` 常用于第三方兼容服务。 - - OpenRouter / Ollama 等场景可能需要避免回退到 Anthropic API Key。 -4. API Key 前后是否包含空格或已失效。 -5. 当前模型 ID 是否确实存在。 -6. 保存后重新测试连接,并新建一条测试会话。 - -Kimi Code 当前预设使用 K3 Coding API 和 API Key 鉴权。旧文档中手动把某个环境变量替换为另一个变量的修复方法,针对的是历史版本,不应作为当前首选步骤。 - -### 登录成功,为什么看不到文档里提到的模型? - -模型目录受账号权限、地区、套餐和服务商动态配置影响。桌面端能识别 GPT、Grok 以及 Fable、Opus、Sonnet、Haiku 等模型,不代表每个账号都能调用全部条目。 - -先刷新服务商状态,再重新打开模型选择器。仍缺失时,请以账号官方控制台或 Provider 返回结果为准。 - -### 为什么 effort 被降低或没有效果? - -`low`、`medium`、`high`、`xhigh`、`max` 不是所有模型都支持。运行时会按模型能力规范化;某些组合会向下调整或忽略,required/adaptive thinking 也可能覆盖通用选择。 - -逐 Agent effort 同样遵守这个边界。它不是固定的 thinking token 预算。 - -## 会话、任务与权限 - -### 对话一直显示运行中,应该直接重启吗? - -先按顺序尝试: - -1. 等待短暂网络波动恢复。 -2. 点击停止,或按 `Ctrl/Cmd + .`。 -3. 查看活动面板中 Task、后台任务和 SubAgent 的实际状态。 -4. 切换到其他会话再返回,排除单次界面刷新问题。 -5. 完全退出并重新打开应用。 -6. 在「设置 → 诊断」复制错误摘要。 - -当前版本会持久化完成、失败和停止等终态,但永不完成的网络握手或持续异常的 sidecar 仍可能需要人工重启。不要只根据一个加载动画判断服务端状态。 - -### 为什么运行中不能切换权限模式? - -活跃轮次期间切换会造成界面选择与实际工具权限不一致,因此选择器会锁定。先等待当前轮结束或停止,再切换模式。 - -### Auto 与“跳过全部”有什么区别? - -Auto 会让 Claude 审查工具调用,并执行其认为安全的操作;它会减少审批,但不能保证绝对安全。 - -“跳过全部”不会进行这种逐项权限检查,风险更高。二者都不适合包含生产密钥、个人资料或不可恢复数据的陌生目录。 - -### 拒绝 Write/Edit 后为什么要检查 Git Diff? - -当前版本会尽量避免把未落盘的拒绝操作显示成变更,但磁盘和 Git 才是最终事实源。遇到中断、外部编辑器并发修改或工具异常时,交付前仍应检查真实 Diff。 - -## 搜索、工作区与附件 - -### 会话搜索、`Ctrl/Cmd + F` 和文件搜索有什么区别? - -| 入口 | 搜索范围 | -|------|----------| -| 侧边栏搜索 | 历史会话 | -| `Ctrl/Cmd + F` | 当前页面或当前长对话 | -| `@` 菜单 | 引用工作区文件 | -| 右侧工作区搜索 | 整个工作目录,包括未展开的目录 | - -正式桌面安装包已内置 ripgrep。源码开发环境的依赖问题不等于正式安装包也缺少文件搜索。 - -### 历史会话突然不在列表里了,数据丢了吗? - -不一定。本地 SQLite 只是一份可重建的派生索引,原始会话仍以 JSON / JSONL 文件为准。 - -1. 打开「设置 → 诊断」。 -2. 查看本地索引是否正在构建或处于降级状态。 -3. 先等待后台构建完成。 -4. 必要时点击「重建本地索引」。 - -重建索引不会删除原始对话和设置。不要为了恢复列表先删除 `~/.claude`。 - -### 附件恢复后只看到路径,或无法打开怎么办? - -当前版本会恢复已经提交的文件、目录、图片和 PDF 上下文。若打开失败: - -- 检查原文件是否移动、重命名或删除。 -- 检查当前系统是否有可打开该格式的应用。 -- Windows 路径与另一个系统上的路径不能自动互换。 -- 使用「打开方式」选择合适的本地应用。 - -历史消息存在不代表原始文件仍在磁盘。 - -### Diff 中如何评论连续多行? - -先点击起始行,再按住 `Shift` 选择同一侧的结束行。评论会带着文件路径、行号和引用回到聊天输入框。 - -不要跨旧文件侧和新文件侧连续选择;折叠或切换文件后需要重新确认当前选区。 - -## 安装与更新 - -### Windows 安装器说应用仍在运行 - -1. 退出主窗口。 -2. 检查系统托盘并退出应用。 -3. 等待 sidecar、终端和 IM adapter 结束。 -4. 必要时在任务管理器中结束相关进程。 -5. 重新双击安装器,以当前用户运行。 - -不要主动选择「以管理员身份运行」,也不要先删除旧安装目录中的数据。 - -### Windows 显示 SmartScreen - -Windows 签名不是当前 Release 的强制发布条件。确认安装包来自本仓库官方 Release 后,未签名版本可选择「更多信息 → 仍要运行」。 - -来源不明、文件名或校验异常时不要绕过 SmartScreen。 - -### 覆盖升级会删除 Provider、会话或 Skills 吗? - -正常用户目录中的 Provider、会话、Skills、Agents、记忆和自定义宠物应被保留。Windows 安装器也会保护旧安装目录中可确认归属的历史数据。 - -但安装器恢复不是备份。遇到来源歧义、进程占用或复制失败时,它会中止;重要数据仍应自行备份。 - -### 更新后会话列表为空 - -先检查本地索引,不要立即重装: - -1. 打开「设置 → 诊断」。 -2. 等待索引构建。 -3. 查看降级来源与错误代码。 -4. 重建派生索引。 -5. 若仍失败,导出诊断包后求助。 - -## H5 与 IM - -### 手机扫码后打不开 H5 - -检查: - -1. 手机和电脑是否在同一可信网络,或反向代理是否可达。 -2. H5 页面显示的“访问主机 / IP”是否仍属于电脑当前网卡。 -3. 二维码中的端口是否等于“当前端口”。 -4. 修改固定端口后是否重启了应用。 -5. 防火墙是否允许当前端口。 -6. Token 是否被重新生成;重新生成后旧二维码立即失效。 - -源码同源部署默认可使用 `http://<主机>:3456/`。详细步骤见[H5 访问](./06-h5-access.md)。 - -### 手机锁屏会停止正在运行的任务吗? - -短暂断连不会直接停止正在执行的任务;它会在后台完成,重连后可以看到结果。只有任务已经空闲且无人连接时,才会按“断连保活”设置停止对应 CLI,默认值为 30 秒。 - -这不等于移动网络下永远不会断开。系统休眠、进程退出、代理故障或服务重启仍会终止连接。 - -### H5 可以直接暴露到公网吗? - -不建议裸露公网。H5 不是公开 SaaS,也没有多租户账号体系。拿到有效 Token 的设备可以访问桌面服务暴露的核心能力。 - -需要远程访问时,应使用自己的 HTTPS 反向代理或 VPN,保留正确的 Host / 代理头,并保护 Token。 - -### IM 已扫码,为什么联系人仍不能聊天? - -扫码只绑定微信、钉钉或 WhatsApp 的平台账号。实际 IM 用户仍需: - -- 发送桌面端生成的一次性配对码;或 -- 被加入允许列表。 - -两者都为空时默认拒绝访问。五个平台的差异见[IM 接入总览](../im/index.md)。 - -## Agent、技能与宠物 - -桌面宠物的完整开启与导入步骤见[桌面宠物指南](./pets.md)。 - -### Agent 已保存,但当前会话没有使用新配置 - -保存 Agent 文件和刷新运行时是两个步骤。刷新失败时文件仍可能已经保存,桌面端会给出非阻塞警告。 - -先打开 Agent 详情确认生效来源和覆盖关系,再新建会话或重启应用。项目 Agent 需要当前会话绑定正确的项目目录。 - -### 为什么内置或插件 Agent 不能编辑? - -桌面端只负责用户级和项目级 Agent 文件。内置、插件、策略与 CLI 参数来源保持只读,避免误改不属于桌面端的事实源。 - -### 技能市场的技能都安全吗? - -不是。市场会展示来源、安装状态和安全提示,但第三方技能仍可能包含脚本、依赖或外部访问。安装前应阅读内容并确认来源。 - -### 单张图片能生成完整宠物动画吗? - -不能。透明 PNG / WebP 单图只会在本地添加呼吸、漂浮和状态等轻动画。完整逐帧动作需要一张 8 列 × 9 行的动作表——可以用「用 AI 画一只会跑会跳的」路径,按弹窗里的提示词让任意画图 AI 生成,再选进来;导入时会自动切格对齐。 - -宠物只运行在 Electron 桌面端,不显示在 H5。 - -### 已经打开任务面板,为什么看不到任务? - -任务面板只列出正在运行、等待处理或失败等非空闲会话。所有任务都已完成或处于空闲状态时,面板会自动隐藏;「显示进行中的任务区域」不会让历史任务一直常驻。 - -先确认「设置 → 宠物 → 显示进行中的任务区域」已开启,再启动一条真实任务。状态会定时刷新;等待几秒仍未出现时,关闭并重新显示宠物。 - -### 宠物为什么不动? - -先确认「设置 → 宠物 → 播放动画」已开启。系统启用“减少动态效果”时,应用也会尊重该设置并降低或关闭动画。 - -单图自定义宠物只有呼吸、漂浮和任务状态等轻动画;它不会像 v2 图集宠物一样播放完整逐帧动作。鼠标悬停或单击宠物可以触发跳跃、挥手等互动。 - -### 为什么自定义宠物导入失败? - -逐项检查: - -- 单图必须是 PNG 或 WebP,单边为 32–4096 像素,文件不超过 8 MB,总像素不超过 16,777,216。 -- 动作表必须是 8 列 × 9 行的布局;尺寸不必精确,导入时会自动切格缩放。已经是 1536×2288 的成品图集会原样保留。 -- 动作表必须有透明背景。白底或彩底会被拒绝,因为宠物会在桌面上显示成一个方块。 -- 宠物 ID 最多 73 个字符,只能包含小写字母、数字和单个连字符,且不能与已有 ID 重复。 -- 显示名称与描述不能为空。 - -导入只读取你在系统文件选择器中确认的本地文件。失败后不要直接编辑清单文件,先按提示修正源图片,再重新创建。 - -### 关闭宠物后如何恢复? - -右键宠物并选择关闭,或关闭「设置 → 宠物 → 显示桌面宠物」,都会保存关闭状态。需要恢复时,重新打开这个开关即可;不需要重装应用。 - -如果宠物曾被拖到另一块屏幕,关闭并重新显示时,保存的位置会被限制在当前可见工作区。 - -### 如何删除自定义宠物? - -当前版本没有“删除”按钮。先切换到一个内置宠物,再点击「设置 → 宠物 → 打开文件夹」,删除目标自定义宠物对应的文件夹,最后返回设置点击「刷新」。 - -只删除 `${CLAUDE_CONFIG_DIR:-~/.claude}/cc-haha/pets` 下确认属于该宠物的目录,不要删除整个 `~/.claude`。内置宠物随应用提供,不在这里删除。 - -### 「用 AI 画一只会跑会跳的」会消耗我的对话额度吗? - -不会。这条路径不调用当前会话选择的聊天模型,应用也不会替你请求任何图片生成服务。它给你的是一段提示词和一张对照模板,图由你自己在任意画图工具里生成,再把文件选进来;整理和导入都在本机完成。 - -### 能在宠物窗口里批准权限或直接回复吗? - -不能。宠物窗口用于展示任务状态、切回主窗口和定位对应会话,不提供消息输入框,也不会批准文件、命令或 Computer Use 权限。 - -看到“等待你处理”时,点击对应任务返回主窗口,再在完整会话界面中阅读上下文、批准或拒绝权限并继续回复。宠物不会绕过当前权限模式。 - -## 诊断与求助 - -### 应该提供哪些信息? - -在「设置 → 诊断」中: - -1. 刷新并记录最近事件 ID。 -2. 复制错误摘要或 Issue 报告。 -3. 需要时导出诊断包。 -4. 补充应用版本、操作系统、CPU 架构、服务商类型和最短复现步骤。 -5. 明确问题是安装、界面、模型请求、H5、IM 还是某个 Agent 工作流。 - -诊断报告会尽力省略聊天、文件正文、完整环境变量和 API Key,但分享前仍需人工检查是否含私密路径、账号信息或其他元数据。 - -### Doctor 会修改什么? - -Doctor 默认检查受保护的用户与项目状态。只有你明确确认“重置安全 UI 状态”时,它才会移除允许重新生成的桌面界面键。 - -聊天历史、模型配置、Skills、MCP、IM 和 OAuth 不属于自动修复范围。不要根据旧教程手动删除这些目录。 diff --git a/docs/desktop/06-h5-access.md b/docs/desktop/06-h5-access.md deleted file mode 100644 index 75617e99..00000000 --- a/docs/desktop/06-h5-access.md +++ /dev/null @@ -1,225 +0,0 @@ -# H5 访问 - -H5 是桌面服务的可选浏览器入口。启用后,手机可以通过局域网或你自己的反向代理访问同一套会话、消息、附件和权限流程。 - -它不是公开 SaaS,也不是多租户账号系统。任何拿到有效 H5 Token 的设备都可以访问桌面服务暴露的核心能力,只应在可信网络、VPN 或自己维护的 HTTPS 代理中使用。 - -## 推荐结构:一个地址、一个端口 - -当前服务端可以同时提供: - -- H5 静态页面 -- REST API -- Provider 代理 -- WebSocket / SDK 连接 - -因此推荐让浏览器只访问一个来源: - -```text -http://192.168.1.20:3456/ -``` - -同源路径不需要再维护独立的前端地址,也能减少 Token 和 WebSocket 代理配置错误。源码运行时默认服务端口是 `3456`;发布版应以设置页显示的“当前端口”为准。 - -安全策略不会因为前后端同源就自动信任一个远程浏览器。该来源仍需出现在 H5 允许来源中;桌面端正常开启流程会沿用已保存的 H5 配置,源码或手工部署应按下文显式填写 `allowedOrigins`。 - -## 桌面端开启步骤 - -1. 打开「设置 → H5 访问」。 -2. 打开「启用 H5 访问」,阅读风险提示后确认。 -3. 查看自动生成的 H5 链接和“当前端口”。 -4. 如果保存的局域网 IP 已失效,使用设置页建议的新 IP,或手动填写电脑当前的局域网 IP。 -5. 需要稳定书签或反向代理时,在“固定端口”填写 `3456` 或其他浏览器允许的端口并保存。 -6. 修改固定端口后重启桌面端,确认“当前端口”已经变成新值。 -7. 用手机扫描二维码,或复制“扫码链接”到可信设备。 - -二维码链接会携带 Server URL 和 H5 Token。浏览器连接成功后会在本机保存连接信息,后续打开可继续使用。 - -### 访问主机 / IP - -普通局域网访问只填写电脑真实的私有 IP,例如: - -```text -192.168.1.20 -``` - -设置页会结合当前服务端口生成完整地址。如果电脑切换 Wi-Fi、网线或 VPN,原 IP 可能不再属于当前网卡;桌面端会提示切换到可用地址。 - -使用反向代理时可以直接填写完整 URL: - -```text -https://cc.example.com -``` - -桌面端无法替你验证外部域名、隧道或证书是否持续可用。 - -### 固定端口 - -不设置固定端口时,桌面端会使用当前运行端口,并尽量复用已有端口。下列场景建议固定: - -- 手机书签需要长期不变。 -- 防火墙只允许指定端口。 -- Nginx、Caddy 或隧道固定转发一个上游端口。 - -端口必须是 `1024–65535` 之间且浏览器允许访问的整数。修改后要重启应用才会生效。 - -### 断连保活 - -手机锁屏、切后台或短暂换网时: - -- 正在执行的任务不会因为 H5 断连被直接停止。 -- 任务可以在后台完成,重连后查看结果。 -- 只有任务已经空闲且无人连接时,才会在保活时间到期后停止对应 CLI。 - -默认空闲保活时间为 30 秒,可配置范围是 5 秒到 24 小时。调大数值会让无人观察的空闲进程保留更久,不应把它当作云端永久任务。 - -## Token 生命周期 - -- Token 会保存在本机设置中,重启桌面端后仍可查看和生成二维码。 -- **关闭 H5** 会立即拒绝远程访问,但会保留 Token,方便以后重新启用已配对设备。 -- **重新生成 Token** 会轮换凭证,旧二维码和旧 Token 立即失效。 -- 不要把带 Token 的扫码链接贴到群聊、公开 Issue、日志截图或公共网页。 - -怀疑链接泄露时,应重新生成 Token,而不只是关闭再打开 H5。 - -## 手机端可用范围 - -H5 优先保证对话主流程: - -- 会话列表与项目切换。 -- 消息发送、停止和流式回复。 -- 图片和文件附件。 -- 权限按钮与 AI 提问。 -- 消息复制、分叉等操作。 -- `@` 文件引用与适合触屏的输入布局。 -- 手机安全区、软键盘和横竖屏下的基础布局。 - -桌面工作区、底部终端、原生文件“打开方式”、Computer Use 系统授权和桌面宠物不属于 H5 的完整等价体验。 - -## 从源码运行同源 H5 - -先构建 Web UI,再让服务端在 `3456` 同时提供页面与 API: - -```bash -# 项目根目录 -bun install -cd desktop -bun install -bun run build -cd .. - -SERVER_HOST=0.0.0.0 \ -SERVER_PORT=3456 \ -CLAUDE_H5_AUTO_PUBLIC_URL=1 \ -bun run src/server/index.ts -``` - -如果不是从项目根目录启动,设置: - -```bash -CLAUDE_H5_DIST_DIR=/absolute/path/to/claude-code-haha/desktop/dist -``` - -没有 `desktop/dist` 时,控制 API 可以启动,但浏览器根路径会返回 404。 - -### 在服务器本机启用 - -H5 控制接口只接受本机可信请求。另开一个服务器终端: - -```bash -curl -sS -X POST http://127.0.0.1:3456/api/h5-access/enable -``` - -响应中的 `token` 是完整 Token。为局域网地址设置同源入口和固定端口: - -```bash -curl -sS -X PUT http://127.0.0.1:3456/api/h5-access \ - -H 'Content-Type: application/json' \ - --data '{ - "allowedOrigins": ["http://192.168.1.20:3456"], - "publicBaseUrl": "http://192.168.1.20:3456", - "fixedPort": 3456, - "disconnectGraceSeconds": 30 - }' -``` - -把示例 IP 替换成服务器真实的局域网 IP。不要填写另一台机器或已经失效的网卡地址。 - -随后打开: - -```text -http://192.168.1.20:3456/ -``` - -更安全的个人方案是保持 `SERVER_HOST=127.0.0.1`,通过 SSH、VPN 或受控隧道访问;单纯 SSH 转发到本机时不需要启用 H5。 - -## 反向代理 - -推荐用一个 HTTPS 域名把静态页面和所有后端路径转到同一个上游: - -```text -https://cc.example.com -> http://127.0.0.1:3456 -``` - -至少确保下列路径进入同一个桌面服务: - -- `/` -- `/api/*` -- `/proxy/*` -- `/ws/*` - -WebSocket 路径必须支持协议升级。 - -### Nginx 必要请求头 - -反向代理连接本机服务时,后端看到的来源地址通常是 `127.0.0.1`。必须保留公开 Host,或传递标准代理头,防止远程请求被误判为本地直连: - -```nginx -proxy_set_header Host $host; -proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; -proxy_set_header X-Forwarded-Proto $scheme; -proxy_set_header Upgrade $http_upgrade; -proxy_set_header Connection "upgrade"; -``` - -Caddy 默认会保留原始 Host 并补充 `X-Forwarded-*`。如果自定义 `header_up`,不要同时把 Host 改成 `127.0.0.1` 并删除全部代理头。 - -反向代理域名也要加入 H5 允许来源。可从服务器本机调用控制接口: - -```bash -curl -sS -X PUT http://127.0.0.1:3456/api/h5-access \ - -H 'Content-Type: application/json' \ - --data '{ - "allowedOrigins": ["https://cc.example.com"], - "publicBaseUrl": "https://cc.example.com" - }' -``` - -如果必须把页面与 API 放在不同来源,需要把页面来源加入 `allowedOrigins`,并另外维护 Server URL。不要使用通配来源。除非现有基础设施强制要求,否则优先使用同源方案。 - -## 安全检查 - -- H5 默认关闭。 -- 远程 REST、Provider 代理与 WebSocket 都需要有效 Token。 -- 本机回环豁免不会扩展到真实局域网或带代理痕迹的请求。 -- 远程预览与桌面可信 session 分离,不会自动获得摄像头、麦克风或通知权限。 -- 不使用时关闭 H5。 -- 泄露时重新生成 Token。 -- 公网使用 HTTPS、VPN、访问控制与防火墙,不要只依赖一个长期 Token。 -- 不要把桌面服务直接暴露给不受信任的团队或互联网扫描。 - -## 排查 - -| 现象 | 检查 | -|------|------| -| 二维码打不开 | 核对访问主机是否属于当前网卡、手机与电脑是否同网 | -| 地址端口不一致 | 以“当前端口”为准;固定端口修改后重启 | -| 页面 404 | 构建 `desktop/dist`,或检查 `CLAUDE_H5_DIST_DIR` | -| Token 无效 | 确认 H5 已启用;重新生成后更新旧书签 | -| 浏览器提示 CORS | 把浏览器地址栏中的来源精确加入 `allowedOrigins`,不要使用 `*` | -| REST 可用但消息不流式 | 检查 `/ws/*` 的 WebSocket upgrade | -| 反代后被当成本机或被拒绝 | 保留公开 Host 和标准代理头 | -| 切换网络后旧 IP 失效 | 在设置页应用建议 IP 或更新反向代理地址 | -| 手机切后台后连接消失 | 重新打开页面;运行中任务应继续,空闲任务受保活时间限制 | - -仍无法定位时,到「设置 → 诊断」复制错误摘要与事件 ID。分享诊断材料前检查其中是否含私有域名、IP、用户名或路径。 diff --git a/docs/desktop/07-electron-migration-research.md b/docs/desktop/07-electron-migration-research.md deleted file mode 100644 index 6015db42..00000000 --- a/docs/desktop/07-electron-migration-research.md +++ /dev/null @@ -1,205 +0,0 @@ -# Tauri 迁移 Electron 调研索引 - -> 日期:2026-05-31 -> 范围:桌面端壳层从 Tauri 2 迁移到 Electron,React/Vite 渲染层尽量复用。 -> 状态:调研与迁移方案,不是最终实现说明。 - -## 结论 - -迁移方向可行,但不能按“把 Tauri 项目换成 Electron 模板”处理。当前桌面端把 Tauri 2 当作完整 host runtime 使用:它负责自动更新、系统通知、文件选择、外链打开、窗口/托盘/菜单、子进程 sidecar、PTY 终端、子 WebView 预览面板、应用模式配置、窗口状态持久化和权限白名单。React 页面主体可以复用,但必须先把 `desktop/src` 里的 `@tauri-apps/*` 直接调用收敛为一层 `desktopHost` adapter,然后再并行保留 Tauri 实现和新增 Electron 实现。 - -推荐目标架构是: - -```text -desktop/src React renderer - -> desktop/src/lib/desktopHost/* typed host adapter - -> Tauri implementation during transition - -> Electron preload contextBridge implementation - -> Electron main process services - - sidecar manager - - updater service - - notification service - - dialog/open service - - window/tray/menu service - - terminal/pty service - - preview WebContentsView service -``` - -这样可以让前端继续用同一套 Zustand store、API client、WebSocket 和组件测试,同时把高风险系统能力迁移压缩到 adapter contract 和 Electron main/preload 层。不要把本地 Bun server 合并进 Electron main,也不要把 renderer 从 HTTP/WebSocket 改成全 IPC;现有 `desktop/src/api/*`、`chatStore`、`workspacePanelStore`、`teamStore` 已经围绕 local server contract 做了重连、去重、流式 flush 和多客户端观察,这些成熟行为应作为迁移边界保留。 - -## 外部依据 - -本次调研以官方文档和 GitHub issue 为准,Twitter/X 搜索结果信噪比较低,未作为决策依据。 - -- Tauri README 明确 Tauri 通过 WRY 使用系统 WebView:macOS/iOS 是 WKWebView,Windows 是 WebView2,Linux 是 WebKitGTK;同时 Tauri 提供 bundler、自更新、托盘和原生通知等桌面能力。参考:[tauri-apps/tauri](https://github.com/tauri-apps/tauri)。 -- Tauri 2 updater 插件使用 endpoint/static JSON 和 capability permission;危险命令默认阻断,必须在 capabilities 里放行。参考:[Tauri Updater](https://v2.tauri.app/plugin/updater/)。 -- Tauri capability 是按 window/webview 授权的权限集合,权限名使用 `plugin:permission` 形式。参考:[Tauri Capability](https://v2.tauri.app/reference/acl/capability/)。 -- Electron 推荐使用 preload + `contextBridge`,不要把 `ipcRenderer` 原样暴露给 renderer;`contextIsolation` 从 Electron 12 起默认启用。参考:[Electron Context Isolation](https://www.electronjs.org/docs/latest/tutorial/context-isolation) 与 [Electron Security](https://www.electronjs.org/docs/latest/tutorial/security)。 -- Electron 内置 `autoUpdater` 不支持 Linux;如果要覆盖 macOS/Windows/Linux,更适合用 `electron-builder` + `electron-updater`。参考:[Electron autoUpdater](https://www.electronjs.org/docs/api/auto-updater/) 与 [electron-builder Auto Update](https://www.electron.build/docs/features/auto-update)。 -- Electron `BrowserView` 已弃用,应使用 `WebContentsView` 承载内嵌浏览器/预览面板。参考:[Electron BrowserView](https://www.electronjs.org/docs/latest/api/browser-view)。 -- Tauri 社区 issue 里也能看到系统 WebView 的真实世界差异,例如 macOS WebView 特定异常和 WebKit memory/performance 争议。参考:[tauri #13141](https://github.com/tauri-apps/tauri/issues/13141)、[tauri #5889](https://github.com/tauri-apps/tauri/issues/5889)。 - -## 本仓库现状 - -### 桌面三层 - -- React/Vite renderer:`desktop/src`。 -- Tauri host:`desktop/src-tauri`。 -- 本地 server/CLI sidecar:`desktop/sidecars/claude-sidecar.ts` 打包成 `desktop/src-tauri/binaries/claude-sidecar-*`,再由 Tauri host 启动。 - -迁移后仍应保留这个三层关系,只把中间 host 从 Tauri 改成 Electron: - -- React renderer 继续调用 `desktop/src/api/client.ts`、`sessionsApi`、`teamsApi`、WebSocket manager。 -- Electron main 负责启动和监管 local server sidecar。 -- renderer 通过 `getServerUrl` 得到 local server URL,而不是直接通过 IPC 读写会话数据。 - -关键入口: - -- `desktop/src-tauri/src/lib.rs` 注册 Tauri plugins、commands、tray/menu、sidecar、terminal、update 准备、通知 bridge、窗口生命周期。 -- `desktop/src-tauri/capabilities/default.json` 声明 Tauri 2 权限。 -- `desktop/src-tauri/tauri.conf.json` 声明窗口、CSP、updater、bundle、externalBin、icons。 -- `desktop/src/lib/desktopRuntime.ts` 解析 Tauri runtime 并获取 local server URL。 -- `desktop/src/stores/updateStore.ts` 管理自动更新状态机。 -- `desktop/src/lib/desktopNotifications.ts` 管理通知权限、发送、点击回跳和系统设置入口。 -- `desktop/src/api/terminal.ts` 通过 Tauri command + event 管理 PTY。 -- `desktop/src/lib/previewBridge.ts` 和 `desktop/src-tauri/src/webview_panel.rs` 管理内嵌预览 WebView。 - -### 当前性能背景 - -`SCROLL_PERF_INVESTIGATION.md` 的关键判断是:卡顿主要来自 macOS Tauri 使用 WKWebView,而 Electron 固定 Chromium/Blink。已合并的 `content-visibility:auto` 是 WebKit 侧缓解方案,但如果目标是接近 CodeX/官方 Claude Code 的一致滚动性能,Electron 是合理方向。迁移收益是可控渲染栈和跨平台一致性;成本是安装体积、内存、签名更新链路和安全边界全部要重建。 - -## Tauri 2 能力盘点与 Electron 对照 - -| 能力 | 当前 Tauri 位置 | Electron 目标 | 迁移风险 | -| --- | --- | --- | --- | -| 自动更新 | `tauri.conf.json` `plugins.updater`、`updateStore.ts`、`prepare_for_update_install`、`tauri.release-ci.json` | `electron-builder` + `electron-updater`,main process 暴露 `check/download/install/relaunch` IPC | 签名、feed metadata、Windows sidecar 文件锁、Linux update 支持不能照搬 | -| 通知 | `tauri-plugin-notification`、`desktopNotifications.ts`、`macos_notifications.m` | Electron `Notification` + main process click routing,必要时保留 macOS native bridge | macOS 权限状态和点击回跳语义必须回归测试 | -| 文件/目录选择 | `@tauri-apps/plugin-dialog.open/save` | `dialog.showOpenDialog/showSaveDialog` | 从 Tauri capability 变成自管 IPC 白名单,路径和 options 要验证 | -| 外链/系统打开 | `@tauri-apps/plugin-shell.open` | `shell.openExternal/openPath` | 必须限制 URL scheme,避免 renderer 任意打开危险目标 | -| sidecar | Tauri shell plugin + `start_server_sidecar` / `spawn_and_track_adapters_sidecar` | Electron main `child_process.spawn` + sidecar manager | 动态端口、启动日志、adapter 重启、Windows kill 逻辑必须等价 | -| PTY 终端 | Rust `portable-pty` commands + Tauri events | Electron main 继续用 Node/Rust sidecar/原生模块之一提供 PTY | 如果从 Rust 移到 Node,shell 解析和 Windows 行为会变 | -| 窗口/托盘/菜单 | `setup_system_tray`、macOS `MenuBuilder`、`RunEvent` | `BrowserWindow`、`Tray`、`Menu`、`app` lifecycle | 关闭隐藏到托盘、macOS reopen、窗口位置恢复必须逐平台验证 | -| 窗口控制/拖拽 | `getCurrentWindow().minimize/toggleMaximize/close/startDragging` | preload bridge 到 `BrowserWindow` actions | 自定义标题栏和拖拽区域需要真实窗口 smoke | -| 子 WebView 预览 | Tauri `WebviewBuilder.add_child` + `preview_*` commands | `WebContentsView`,renderer 只发 typed IPC | `BrowserView` 已弃用,URL 白名单和消息 JSON 校验必须保留 | -| 单实例 | `tauri_plugin_single_instance` | `app.requestSingleInstanceLock()` | 第二实例参数、显示主窗口、协议启动要补测试 | -| app mode / portable config | `get_app_mode`、`set_app_mode`、`detect_portable_dir`、`CLAUDE_CONFIG_DIR` | main process config service | 这是持久化形状变更,要跑 persistence upgrade gate | -| zoom | `set_app_zoom` | `webContents.setZoomFactor` | 范围 clamp 和设置页行为要保持 | -| 权限模型 | `capabilities/default.json` | 自定义 IPC capability registry | 这是安全边界,不能让 renderer 直接拿 Node/Electron API | - -## React 可复用边界 - -可复用: - -- `desktop/src/components/**` 中大部分聊天、设置、任务、workspace、markdown、browser UI。 -- `desktop/src/stores/**` 的业务状态机,前提是依赖 host 能力的 store 改成调用 adapter。 -- `desktop/src/api/**` 的 HTTP/WebSocket client。 -- `desktop/scripts/build-preview-agent.ts`、`desktop/scripts/build-sidecars.ts` 的 sidecar 编译逻辑。 -- `scripts/quality-gate/**` 的报告和 lane 框架。 - -必须抽象: - -- runtime 探测:`isTauriRuntime()` 需要替换为 `getDesktopHostKind()`,支持 `browser`、`tauri`、`electron`。 -- command 调用:`invoke('...')` 改为 typed host methods。 -- event 订阅:Tauri `listen(...)` 改为 typed `desktopHost.events.on(...)`。 -- shell/dialog/notification/update/window/preview/terminal:全部通过 adapter 暴露。 - -不应复用为最终形态: - -- `desktop/src-tauri/**` 的 Tauri 配置和 Rust host crate。 -- Tauri capability JSON。Electron 需要等价的 IPC allowlist,但不是同一格式。 -- `tauri-apps/tauri-action` 和 `tauri build` CI 步骤。 - -必须避免的重写: - -- 不重写 `desktop/src/api/websocket.ts` 的重连、退避、ping 和 pending queue。 -- 不重写 `chatStore` 的历史加载去重和 streaming flush。 -- 不重写 workspace 请求的 latest-request 防陈旧覆盖。 -- 不把 session/team/workspace API 从 HTTP/WS 改成 Electron IPC。 - -## 推荐迁移路径 - -### 1. 冻结基线 - -先在现有 Tauri 实现上跑并记录: - -```bash -bun run check:policy -bun run check:desktop -bun run check:server -bun run check:coverage -bun run quality:gate --mode baseline --allow-live --provider-model -``` - -目的不是证明迁移完成,而是固定“现有能力本来是什么状态”。 - -### 2. 新增 host adapter,但 Tauri 仍是唯一实现 - -新建建议路径: - -- `desktop/src/lib/desktopHost/types.ts` -- `desktop/src/lib/desktopHost/index.ts` -- `desktop/src/lib/desktopHost/tauriHost.ts` -- `desktop/src/lib/desktopHost/browserHost.ts` -- `desktop/src/lib/desktopHost/__tests__/contract.test.ts` - -先把 renderer 中直接 import `@tauri-apps/*` 的位置迁走。完成标准: - -```bash -rg "@tauri-apps|invoke\\(|getCurrentWindow" desktop/src -``` - -生产代码中除 `desktopHost/tauriHost.ts` 外不应再命中。 - -### 3. 增加 Electron 壳 - -建议新增: - -- `desktop/electron/main.ts` -- `desktop/electron/preload.ts` -- `desktop/electron/services/sidecarManager.ts` -- `desktop/electron/services/updater.ts` -- `desktop/electron/services/notifications.ts` -- `desktop/electron/services/dialogs.ts` -- `desktop/electron/services/windows.ts` -- `desktop/electron/services/preview.ts` -- `desktop/electron/ipc/capabilities.ts` - -renderer 通过 preload 暴露的 `window.desktopHost` 调用,不直接使用 Electron API。 - -### 4. 构建与发布改造 - -保留 Vite build、sidecar build、质量门禁骨架,替换 Tauri build: - -- `check:native` 改名或重定义为 Electron main/preload compile + package config check。 -- 新增 `check:electron`。 -- `release-desktop.yml` 从 `tauri-action` 迁到 `electron-builder` 的 macOS/Windows/Linux matrix。 -- `scripts/release.ts` 从更新 `tauri.conf.json` / `Cargo.toml` 改为更新 Electron package/build config。 - -### 5. 逐能力迁移和验收 - -每迁移一个能力都要满足三层证据: - -1. contract/unit test 通过; -2. Electron main/preload 实现测试通过; -3. 打包 app smoke 通过。 - -不要等到全部迁移完才第一次打开 Electron app。 - -## 安全设计要求 - -Electron 迁移最大的安全风险不是 Electron 本身,而是从 Tauri 声明式 capability 退化成“renderer 想要什么就 IPC 什么”。必须建立等价能力层: - -- renderer 不暴露 Node.js、`ipcRenderer`、`shell`、`dialog`、`child_process`。 -- preload 只暴露固定函数,不暴露通用 invoke/send。 -- IPC channel 必须枚举,payload 用 schema 校验。 -- shell open 只允许 `https:`, `http:`, `mailto:` 和明确需要的本地 path 打开场景。 -- child process 只允许 sidecar 和 PTY 服务,参数白名单要保留 Tauri capability 中的约束。 -- preview URL 继续只允许 `http/https`。 -- 更新安装前必须停止 sidecar,失败后恢复可重试状态。 - -## 文档索引 - -- 具体执行任务见:[Electron 迁移任务清单](./08-electron-migration-tasks.md)。 -- 当前桌面架构见:[桌面端架构设计](./02-architecture.md)。 -- 当前安装构建见:[安装与构建](./04-installation.md)。 -- 性能背景见本地文档:`SCROLL_PERF_INVESTIGATION.md`。 diff --git a/docs/desktop/08-electron-migration-tasks.md b/docs/desktop/08-electron-migration-tasks.md deleted file mode 100644 index c900884b..00000000 --- a/docs/desktop/08-electron-migration-tasks.md +++ /dev/null @@ -1,705 +0,0 @@ -# Electron 迁移任务清单 - -> **For agentic workers:** 按任务顺序执行。每个任务完成后先跑该任务列出的核对命令,再进入下一任务。不要跳过打包壳 smoke,也不要把浏览器/Vite smoke 当作 Electron 壳验收。 - -**目标:** 将桌面端 host runtime 从 Tauri 2 迁移到 Electron,同时保持 React 层、server/sidecar、自动更新、系统交互、通知、构建发布和 Computer Use 能力可用。 -**架构:** 先抽象 `desktopHost` contract,再并行实现 Tauri 与 Electron,最后切换构建发布链路。保留本地 Bun server + REST/WebSocket 边界,不把会话、聊天、workspace、team 业务协议改成 Electron IPC。 -**技术栈:** React 18、Vite、Zustand、Bun、Electron、electron-builder/electron-updater、现有 sidecar、现有质量门禁。 - ---- - -## 当前同步状态 - -**执行记录(2026-06-01):** - -- 已按本地 `main` 重新同步当前迁移工作区:当前分支 `feat/electron-migration-main-sync-2392` 已快进到本地 `main` 的最新提交后再恢复 Electron 迁移改动。 -- 已在当前工作区内解决同步冲突,重点文件包括 `desktop/package.json`、`desktop/scripts/build-macos-arm64.sh`、`desktop/src-tauri/resources/preview-agent.js`、`desktop/src/components/workspace/WorkspaceFileOpenWith.test.tsx`、`desktop/src/lib/previewEvents.ts` 和 `desktop/src/lib/previewEvents.test.ts`。 -- 冲突解决后已重建 preview agent,并运行 targeted tests 与 Electron package dir 构建;后续迭代均基于同步后的本地 `main`。 -- packaged Electron renderer 使用 `file://.../app.asar/dist/index.html`,真实模型 smoke 暴露出 H5 token mode 下 WebSocket 会因 `Origin: file://` 被拒绝;已在 server CORS/H5 access policy 中把 `file://` 纳入本地桌面可信来源,并补回归测试。 - ---- - -## Phase 0:迁移前基线 - -### 1. 冻结当前 Tauri 行为基线 - -**涉及文件:** - -- 只读:`desktop/src/**` -- 只读:`desktop/src-tauri/**` -- 只读:`scripts/quality-gate/**` - -**步骤:** - -- [x] 运行 `git status --short`,确认当前工作区是否有无关改动。 -- [x] 运行 `bun run check:policy`。 -- [x] 运行 `bun run check:desktop`。 -- [x] 运行 `bun run check:server`。 -- [x] 运行 `bun run check:coverage`。 -- [ ] 有 live provider 时运行 `bun run quality:gate --mode baseline --allow-live --provider-model `。 -- [x] 记录 quality report 路径和失败项;失败时先修现有基线,不进入迁移。 - -**执行记录(2026-05-31):** - -- `bun run check:policy` 通过。 -- `bun run check:desktop` 通过;首次缺少 desktop 依赖,已在 `desktop/` 执行 `bun install` 后复跑通过。 -- `bun run check:server` 通过。 -- `bun run check:coverage` 初次运行未通过;归因后在 `adapters/` 执行 `bun install` 修复 `grammy` 缺包环境问题,并补足 changed-line 覆盖率。 -- 后续 `bun run check:coverage` 复跑曾因本机 WeChat 占用随机端口 `24023` 导致 `src/server/__tests__/h5-access-auth.test.ts` 超时;已把该测试的随机端口选择改为先探测可绑定端口。 -- 最新 `bun run check:coverage` 通过;report 为 `artifacts/coverage/2026-05-31T13-15-40-522Z/coverage-report.md`,summary 为 `passed=5 failed=0`,changed-lines coverage 为 `90.67% (204/225)`。 - -**通过标准:** 现有 Tauri 主干的 desktop/server/coverage/baseline 状态可复现。 -**阻断条件:** 当前主干已有红灯且未归因。 - ---- - -## Phase 1:Host Adapter Contract - -### 2. 新增 host adapter 类型与 browser fallback - -**创建:** - -- `desktop/src/lib/desktopHost/types.ts` -- `desktop/src/lib/desktopHost/browserHost.ts` -- `desktop/src/lib/desktopHost/index.ts` -- `desktop/src/lib/desktopHost/contract.test.ts` - -**步骤:** - -- [x] 定义 `DesktopHost` 接口,至少覆盖 `runtime/server URL`、`updates`、`notifications`、`dialogs`、`shell`、`window`、`terminal`、`preview`、`appMode`。 -- [x] 在 `browserHost` 中保留 H5/browser fallback:没有桌面能力时返回明确错误或 no-op。 -- [x] 在 contract test 中固定每个方法的 payload 和错误语义。 -- [x] 运行 `cd desktop && bun run test -- src/lib/desktopHost/contract.test.ts --run`。 - -**执行记录(2026-05-31):** - -- 新增 `desktop/src/lib/desktopHost/types.ts`、`browserHost.ts`、`index.ts`、`contract.test.ts`。 -- 先运行 contract test 得到预期红灯(缺少 `browserHost` 模块),补实现后 `desktopHost/contract.test.ts` 6 tests 通过。 - -**通过标准:** host adapter 类型能表达当前所有 Tauri command/plugin 能力。 -**阻断条件:** 仍需要 renderer 直接 import Tauri API 才能表达某个能力。 - -### 3. 实现 Tauri host adapter - -**创建:** - -- `desktop/src/lib/desktopHost/tauriHost.ts` - -**修改:** - -- `desktop/src/lib/desktopRuntime.ts` -- `desktop/src/api/terminal.ts` -- `desktop/src/stores/updateStore.ts` -- `desktop/src/lib/desktopNotifications.ts` -- `desktop/src/lib/previewBridge.ts` -- `desktop/src/lib/previewEvents.ts` -- `desktop/src/lib/appZoom.ts` -- `desktop/src/stores/adapterStore.ts` -- `desktop/src/stores/settingsStore.ts` -- dialog/shell 调用组件:`DirectoryPicker.tsx`、`Sidebar.tsx`、`Settings.tsx`、`TerminalSettings.tsx`、`ComputerUseSettings.tsx`、`WorkspaceFileOpenWith.tsx`、聊天链接打开组件 - -**步骤:** - -- [x] 把 `@tauri-apps/api/core.invoke` 包进 `tauriHost.commands.invoke` 或具体方法。 -- [x] 把 Tauri `listen` 包进 `desktopHost.events`。 -- [x] 把 dialog/shell/window/update/notification 调用全部迁到 `tauriHost`。 -- [x] 运行 `rg "@tauri-apps|__TAURI_INTERNALS__|window\\.__TAURI__|from '@tauri" desktop/src`。 -- [x] 确认生产代码只在 `desktop/src/lib/desktopHost/tauriHost.ts` 命中。 -- [x] 运行 `bun run check:desktop`。 - -**通过标准:** renderer 业务文件不再知道 Tauri API。 -**阻断条件:** 任一 store/component 仍直接动态 import `@tauri-apps/*`。 - -**当前进度(2026-05-31):** - -- 新增 `desktop/src/lib/desktopHost/tauriHost.ts`,先迁入 `get_server_url` 运行时调用。 -- `desktop/src/lib/desktopRuntime.ts` 已改为通过 `desktopHost` 选择 Electron preload host、Tauri host 或 browser fallback;新增回归测试确保 `window.desktopHost` 优先于 H5/browser fallback。 -- 已迁移 `desktop/src/api/terminal.ts`、`desktop/src/lib/previewBridge.ts`、`desktop/src/lib/previewEvents.ts`、`desktop/src/lib/appZoom.ts`、`desktop/src/lib/desktopNotifications.ts`、`desktop/src/stores/updateStore.ts`、`desktop/src/stores/adapterStore.ts`、`desktop/src/stores/settingsStore.ts` 的 runtime/terminal/preview/zoom/notification/update/adapter restart/app mode 调用到 `desktopHost`。 -- `desktopHost` 现在覆盖 `app.getVersion`、`commands.invoke`、`events.listen`、`webview.onDragDropEvent`、`dialogs.open/save`、`shell.open`、`notifications.permission/request/send/onAction`、`window.minimize/toggleMaximize/close/startDragging/focus/requestAttention/isMaximized/onResized/onNativeMenuNavigate`、`updates.check/prepare/cancel/relaunch` 等 Tauri 能力。 -- 已运行 focused tests:`desktopRuntime.test.ts`、`desktopHost/contract.test.ts`、`terminal.test.ts`、`previewBridge.test.ts`、`previewEvents.test.ts`、`appZoom.test.ts`、`desktopNotifications.test.ts`、`updateStore.test.ts`、`adapterStore.test.ts`、`settingsStore.test.ts`、`AppShell.test.tsx`、`WindowControls.test.tsx`、`ComputerUseSettings.test.tsx` 全部通过。 -- 继续迁移了 dialog/shell/window/menu 组件层、file drop webview、official login/open-with 等打开路径:`DirectoryPicker`、`composerAttachments`、`TerminalSettings`、`ComputerUseSettings`、`Settings`、`Sidebar`、`TabBar`、`WindowControls`、`AppShell`、`WorkspaceFileOpenWith`、`AssistantMessage`、`AssistantOutputTargetCard`、`CurrentTurnChangeCard`。 -- 2026-05-31 扫描结果:业务 renderer 生产代码不再直接 import `@tauri-apps/*`;剩余 Tauri 命中为 `desktopHost` 自身、runtime detection 类型声明和测试 mock。生产路径排除 `desktopHost` 后扫描为空:`rg -n "@tauri-apps|__TAURI_INTERNALS__|window\\.__TAURI__|from '@tauri" desktop/src --glob '!**/*.test.ts' --glob '!**/*.test.tsx' --glob '!**/lib/desktopHost/**' --glob '!src-tauri/**'`。 -- `bun run check:desktop` 通过:desktop `tsc --noEmit`、Vitest 全量 146 test files / 1127 tests passed,production build 成功;现有 React `act(...)` warnings 仍为非阻断输出。 - ---- - -## Phase 2:Electron 最小壳 - -### 4. 新增 Electron main/preload 骨架 - -**创建:** - -- `desktop/electron/main.ts` -- `desktop/electron/preload.ts` -- `desktop/electron/ipc/channels.ts` -- `desktop/electron/ipc/capabilities.ts` -- `desktop/electron/tsconfig.json` - -**修改:** - -- `desktop/package.json` -- 根 `package.json` - -**步骤:** - -- [x] 新增 `electron:dev`、`electron:build`、`check:electron` 脚本。 -- [x] `BrowserWindow` 使用 preload,保持 `contextIsolation`,不启用 renderer Node。 -- [x] preload 暴露 `window.desktopHost`,不暴露通用 `ipcRenderer`。 -- [x] `capabilities.ts` 枚举允许的 IPC channel 和 payload validator。 -- [x] 运行 `bun run check:electron`。 - -**执行记录(2026-05-31):** - -- 新增 `desktop/electron/main.ts`、`preload.ts`、`ipc/channels.ts`、`ipc/capabilities.ts`、`ipc/capabilities.test.ts`、`electron/tsconfig.json`、`scripts/electron-dev.ts`。 -- `BrowserWindow` 配置为 `preload + contextIsolation: true + nodeIntegration: false + sandbox: true`;preload 只通过 `contextBridge.exposeInMainWorld('desktopHost', ...)` 暴露 typed host contract。 -- `check:electron` 通过:Electron IPC validator 测试 3/3 通过,`electron-dist/main.cjs` 与 `electron-dist/preload.cjs` 构建成功。 -- `electron:build` 通过:renderer `desktop/dist` 和 Electron CJS entrypoints 均构建成功。 -- 短时 Electron 启动 smoke 已尝试;当前机器首次运行 `electron` 会触发 Electron binary 下载,下载未在本轮完成,因此真实窗口启动验证保留到下一轮 runtime/server bridge smoke。 - -**通过标准:** Electron 壳能启动并加载 Vite/dev 或 `desktop/dist`。 -**阻断条件:** renderer 需要 Node API 或 Electron API 才能工作。 - -### 5. 实现 Electron runtime/server bridge - -**创建:** - -- `desktop/electron/services/sidecarManager.ts` -- `desktop/electron/services/serverRuntime.ts` - -**步骤:** - -- [x] 复刻 `start_server_sidecar`:保留动态端口、host、config、startup log、超时错误。 -- [x] 复刻 adapter sidecar 启动和 `restart_adapters_sidecar`。 -- [x] 保留 `CLAUDE_CONFIG_DIR`、`CC_HAHA_APP_PORTABLE_DIR`、`ADAPTER_SERVER_URL` 注入。 -- [x] 提供 `get_server_url` 等价 IPC。 -- [x] 确认 renderer 仍通过 `desktop/src/api/client.ts` 和 WebSocket manager 访问会话/聊天/workspace/team,不新增这些业务的 IPC handler。 -- [x] 运行 `bun run check:electron`。 -- [x] 打开 Electron dev app,确认首页能连上 local server。 - -**执行记录(2026-05-31):** - -- 新增 `desktop/electron/services/sidecarManager.ts` 和 `serverRuntime.ts`;Electron main 在 ready 阶段启动 server sidecar,`runtime.getServerUrl()` 通过 IPC 返回动态 loopback URL。 -- `sidecarManager` 复用现有 `desktop/src-tauri/binaries/claude-sidecar-`,server 参数保持 `server --app-root --host 0.0.0.0 --port `,adapter 参数保持 `adapters --app-root --feishu/--telegram/--wechat/--dingtalk`。 -- 环境变量保留 `CLAUDE_CONFIG_DIR`、`XDG_CACHE_HOME`、`CLAUDE_H5_AUTO_PUBLIC_URL`、`CLAUDE_H5_DIST_DIR`;adapter 注入 `ADAPTER_SERVER_URL=ws://`。 -- headless smoke 通过:`bun run build:sidecars` 后用 `ElectronServerRuntime` 启动 server,`GET /health` 返回 200,随后 `stopAll()` 停止 server/adapters 子进程。 -- 2026-06-01 Electron dev 壳已由 Computer Use 多轮复测:renderer 加载 `localhost:1420/`,main process 自动启动 local server/sidecar,首页、设置页和会话页均可通过 server API/WebSocket 工作。 - -**通过标准:** Electron app 不依赖外部手动启动 server。 -**阻断条件:** 需要用户先运行 `SERVER_PORT=... bun run src/server/index.ts` 才能使用桌面端,或迁移方案开始绕过 local server 直接重写 session/chat IPC。 - ---- - -## Phase 3:系统能力迁移 - -### 6. 迁移文件选择、保存、外链打开 - -**创建/修改:** - -- `desktop/electron/services/dialogs.ts` -- `desktop/electron/services/shell.ts` -- `desktop/src/lib/desktopHost/electronHost.ts` - -**步骤:** - -- [x] `openFile`、`openDirectory`、`saveFile` 映射到 Electron dialog。 -- [x] `openExternal` 限制 URL scheme,`openPath` 限制为明确来自文件选择或 workspace 的路径。 -- [x] 更新 adapter contract tests。 -- [x] 运行 `bun run check:desktop && bun run check:electron`。 -- [x] 手动 smoke:选择工作目录、选择附件、选择 Python 路径、打开外链。 - -**执行记录(2026-05-31):** - -- 新增 `desktop/electron/services/dialogs.ts` 与 `shell.ts`;dialog options 映射到 Electron `showOpenDialog/showSaveDialog`,shell URL 只允许 `http: / https: / mailto:`。 -- `DesktopHost.shell` 拆分为 `open(url)` 与 `openPath(path)`;workspace/open-with 本地文件路径改走 `openPath`,普通网页/OAuth/通知设置等外链继续走 `open`。 -- 新增 `desktop/src/lib/desktopHost/electronHost.ts`,preload 改为复用该 typed host factory;IPC payload 在 preload 进入 `ipcRenderer.invoke` 前先校验。 -- Focused tests 通过:`electron/services/shell.test.ts`、`dialogs.test.ts`、`electronHost.test.ts`、`desktopHost/contract.test.ts`、`WorkspaceFileOpenWith.test.tsx`。 -- `bun run check:electron` 通过:16 tests passed,Electron main/preload 构建成功。 -- `bun run check:desktop` 通过,现有 React/Tauri 主干未被 `openPath` contract 破坏。 -- 2026-06-01 Electron dev 壳 Computer Use 复测附件选择:点击 composer `添加文件或图片` 打开 macOS 原生 `打开` 面板,选择 `/Users/nanmi/cc-haha-cua-attachment-smoke.txt` 后回到应用,composer 显示 `cc-haha-cua-attachment-smoke.txt` 附件 chip。 -- 2026-06-01 Electron dev 壳 Computer Use 复测 Python 路径选择:在 Computer Use 设置中点击 `选择` 打开 macOS 原生 `选择 Python 解释器` 面板,选择 `/Users/nanmi/cc-haha-cua-python3-smoke` 后应用解析并保存为 `/opt/homebrew/Cellar/python@3.14/3.14.5/Frameworks/Python.framework/Versions/3.14/bin/python3.14`;随后恢复自动检测,`/api/computer-use/authorized-apps` 返回 `pythonPath: null`。 -- 2026-06-01 Electron dev 壳 Computer Use 复测外链:点击关于页 GitHub 项目卡片后,系统浏览器 Google Chrome 被拉到前台,地址栏为 `github.com/NanmiCoder/cc-haha`;Electron renderer 仍停留在 `localhost:1420/`,未在应用内导航外链。 - -**通过标准:** 所有原 `plugin-dialog` 和 `plugin-shell` 用户路径可用。 -**阻断条件:** 任一路径绕过 preload 或接受未校验目标。 - -### 7. 迁移窗口、托盘、菜单和单实例 - -**创建/修改:** - -- `desktop/electron/services/windows.ts` -- `desktop/electron/services/tray.ts` -- `desktop/electron/services/menu.ts` -- `desktop/electron/services/singleInstance.ts` - -**步骤:** - -- [x] 实现 minimize/toggleMaximize/close/startDragging。 -- [x] 实现关闭隐藏到托盘、托盘 show/quit。 -- [x] 实现 macOS About/Settings 菜单并发送 `native-menu-navigate` 等价事件。 -- [x] 实现窗口位置/尺寸持久化和 monitor 可见性保护。 -- [x] 实现 `app.requestSingleInstanceLock()`,第二实例显示主窗口。 -- [x] 运行 `bun run check:electron`。 -- [ ] 打包 app smoke:关闭窗口、托盘恢复、菜单打开设置页、重启后窗口仍可见。 - -**执行记录(2026-05-31):** - -- 新增 `desktop/electron/services/windows.ts`、`tray.ts`、`menu.ts`、`singleInstance.ts`。 -- Electron main 现在启动前申请 single-instance lock;重复启动会 show/focus 主窗口。 -- 主窗口关闭默认隐藏到托盘,托盘菜单提供 `Show Claude Code Haha` 和 `Quit Claude Code Haha`;显式 quit 才停止 sidecar 并退出。 -- 窗口位置/尺寸/maximized 状态写入 `window-state.json`,优先使用 `CLAUDE_CONFIG_DIR`,恢复前会验证尺寸和 display/workArea 可见性。 -- 原生菜单发送 `desktop:window:native-menu-navigate`,保持 renderer 现有 settings/about 路由入口。 -- Electron 先不声明自定义 Windows chrome 能力:`windowControls=false`,renderer 不渲染自定义窗口按钮,也不会把空白 tab/sidebar 区当成可拖拽窗口区域;后续若启用 frameless window,需要同时实现真实拖拽。 -- `bun run check:electron` 通过:26 tests passed,Electron main/preload 构建成功。 -- 2026-06-01 Electron dev 壳 Computer Use 复测窗口关闭/恢复:点击 macOS 关闭按钮后 Electron 进程仍在 `list_apps` 中保持 running,主窗口消失;通过 macOS app activation 路径重新激活同一 Electron app 后,Computer Use 再次看到 `Claude Code Companion` 主窗口和 `localhost:1420/` renderer。Computer Use 当前不能稳定枚举 Dock,因此本轮验证的是与 Dock 点击等价的 app activation/show 主窗口路径。 -- 2026-06-01 packaged app Computer Use 复测菜单与窗口生命周期:在 `desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app` 隔离实例中,通过 macOS app menu 点击 `Settings...` 后 renderer 打开设置页;点击关闭按钮后 Computer Use 返回 `noWindowsAvailable`,System Events 显示同一 PID `count=0` 且进程仍存活;随后用 app activation 恢复,Computer Use 再次读取到 `Claude Code Companion` 设置页窗口。继续通过 app menu `Quit` 退出后,隔离配置写入 `window-state.json`;用同一 `CLAUDE_CONFIG_DIR` 和 `--user-data-dir` 重启,Computer Use 成功读取新 PID 的 packaged `file://.../app.asar/dist/index.html` 主窗口,System Events 显示 `count=1 names=Claude Code Companion`。该轮覆盖 packaged 菜单打开设置、关闭隐藏、activation 恢复和重启后窗口仍可见;真实 tray 图标点击仍需后续实机/可枚举 tray 环境复验。 - -**通过标准:** macOS/Windows/Linux 主窗口生命周期等价。 -**阻断条件:** 关闭行为会误退出或窗口状态可能恢复到屏幕外。 - -### 8. 迁移通知 - -**创建/修改:** - -- `desktop/electron/services/notifications.ts` -- `desktop/src/lib/desktopNotifications.ts` -- `desktop/src/lib/desktopHost/contract.test.ts` - -**步骤:** - -- [x] 实现权限状态、请求权限、发送通知。 -- [x] 实现通知点击回跳并传递 target。 -- [x] Windows 保留打开系统通知设置入口。 -- [x] macOS 若 Electron `Notification` 无法满足权限/点击语义,保留 native bridge 或补原生模块。 -- [x] 运行 `bun run check:electron`。 -- [x] 运行 `bun run check:desktop`。 -- [ ] 打包 app smoke:发送通知、点击通知、跳回目标 session。 - -**执行记录(2026-05-31):** - -- 新增 `desktop/electron/services/notifications.ts`,使用 Electron main-process `Notification` 发送系统通知并监听 `click`。 -- 通知点击通过 `desktop:notification:action` 发送 `{ id, extra, target, action: 'click' }`,renderer 继续复用现有 `installDesktopNotificationClickListener` target 解析和导航。 -- Electron `commandInvoke` 兼容现有 renderer 的 legacy 通知入口:`macos_notification_permission_state`、`macos_request_notification_permission`、`macos_send_notification`、`plugin:notification|is_permission_granted`、`plugin:notification|request_permission`。 -- Windows 通知设置入口通过 allowlisted `ms-settings:notifications` 打开;macOS 系统通知设置 URL 仍限制在 allowlist。 -- 参考 Electron 官方 Notification 文档:main process 使用 `new Notification(...).show()`,点击通过 `notification.on('click', ...)` 处理;Electron 没有独立的桌面通知 request-permission API,因此 Electron host 将 `Notification.isSupported()` 映射为 host permission state。 -- `bun run check:electron` 通过:32 tests passed,Electron main/preload 构建成功。 -- `bun run check:desktop` 通过:141 test files / 1103 tests passed,production build 成功;现有 React `act(...)` warnings 仍为非阻断输出。 - -**执行记录(2026-06-01):** - -- 新增 `desktop/electron/services/notificationSmoke.ts`,提供显式环境变量触发的 Electron 通知 smoke hook:设置 `CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_SESSION_ID=` 后,main process 会延迟发送带 `{ type: 'session', sessionId }` target 的真实 Electron `Notification`。默认不启用,不影响正常启动和生产行为。 -- 新增 `desktop/electron/services/notificationSmoke.test.ts`,锁定默认不发送、target payload 透传和 delay clamp;`cd desktop && bun test electron/services/notificationSmoke.test.ts electron/services/notifications.test.ts electron/services/windows.test.ts` 通过:16 tests passed。 -- Computer Use 真实 OS 点击复测:带 smoke env 启动 Electron dev 壳并关闭主窗口后,Electron 仍 running;但本机 Computer Use 对 `/System/Library/CoreServices/NotificationCenter.app` 和 `SystemUIServer` 均返回 `timeoutReached`,无法稳定枚举或点击 macOS 通知横幅。重新 `get_app_state(Electron)` 会触发 app activation 并恢复窗口,因此不能作为“点击通知回跳”的证据。该项保持未完成,需在可操作 Notification Center/横幅的 macOS 会话或签名 packaged app 上复验。 -- 2026-06-01 通知点击二次复测:用 `CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_DELAY_MS=30000` 启动 Electron dev 壳,Computer Use 确认主窗口可见后点击 macOS close button,Electron 无主窗口且进程保持 running;等待通知触发后,Computer Use 对 `SystemUIServer` 与 Notification Center 仍 `timeoutReached`,前台 Chrome 截图/AX 树也没有出现可点击通知横幅。真实 OS 通知点击回跳仍未验收通过。 -- 2026-06-01 通知 smoke 可审计化:新增 `CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_LOG=`,仅在显式 smoke env 下把 scheduled/sent/action/send_failed 写入 JSONL,方便真实 macOS/Windows runner 点击通知后确认 Electron main 是否收到 OS click。`cd desktop && bun test electron/services/notificationSmoke.test.ts electron/services/notifications.test.ts electron/services/windows.test.ts` 通过 17 tests,`cd desktop && bun run check:electron` 通过 85 tests。 -- 2026-06-01 packaged 通知 smoke 复测:重新运行 `CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron:package:dir` 后,用隔离配置启动 packaged app,并设置 `CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_LOG=/tmp/.../notification-smoke.jsonl`。Computer Use 确认 packaged `file://.../app.asar/dist/index.html` 主窗口可见;JSONL 记录 `scheduled` 与 `sent:true`,证明 Electron main 已向系统发出真实通知。但 Computer Use 仍无法读取 `SystemUIServer` 或 `NotificationCenter.app`(均 `timeoutReached`),日志也未出现 `action`,所以“点击通知回到目标 session”仍未完成。 -- 2026-06-01 通知点击当前态再复测:用 canonical packaged app 启动隔离通知 smoke,设置 `CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_SESSION_ID=notification-click-smoke-session`、`CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_DELAY_MS=8000` 和 JSONL log;Computer Use 关闭主窗口后 app 留在后台,JSONL 记录 `scheduled` 与 `sent:true`。Computer Use 对 `/System/Library/CoreServices/UserNotificationCenter.app/` 返回安全策略拒绝,对 `NotificationCenter.app` 仍 `timeoutReached`,所以仍无法执行真实 OS 通知点击。为后续实机复验,通知 smoke 日志已扩展记录 `lifecycle: close|failed`,可区分通知发出后被系统关闭、发送失败和用户点击;`cd desktop && bun test electron/services/notificationSmoke.test.ts electron/services/notifications.test.ts` 通过 10 tests,`cd desktop && bun run check:electron` 通过 90 tests。 - -**通过标准:** 通知提示、权限、点击回跳都能在真实打包 app 里证明。 -**阻断条件:** 只在 unit test 中 mock 成功,未走系统通知。 - -### 9. 迁移自动更新 - -**创建/修改:** - -- `desktop/electron/services/updater.ts` -- `desktop/src/stores/updateStore.ts` -- `desktop/package.json` -- `.github/workflows/release-desktop.yml` - -**步骤:** - -- [x] 选定 `electron-builder` + `electron-updater`,不要用 Electron 内置 updater 覆盖 Linux。 -- [x] 保留 `check -> download -> progress -> install -> relaunch` 状态机。 -- [x] `install` 前调用 sidecar manager 停止 server/adapters,失败后恢复可重试状态。 -- [x] 建立 mock update feed 测试。 -- [x] macOS 签名/zip metadata、Windows NSIS metadata、Linux AppImage/deb update 策略写入 Electron build metadata。 -- [x] 运行 `bun run check:electron`。 -- [x] 运行 `bun run check:desktop`。 -- [x] release workflow 切换到 Electron builder,并在打包前验证签名/notarization/update metadata secrets 是否配置齐全。 - -**通过标准:** 自动更新全链路有 unit、mock feed、打包产物证据。 -**阻断条件:** 只能检查到新版本,不能安全安装并 relaunch。 - -**执行记录(2026-05-31):** - -- 新增运行时依赖 `electron-updater@6.8.3` 和打包依赖 `electron-builder@26.8.1`。 -- 新增 `desktop/electron/services/updater.ts`;`autoDownload=false`,`checkForUpdates()` 只返回 metadata,`downloadUpdate()` 将 electron-updater `download-progress` 转成现有 `DesktopUpdateDownloadEvent`,`relaunch()` 阶段调用 `quitAndInstall(false, true)`。 -- 扩展 Electron IPC:`desktop:update:download`、`desktop:update:install` 和 `desktop:update:download-event`。 -- `desktop/src/lib/desktopHost/electronHost.ts` 将 Electron update metadata 包装成现有 `DesktopUpdate` 对象,renderer `updateStore` 不需要改状态机。 -- `desktop/package.json` 已加入 Electron builder metadata:`appId=com.claude-code-haha.desktop`、GitHub publish 到 `NanmiCoder/cc-haha`、macOS `dmg/zip`、Windows `nsis`、Linux `AppImage/deb`;packaged runtime 使用 `asar=true`,并通过 `asarUnpack` 保留 `node-pty` 与 sidecar binaries。 -- `bun run check:electron` 通过:36 tests passed,Electron main/preload 构建成功。 -- `bun run check:desktop` 通过:desktop `tsc --noEmit`、Vitest 全量 146 test files / 1127 tests passed,production build 成功。 - -**执行记录(2026-06-01):** - -- 完整 macOS Electron Builder release packaging 暴露出两个自动更新发布问题:平台级 `publish: ["github"]` 会在 worktree/CI 中触发 git remote 自动探测并导致 update info 生成崩溃;默认含空格 artifact name 会让 `latest-mac.yml` 指向 `Claude-Code-Haha-...`,但实际上传文件是 `Claude Code Haha-...`。 -- 已修复 `desktop/package.json`:保留顶层显式 GitHub publish 元数据,并移除 mac/win/linux 平台级 publish 覆盖;新增统一无空格 `artifactName`,避免 update metadata 与上传 asset 文件名漂移。 -- 已增强 `scripts/quality-gate/package-smoke/index.ts`:当存在 `latest-mac.yml` / `latest.yml` / `latest-linux.yml` 时,会解析 `url` / `path` 并确认引用的 artifact 文件实际存在;当发布 archive 存在但 update metadata 缺失时会失败。 -- 已新增回归测试:`scripts/pr/release-workflow.test.ts` 防止 Electron Builder 再依赖 git remote autodetection;`scripts/quality-gate/package-smoke/index.test.ts` 会捕获 `latest-mac.yml` 指向缺失 asset 的情况。 -- 2026-06-01 packaged app updater 缺失 channel metadata 修复:本地打包 app 启动后 electron-updater 可访问 GitHub release,但当前 release 缺少 `latest-mac.yml` 时会抛 `ERR_UPDATER_CHANNEL_FILE_NOT_FOUND`。该错误现在仅在 message 指向 `latest*.yml` 时降级为“无更新”,继续保留其它 updater 错误抛出;`cd desktop && bun test electron/services/updater.test.ts` 通过 7 tests,`cd desktop && bun run check:electron` 通过 84 tests。 -- 2026-06-01 packaged app 当前态复验:重新运行 `cd desktop && CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron:package:dir` 后,从 `desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app` 以隔离 `CLAUDE_CONFIG_DIR` / `--user-data-dir` 启动;Computer Use 确认主窗口加载 `file://.../app.asar/dist/index.html`,sidecar 自动启动,未出现 Safe Storage 钥匙串弹窗,日志未再出现 `ERR_UPDATER_CHANNEL_FILE_NOT_FOUND` IPC handler 崩错。纯 `--dir` 包缺少 `app-update.yml` 的提示仍按无更新处理,不代表 signed release update feed 已验收。 -- 当前本机 `.dmg` 构建仍受 macOS DiskImages/hdiutil 状态阻塞:`hdiutil: create failed - 操作超时`,且当前 worktree 下残留的临时读写 disk image 无法正常 detach;这属于本机磁盘镜像服务阻塞,不应被 package-smoke 或浏览器 smoke 替代为发布通过。 - -### 10. 迁移 PTY 终端 - -**创建/修改:** - -- `desktop/electron/services/terminal.ts` -- `desktop/src/api/terminal.ts` - -**步骤:** - -- [x] 明确 PTY 实现:保留 Rust sidecar command 或引入 Node PTY 方案。 -- [x] 实现 spawn/write/resize/kill 和 output/exit event。 -- [x] 保留 terminal shell 配置读写语义。 -- [x] 运行 `bun run check:desktop && bun run check:electron`。 -- [x] 打包 app smoke:打开终端、输入命令;resize/kill 需随完整 packaged smoke 继续复验。 - -**通过标准:** 终端交互和事件流等价。 -**阻断条件:** Windows shell 或自定义 shell path 行为不明确。 - -**执行记录(2026-05-31):** - -- 已选择 Node PTY 方案:新增运行时依赖 `node-pty@1.1.0`,Electron main process 负责 `pty.spawn()`,renderer 只通过 preload IPC 发送 spawn/write/resize/kill。Context7 `/microsoft/node-pty` 文档确认主进程集成方式为 `spawn` + `onData`/`onExit` + `write`/`resize`/`kill`。 -- 新增 `desktop/electron/services/terminal.ts`,保留 Tauri 终端语义:`CLAUDE_CONFIG_DIR/terminal-config.json` 优先,其次 Electron `userData/terminal-config.json`;继续读取 `~/.claude/settings.json` 的 `desktopTerminal` Windows shell 配置;保留 Windows `bash_path`、`COMSPEC`、POSIX `SHELL`、`/bin/zsh`/`/bin/bash` 默认 shell 选择。 -- `terminal_spawn` 等价实现最小尺寸限制:`cols >= 20`、`rows >= 8`;默认 cwd 仍按 `cwd -> CLAUDE_CONFIG_DIR -> HOME/USERPROFILE -> process.cwd()`;环境变量合并 login shell env,并强制 UTF-8 locale,补充 `TERM=xterm-256color` 与 `COLORTERM=truecolor`。 -- `desktop/electron/main.ts` 已注册 `desktop:terminal:*` IPC:`spawn/write/resize/kill/get-bash-path/set-bash-path`,并通过 `desktop:terminal:output`、`desktop:terminal:exit` 事件回传现有 renderer payload。 -- `desktop/electron/ipc/capabilities.ts` 收紧 `terminalSpawn` payload 校验,避免 renderer 传入非数字尺寸或非字符串 cwd/shell。 -- 新增 `desktop/electron/services/terminal.test.ts`,覆盖配置路径、bash path 持久化、Windows shell 解析、UTF-8 env、spawn event 转发、write/resize/kill、exit 后 session 清理。 -- packaged macOS `node-pty` 直接从 `.app/Contents/Resources/app.asar.unpacked` 执行会在 `spawn-helper` 上触发 `posix_spawnp failed`;已改为打包时先执行 `prepare:node-pty` 修正 helper executable bit,并在 packaged macOS runtime 将 `node-pty` 复制到 Electron `userData/native/node-pty---` 后加载,避免嵌套 app bundle 资源执行限制。 -- `bun run check:electron` 通过:58 tests passed,Electron main/preload/preview-preload 构建成功。 -- `bun run check:desktop` 通过:desktop `tsc --noEmit`、Vitest 全量 146 test files / 1127 tests passed,production build 成功。 -- `CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron:package:dir` 通过并产出 unpacked macOS `.app`;Computer Use packaged smoke 通过:启动 `Claude Code Haha.app`,点击“打开终端”后显示 `运行中 /bin/zsh`,在终端输入 `printf electron-terminal-ok` 并看到输出 `electron-terminal-ok`。终端 resize/close kill 仍需在完整 packaged smoke 中补齐。 - -### 11. 迁移预览面板 - -**创建/修改:** - -- `desktop/electron/services/preview.ts` -- `desktop/src/lib/previewBridge.ts` -- `desktop/src/lib/previewEvents.ts` - -**步骤:** - -- [x] 使用 `WebContentsView`,不要用已弃用的 `BrowserView`。 -- [x] 保留 `http/https` URL 白名单。 -- [x] 保留 preview agent 注入和 JSON 消息校验。 -- [x] 实现 open/navigate/setBounds/setVisible/close/eval/message。 -- [x] 运行 `bun run check:desktop && bun run check:electron`。 -- [x] 打包 app smoke:打开 workbench/browser panel,导航 URL,完成 preview/screenshot 回填 smoke。 - -**通过标准:** 预览面板功能等价且安全边界保留。 -**阻断条件:** 允许 `file:` 或 `javascript:` URL 进入 preview。 - -**执行记录(2026-05-31):** - -- Context7 `/electron/electron` 文档确认当前 API 使用 `WebContentsView` + `contentView.addChildView()` + `setBounds()` + `webContents.loadURL()`,不再新增 `BrowserView`。 -- 新增 `desktop/electron/services/preview.ts`,使用 `WebContentsView` 实现 `open/navigate/setBounds/setVisible/close/eval`,继续拒绝空 URL、`file:`、`javascript:` 等非 `http/https` URL。 -- 新增 `desktop/electron/preview-preload.ts`,只暴露 `window.__DESKTOP_PREVIEW_POST__(raw)`;外部页面无法拿到 Node/Electron API,只能把 JSON 字符串发回 main process。 -- `desktop/src/preview-agent/index.ts` 改为调用通用 `__DESKTOP_PREVIEW_POST__`;Tauri 端在 `desktop/src-tauri/src/webview_panel.rs` 创建 child webview 时先注入该函数,再注入 preview agent;Electron 端由 preview preload 注入同名函数。 -- Electron main process 增加内部 channel `desktop:preview:message-from-view`,只接受当前 preview `webContents` 发出的 raw JSON;校验 JSON 后通过 `desktop:preview:event` 转发给 React。 -- `desktop/package.json` 的 `build:electron` 增加 `preview-preload.cjs` 构建,Electron builder files 增加 `src-tauri/resources/preview-agent.js`,保证 packaged app 能读取注入脚本。 -- 新增 `desktop/electron/services/preview.test.ts`,覆盖 URL 白名单、bounds 规范化、child view 复用、load 后注入 preview agent、JSON event 转发、close 清理。 -- `bun run build:preview-agent` 已重建 `desktop/src-tauri/resources/preview-agent.js`。 -- `bun run check:electron` 通过:49 tests passed,Electron main/preload/preview-preload 构建成功。 -- `bun run check:desktop` 通过:desktop `tsc --noEmit`、Vitest 全量 146 test files / 1127 tests passed,production build 成功。 -- 2026-06-01 Computer Use packaged smoke 覆盖 preview/workbench:临时 packaged app 中打开 browser panel,导航 `https://example.com/` 成功,并完成 screenshot 回填 composer;完整 preview event 回归继续由 `desktop/electron/services/preview.test.ts` 覆盖。 - -### 12. 迁移 app mode、portable config 和 zoom - -**创建/修改:** - -- `desktop/electron/services/appMode.ts` -- `desktop/electron/services/zoom.ts` -- `desktop/src/stores/settingsStore.ts` -- `desktop/src/lib/appZoom.ts` - -**步骤:** - -- [x] 保留 `get_app_mode`、`set_app_mode`、`detect_portable_dir` 返回形状。 -- [x] 保留同时写系统默认配置目录和 portable 目录的语义。 -- [x] zoom 继续 clamp 到 `0.5..2.0`。 -- [x] 运行 `bun run check:persistence-upgrade`。 -- [x] 运行 `bun run check:desktop && bun run check:electron`。 - -**通过标准:** 老配置可升级,unknown fields 保留。 -**阻断条件:** 写入 `~/.claude/settings.json` 或受保护配置路径。 - -**执行记录(2026-05-31):** - -- 新增 `desktop/electron/services/appMode.ts`,复刻 Tauri `app-mode.json` 语义:启动时按 `external CLAUDE_CONFIG_DIR -> default portable app-mode.json -> system app-mode.json -> default portable data auto-detect` 判定是否设置 `CLAUDE_CONFIG_DIR`。 -- Electron `app.whenReady()` 后、server sidecar 启动前调用 `applyStartupPortableMode(app)`,保证 sidecar、window state、terminal config 都能看到 portable env;同时设置 `CC_HAHA_APP_PORTABLE_DIR=1` 与 `WEBVIEW2_USER_DATA_FOLDER=/EBWebView`。 -- `desktop:app-mode:get` 返回现有 renderer 形状:`mode`、`portableDir`、`defaultPortableDir`、`activeConfigDir`、`configDirSource`;`configDirSource` 区分 `system`、外部环境变量 `environment`、应用切换产生的 `portable`。 -- `desktop:app-mode:set` 在当前 active config、目标 portable dir、系统默认 `userData` 三处写入 `app-mode.json`,不写 `~/.claude/settings.json`,也不移动/删除已有数据。 -- `desktop:app-mode:restart` 现在 `relaunch()` 后立即 `quit()`,与设置页“切换后重启”的语义一致;`prepareRestart` 继续先停止 server/adapters。 -- 新增 `desktop/electron/services/zoom.ts`,native zoom 统一 clamp 到 `0.5..2.0`,避免 Electron IPC 绕过 renderer 侧限制。 -- 新增 `appMode.test.ts` 与 `zoom.test.ts`,覆盖 portable 数据探测、启动模式解析、env 设置、返回形状、三处 app-mode 写入、zoom clamp。 -- `bun run check:electron` 通过:56 tests passed,Electron main/preload/preview-preload 构建成功。 -- `bun run check:persistence-upgrade` 通过:server persistence 6 tests passed,desktop localStorage migration 8 tests passed。 -- `bun run check:desktop` 通过:desktop `tsc --noEmit`、Vitest 全量 146 test files / 1127 tests passed,production build 成功。 - ---- - -## Phase 4:Build、CI、Release - -### 13. 替换本地构建脚本 - -**修改:** - -- `desktop/package.json` -- `package.json` -- `desktop/scripts/build-macos-arm64.sh` -- `desktop/scripts/build-windows-x64.ps1` - -**步骤:** - -- [x] 保留 `desktop build` 作为 renderer build。 -- [x] 保留 `build:sidecars`。 -- [x] 新增 Electron package 脚本,输出到 `desktop/build-artifacts/`。 -- [x] `check:native` 替换为 Electron main/preload/package config check,或新增 `check:electron` 并让 `quality:gate` 使用它。 -- [x] 运行 `bun run check:desktop && bun run check:electron`。 - -**通过标准:** 本地 macOS/Windows 构建命令产出可启动 app。 -**阻断条件:** sidecar 没有被打进产物或产物启动后仍读开发路径。 - -**执行记录(2026-05-31):** - -- `desktop/package.json` 保留 `build` 为 renderer build,新增/调整 `electron:build`、`electron:package`、`electron:package:dir`,并将 `build:electron` 扩展为 main/preload/preview-preload 三个 bundle。 -- Electron builder 配置包含 `dist/**`、`electron-dist/**`、`src-tauri/binaries/**`、`src-tauri/resources/preview-agent.js` 和 `node_modules/node-pty/**`;`electron-updater` 已打进 main bundle,`node-pty` 保持外部原生模块并通过 `asarUnpack` 解包。 -- `check:native` 已从 Tauri `cargo check` 改为 `build:sidecars + check:electron`;`quality:gate` native lane 描述同步为 Electron host/package checks。 -- `desktop/scripts/build-macos-arm64.sh` 改为 Electron Builder:构建 sidecar、renderer、Electron bundles,执行 `electron-builder --mac dmg zip --arm64 --publish never`,输出复制到 `desktop/build-artifacts/macos-arm64`。 -- `desktop/scripts/build-windows-x64.ps1` 改为 Electron Builder:导入 MSVC 环境,构建 sidecar、renderer、Electron bundles,执行 `electron-builder --win nsis --x64 --publish never`,输出复制到 `desktop/build-artifacts/windows-x64`。 -- `desktop/scripts/build-macos-arm64.sh` 支持 `MAC_TARGETS` 覆盖 Electron Builder macOS target,默认仍为 `dmg zip`;本机 DiskImages 异常时可用 `MAC_TARGETS=zip` 单独验证 zip/update metadata 链路。脚本会对当前 worktree 的 stale Electron Builder 临时 DMG 挂载 fail fast,成功复制 canonical artifacts 后默认运行 package-smoke。 -- `desktop/scripts/build-windows-x64.ps1` 会把 installer、update metadata、blockmap 和 `win-unpacked` 复制到 canonical `desktop/build-artifacts/windows-x64`;成功复制后默认运行 `bun run test:package-smoke --platform windows --package-kind release --artifacts-dir desktop/build-artifacts/windows-x64`,可用 `SKIP_PACKAGE_SMOKE=1` 跳过静态验包。 -- `desktop/scripts/build-linux.sh` 支持 `LINUX_ARCH=x64|arm64` 和 `LINUX_TARGETS` 覆盖,默认构建 AppImage/deb x64;脚本会复制 `.AppImage`、`.deb`、`latest-linux.yml`、blockmap 和 `linux-unpacked` 到 canonical `desktop/build-artifacts/linux-`,并默认运行 `bun run test:package-smoke --platform linux --package-kind release --artifacts-dir desktop/build-artifacts/linux-`。 -- `node-pty` 原生模块验证:`bunx electron ./tmp-electron-node-pty-smoke.cjs` 在 Electron 42.3.0 runtime 下成功输出 `node-pty spawn type: function`。 -- `electron-builder install-app-deps` 在本机 `@electron/rebuild` node-gyp worker 上空转无输出;由于 `node-pty` Electron runtime smoke 已通过,默认配置设为 `npmRebuild=false`,脚本保留 `REBUILD_NATIVE=1` 作为 runner/调试开关。 -- packaged `.app` 首轮 smoke 暴露两个打包差异:Vite 默认绝对 `/assets/...` 在 `file://` 下加载失败,已通过 `vite.config.ts base: './'`、`publicAssetPath()` 和字体 URL 改造修复;`asar=false` 的全量 unpacked resources 会触发 macOS assessment `Too many open files`,已切换为 `asar=true` + `asarUnpack`。 -- `CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron:package:dir` 通过并产出 `desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app`;`codesign --verify --deep --strict --verbose=2` 通过;packaged app 启动后 sidecar 从 `app.asar.unpacked/src-tauri/binaries/claude-sidecar-aarch64-apple-darwin` 运行,renderer 从 `app.asar/dist/index.html` 加载。 -- `bun run check:native` 通过:sidecar build 成功,Electron host 56 tests passed,main/preload/preview-preload 构建成功。 -- 2026-06-01 已把 `check:native` 扩展为 `build:sidecars + check:electron + electron:package:dir + test:package-smoke:current`;最新复跑通过:Electron 77 tests passed,Electron `--dir` 产出 `desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app`,当前平台 macOS package-smoke PASS。 -- `bun run check:desktop` 通过:desktop `tsc --noEmit`、Vitest 全量 146 test files / 1127 tests passed,production build 成功。 -- 生产 React 直接 Tauri 扫描无输出:`rg -n "@tauri-apps|__TAURI_INTERNALS__|window\\.__TAURI__|from '@tauri" desktop/src --glob '!**/*.test.ts' --glob '!**/*.test.tsx' --glob '!**/lib/desktopHost/**' --glob '!src-tauri/**'`。 - -### 14. 替换 CI workflow - -**修改:** - -- `.github/workflows/build-desktop-dev.yml` -- `.github/workflows/release-desktop.yml` -- `.github/workflows/pr-quality.yml` -- `scripts/quality-gate/modes.ts` -- `scripts/pr/change-policy.ts` -- `scripts/pr/check-pr.ts` - -**步骤:** - -- [x] 移除 `tauri-apps/tauri-action@v0`。 -- [x] 移除 Linux WebKitGTK/Rust/Tauri 专用依赖,保留 Electron/Linux 打包需要的依赖。 -- [x] 配置 macOS arm64/x64、Windows x64、Linux x64/arm64 matrix。 -- [x] 配置 signing/notarization/update metadata secrets。 -- [x] 让 PR/change policy 识别 `desktop/electron/**` 和 Electron build config。 -- [x] 运行 `bun run check:policy`。 - -**通过标准:** CI lane 名称、触发条件和产物路径都更新为 Electron。 -**阻断条件:** PR 改 Electron host 文件但不会触发桌面/原生检查。 - -**执行记录(2026-05-31):** - -- `.github/workflows/build-desktop-dev.yml` 改为 Electron Builder matrix:macOS arm64/x64、Windows x64、Linux x64/arm64;每个 job 安装 Bun/Node、构建 sidecars、构建 renderer/Electron bundles、运行 `electron-builder` 并上传 `desktop/build-artifacts/electron` 产物。 -- `.github/workflows/release-desktop.yml` 移除 `tauri-apps/tauri-action@v0`,改为 `electron-builder --publish never` 生成产物,再用 `softprops/action-gh-release@v2` 上传 release assets。 -- Linux CI 依赖删除 WebKitGTK/Rust/Tauri 专用包,仅保留 Electron packaging 与 AppImage 需要的基础构建工具和 `libfuse2`。 -- PR `desktop-native-checks` 不再安装 Rust toolchain 或 Rust cache;`desktop/scripts/build-sidecars.ts` 改为用 `process.platform` / `process.arch` 推导 host triple,避免 Electron `check:native` 继续依赖 `rustc -vV`。 -- sidecar build target env 已改为 `SIDECAR_TARGET_TRIPLE`;`build-sidecars.ts` 暂时兼容旧 `TAURI_ENV_TARGET_TRIPLE`,但 CI 和平台脚本不再使用 Tauri 命名。 -- `scripts/pr/change-policy.ts` 将 `desktop/electron/tsconfig.json`、Electron macOS/Windows build scripts 纳入 native/release paths;`scripts/quality-gate/modes.ts` 和 `scripts/pr/impact-report.ts` 文案从 Tauri native 改为 Electron host/package。 -- `bun run check:policy` 通过:64 tests passed,quarantine review 0 expired。 - -### 15. 替换 release 脚本 - -**修改:** - -- `scripts/release.ts` -- `scripts/pr/release-workflow.test.ts` - -**步骤:** - -- [x] 版本来源从 `desktop/src-tauri/tauri.conf.json` 改为 Electron package/build config。 -- [x] 删除 `Cargo.toml`/`Cargo.lock` release 更新。 -- [x] 保留 release note 校验、commit、tag 流程。 -- [x] 运行 `bun run scripts/release.ts --dry`。 -- [x] 运行 `bun test scripts/pr/release-workflow.test.ts`。 - -**通过标准:** dry run 显示的版本文件全部属于 Electron 发布链。 -**阻断条件:** release 仍依赖 Tauri 版本文件。 - -**执行记录(2026-05-31):** - -- `scripts/release.ts` 当前版本来源改为 `desktop/package.json`,版本更新文件只保留 `desktop/package.json`。 -- release 脚本不再更新 `desktop/src-tauri/tauri.conf.json`、`Cargo.toml` 或 `Cargo.lock`,也不再运行 `cargo generate-lockfile`。 -- Git commit/tag 流程仍保留 release notes 校验、`git add desktop/package.json release-notes/vX.Y.Z.md`、commit、annotated tag。 -- `bun run scripts/release.ts patch --dry` 通过,显示 `0.3.1 -> 0.3.2`,只会更新 `desktop/package.json` 和 `release-notes/v0.3.2.md`。 -- `bun test scripts/pr/release-workflow.test.ts scripts/pr/change-policy.test.ts` 通过:11 tests passed。 - ---- - -## Phase 5:最终验收 - -### 16. Electron packaged smoke - -**步骤:** - -- [ ] macOS:构建 `.app/.dmg`,启动 app,验证聊天、附件、通知、托盘、更新 mock、终端、预览面板。 -- [ ] Windows:构建 NSIS 安装器,安装后启动 app,验证 sidecar 文件锁和更新前停进程。 -- [ ] Linux:构建 deb/AppImage,验证启动、tray、通知、更新策略。 -- [x] 运行 `bun run test:package-smoke --platform macos` 作为包结构预检;Windows/Linux 待对应平台产物。 - -**通过标准:** 每个平台至少一个真实发布格式完成 smoke。 -**阻断条件:** 只有 dev build 或 Vite 页面通过。 - -**执行记录(2026-05-31):** - -- macOS unpacked `.app` smoke 已完成一部分:`CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron:package:dir` 成功,Computer Use 启动 packaged app 后主界面正常渲染,server sidecar 正常启动,终端面板可打开并执行 `printf electron-terminal-ok`。 -- Review 后重新运行 `cd desktop && CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron:package:dir` 成功;`bun run test:package-smoke --platform macos` 通过,确认当前 `.app` 结构、`app.asar`、unpacked sidecar、unpacked `node-pty` native module 和 `spawn-helper` 存在。 -- 因本机已有正式 `/Applications/Claude Code Haha.app` 实例持有单实例锁,Computer Use 最终 smoke 使用同一构建输出重新打包临时 `Claude Code Haha Smoke.app`(仅临时 appId/productName 不同)执行:packaged renderer 从 `app.asar/dist/index.html` 加载,sidecar 自动启动,终端执行 `electron-terminal-ok`,系统目录选择器可打开,preview WebContentsView 可导航 `https://example.com/`,截图可回填 composer。 -- 注意:`test:package-smoke` 是 bundle-structure/static-artifact preflight,不启动 GUI,也不替代 Computer Use 或实机 smoke。 -- 2026-06-01 同步本地 `main` 后重新运行 `cd desktop && CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron:package:dir` 成功,产出 `desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app`。 -- 2026-06-01 使用 Computer Use 操作真实 packaged app,并把本机 cc-haha provider/model 配置复制到隔离临时 `CLAUDE_CONFIG_DIR`:模型选择器显示 `gpt-5.5 Sub2API-ChatGPT`,系统目录选择器选中 `/private/tmp/cc-haha-electron-real-fixed-sRFUeO/project`。 -- 真实模型 smoke 会话 `e0d7abc2-27db-4ff1-9954-7923f6c3385e` 通过:packaged renderer 从 `file://.../app.asar/dist/index.html` 加载,server sidecar 建立 WebSocket,CLI subprocess 使用 `--model gpt-5.5` 启动,模型读取 `package.json` 与 `src/greeting.ts` 后经权限审批执行 `bun test`,结果为 `1 pass / 0 fail`,最终回复“测试通过。”。 -- 2026-06-01 重跑 `SKIP_INSTALL=1 SIGN_BUILD=0 desktop/scripts/build-macos-arm64.sh`:修复平台级 publish 后曾产出 `.app/.zip/.dmg/latest-mac.yml`,但 `latest-mac.yml` 与实际含空格 asset 文件名不一致;加入稳定 `artifactName` 后,清理旧 disk image 挂载前本机 DiskImages/hdiutil 在 `.dmg` 创建阶段超时,zip-only Electron Builder 也曾在本机 7za 子进程处失败。 -- 2026-06-01 清理旧 Tauri/Electron disk image 挂载后,`MAC_TARGETS=zip SKIP_INSTALL=1 SIGN_BUILD=0 desktop/scripts/build-macos-arm64.sh` 通过,canonical `desktop/build-artifacts/macos-arm64` 产出 `.app`、`Claude-Code-Haha-0.3.2-arm64.zip`、`.zip.blockmap` 和 `latest-mac.yml`;脚本自动运行 `bun run test:package-smoke --platform macos --artifacts-dir desktop/build-artifacts/macos-arm64` 并 PASS,确认 `latest-mac.yml` 引用真实 zip 且 packaged resources 含 `app-update.yml`。 -- 2026-06-01 `.dmg` target 最新复验通过:后续默认 `SKIP_INSTALL=1 SIGN_BUILD=0 desktop/scripts/build-macos-arm64.sh` 已完成 `dmg zip` target,canonical `desktop/build-artifacts/macos-arm64` 包含 `.app`、`.dmg`、`.zip`、blockmap 和 `latest-mac.yml`,脚本自动 release package-smoke PASS。Gatekeeper/signed/notarized launch 仍保持 release blocker。 -- 2026-06-01 macOS build script hardening:默认 `dmg zip` target 在当前 worktree 检测到 stale Electron Builder `.temp...dmg` 挂载时会提前失败并给出 `MAC_TARGETS=zip` 替代路径,避免继续进入会卡死的 DiskImages detach/create 分支。 -- 2026-06-01 Windows build script hardening:canonical 输出现在保留 `win-unpacked` 供 package-smoke 检查 `app.asar`、`app-update.yml`、unpacked sidecar 和 `node-pty` 原生模块;脚本默认在 Windows runner 上执行 canonical package-smoke,但本机 macOS 未执行 NSIS 真实构建/安装。 -- 2026-06-01 Linux build script hardening:新增 `desktop/scripts/build-linux.sh` 与 `build:linux-x64` / `build:linux-arm64` scripts,canonical 输出现在保留 AppImage/deb、`latest-linux.yml` 和 `linux-unpacked` 供 package-smoke 检查;本机 macOS 不能执行 Linux 真实构建/安装。 -- 2026-06-01 Linux package-smoke 区分开发目录包和发布包:`electron-builder --dir` 只产出 `linux-unpacked` 时仍检查 `app.asar`、sidecar 和 `node-pty` 并通过 `check:native`;AppImage/deb 发布产物存在时继续要求 `latest-linux.yml` 和 `app-update.yml`。 -- 2026-06-01 Linux arm64 updater metadata 修复:`build-linux.sh` 现在复制 `latest-linux*.yml`,避免 arm64 Electron Builder 产出 `latest-linux-arm64.yml` 时 canonical `linux-arm64` 目录丢失 update metadata;`package-smoke` 同步识别 `latest-linux.yml` 和 `latest-linux-arm64.yml`,并检查 metadata 引用的 AppImage/deb artifact 是否真实存在。 -- 2026-06-01 package-smoke 增加显式 `--package-kind dir|release`:`check:native` 使用 `dir + desktop/build-artifacts/electron`,发布/dev packaging workflow 和平台 release 脚本使用 `release + 精确 artifacts-dir`,避免旧 installer/metadata 污染本轮验包。 -- 2026-06-01 Gatekeeper 诊断加固:`--require-macos-gatekeeper` 的 `spctl` 失败时,package-smoke 会同时记录 `codesign --verify --deep --strict --verbose=2`、`codesign -dv --verbose=4` 与 `xcrun stapler validate` 摘要,方便区分未签名、签名链、bundle 格式和 notarization ticket 问题。 -- 2026-06-01 SubAgent Review 修复:release workflow 在打包矩阵前新增 `bun run verify` 非 live 预检;`quality:gate --mode release` 会包含 PR checks,并把 `desktop-package-smoke:` 改为 `--package-kind release --artifacts-dir desktop/build-artifacts/`,避免验到旧的 `--dir` 包。 -- 2026-06-01 多架构 metadata 风险收敛:release workflow 上传前会把 `latest*.yml` 改名为带 matrix label 后缀的唯一 asset,避免 GitHub Release 中同名 metadata 互相覆盖或上传失败;matrix 全部通过后由 `scripts/release-update-metadata.ts` 重新发布标准 updater channel metadata。macOS 会把 x64/arm64 zip entries 合并回 `latest-mac.yml`,Linux 会恢复 `latest-linux.yml` / `latest-linux-arm64.yml`,Windows 会恢复 `latest.yml`。完整自动更新 release 仍需在 signed/notarized artifact 上复验。 -- 2026-06-01 signing/notarization secrets preflight:release workflow 新增非 matrix `signing-preflight` job,在任何平台 artifact 上传前检查 `MACOS_CERTIFICATE`、`MACOS_CERTIFICATE_PASSWORD`、`APPLE_ID`、`APPLE_APP_SPECIFIC_PASSWORD`、`APPLE_TEAM_ID`、`WINDOWS_CERTIFICATE` 和 `WINDOWS_CERTIFICATE_PASSWORD`。缺失时用 GitHub Actions error 早停,避免部分 matrix leg 先发布 partial release。真实 Developer ID signing/notarization 仍需在 CI secrets 存在时复验。 -- 2026-06-01 native/package 当前态复验:`bun run check:native` 通过,完成 sidecar 构建和 ad-hoc signing、`check:electron` 83 tests、desktop production build、`electron-builder --dir`,并运行 `bun run test:package-smoke --platform macos --package-kind dir --artifacts-dir desktop/build-artifacts/electron` PASS。该证据只覆盖 directory-only unpacked app bundle 结构、`app.asar`、sidecar、`node-pty` 和 `spawn-helper`,不替代 `.dmg`、Gatekeeper 或 signed release smoke。 -- 2026-06-01 DMG 当前态复验:默认 `SKIP_INSTALL=1 SIGN_BUILD=0 desktop/scripts/build-macos-arm64.sh` 已通过,canonical `desktop/build-artifacts/macos-arm64` 恢复 `.app`、`.dmg`、`.dmg.blockmap`、`.zip`、`.zip.blockmap`、`latest-mac.yml`,并由 release package-smoke PASS。 -- 2026-06-01 跨平台 package-smoke 当前态复验:`bun test scripts/quality-gate/package-smoke/index.test.ts scripts/pr/release-workflow.test.ts scripts/release-update-metadata.test.ts` 通过 26 tests,覆盖 Windows canonical output、Linux x64/arm64 update metadata、release workflow Gatekeeper/signing preflight 和 metadata republish;`bun run test:package-smoke --platform macos --package-kind release --artifacts-dir desktop/build-artifacts/macos-arm64` PASS,确认当前 macOS zip/update canonical artifact set 仍完整。 -- 本轮尚未完成 `.dmg` 安装包、通知点击、signed/notarized Gatekeeper launch、Windows NSIS 和 Linux AppImage/deb 的真实发布格式 smoke,因此本阶段保持未完成。附件选择、目录选择、外链、终端和 preview 已有 dev 或 unpacked packaged Computer Use 证据,但还不能替代最终 signed release artifact 的全量验收。 - -### 17. Release gate - -**步骤:** - -- [x] 运行 `bun run verify`。 -- [x] 有 live provider 时运行 `bun run quality:gate --mode release --allow-live --provider-model `。 -- [x] 检查 quality report 的 pass/fail/skip。 -- [x] 记录 coverage report 路径。 - -**通过标准:** PR/release 门禁通过,coverage 未退化。 -**阻断条件:** 任一 release 必需 lane 失败。 - -**执行记录(2026-06-01):** - -- `bun run verify` 通过:最新为 `artifacts/quality-runs/2026-05-31T21-42-57-279Z/report.md`,`passed=9 failed=0 skipped=1`。 -- 首次重跑完整 `release --allow-live` 后,旧 agent-browser/Vite blocker 已消失;唯一失败为 coverage 中 `h5-access-auth.test.ts` 随机端口撞到本机 WeChat 的 `127.0.0.1:24023`。 -- 已把 `h5-access-auth.test.ts` 端口选择改成 OS 分配 ephemeral port;focused `bun test src/server/__tests__/h5-access-auth.test.ts` 通过,`bun run check:coverage` 最新通过:`artifacts/coverage/2026-05-31T21-46-15-873Z/coverage-report.md`,`passed=5 failed=0`。 -- 历史 `bun run quality:gate --mode release --allow-live --provider-model sub2api-chatgpt:main:sub2api-chatgpt-main` 曾通过:`artifacts/quality-runs/2026-05-31T20-29-45-758Z/report.md`,`passed=15 failed=0 skipped=0`。SubAgent Review 后 release gate 已收窄为必须检查 canonical release artifact,当前仍需在 signed/notarized artifact 与可用 Computer Use 桌面会话上重新跑完整 release gate。 - -### 18. Computer Use 全面测试 - -**步骤:** - -- [x] 用真实打包 app 运行 Computer Use 设置流程。 -- [x] 验证 Python 路径选择、权限授权、屏幕录制/辅助功能授权提示、授权应用列表。 -- [x] 验证点击、输入、滚动、系统快捷键、剪贴板。 -- [ ] 验证通知点击回到目标会话。 -- [ ] macOS 和 Windows 至少各完成一次;Linux 如能力降级,写入 release note 和已知限制。 - -**通过标准:** Computer Use 在真实 Electron app 中完成端到端操作。 -**阻断条件:** 只用浏览器/Vite 或 mock 路径证明。 - -**验证清单:** 迁移收口使用 `docs/desktop/09-electron-migration-validation-checklist.md`,其中区分自动化证据、macOS Computer Use smoke、Windows 待实机和 Linux 待实机。 - -**执行记录(2026-06-01):** - -- 尝试继续 Computer Use GUI 验收时,Computer Use 工具层对 `list_apps` 和 `get_app_state(Finder)` 均返回 `NSOSStatusErrorDomain Code=-600 procNotFound`。该结果说明当前 macOS 自动化桥未拿到可用进程,不是 Electron app 单点问题;剩余附件、Python 路径、外链、Dock/tray、通知点击和完整 update feed 验收保持未完成。 -- 2026-06-01 后续复测 Computer Use 仍返回同一 `procNotFound`,这是连续第二个 goal turn 复现;目标仍可在代码和发布门禁层继续推进,因此暂不标记 goal blocked。 -- 2026-06-01 再次复测后 Computer Use `list_apps` 已恢复,但 `get_app_state(Electron)` 对 Electron dev 壳超时,`get_app_state(Finder)` 为 `cgWindowNotFound`;系统 `screencapture` 同时返回 `could not create image from display`。这说明当前桌面会话没有可用截图/窗口捕获能力,Computer Use 交互验收仍不能推进。 -- 2026-06-01 continuation 复测:Computer Use `list_apps` 仍可列应用,`get_app_state(Finder)` 仍为 `cgWindowNotFound`,`screencapture` 仍失败为 `could not create image from display`;当前仍不能完成附件、Python 路径、外链、Dock/tray、通知点击等 GUI 验收。 -- 2026-06-01 packaged app 启动被 macOS `AppleSystemPolicy` 拦截:`spctl -a -t execute desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app` 报 `bundle format unrecognized, invalid, or unsuitable`,系统日志显示 ad-hoc/unknown-chain app 被终止。本轮已新增 `bun run test:package-smoke --platform macos --require-macos-gatekeeper` 作为可选 release-readiness 检查;当前该检查失败,release-ready 需要 Developer ID signing/notarization 或真实签名产物复验。 -- 2026-06-01 release workflow 已补强:macOS release artifact 上传前强制运行 `bun run test:package-smoke --platform macos --require-macos-gatekeeper`,防止未通过 macOS 启动策略的 `.app/.dmg/.zip` 被发布;dev build 仍保留结构型 package-smoke。 -- 2026-06-01 修复 Electron dev launcher 代理问题:`electron:dev` 默认 renderer URL 改为 `http://localhost:1420`,并对当前进程、Vite 与 Electron 子进程补齐 `NO_PROXY/no_proxy=localhost,127.0.0.1,::1`,避免本机代理把 localhost renderer 等待请求打成 502。 -- 2026-06-01 主窗口显示逻辑加固:创建 `BrowserWindow` 后立即 `show/focus`,renderer load 完成后再次 `show/focus`,避免隐藏窗口在 load 等待期间被托盘/单实例/Computer Use 路径视为无窗口。 -- 2026-06-01 SubAgent gap follow-up:renderer 生产代码不再用 `isTauriRuntime()` 判断通用桌面能力,新增 `isDesktopRuntime()`,更新弹窗、附件 native picker、设置 app mode、移动布局分支均识别 Electron/Tauri desktop host。 -- 2026-06-01 Electron updater proxy contract 已补齐:renderer 传入的 `{ proxy }` 会进入 Electron main,检查更新前应用到 Electron `app/session` proxy;切回系统代理时会清理手动代理,避免更新检查继续沿用旧 proxy。 -- 2026-06-01 package-smoke 增加发布型包的 installed updater metadata 检查:当 artifact set 含 `.zip/.dmg/latest-mac.yml`、Windows installer/latest.yml 或 Linux package/latest-linux.yml 时,安装后 resources 目录必须包含 `app-update.yml`;纯 `--dir` 开发包会记录 note 但不失败。 -- 2026-06-01 真实 provider smoke 使用本机 `~/.claude/cc-haha/providers.json` selector `sub2api-chatgpt:main:sub2api-chatgpt-main` 通过:`artifacts/quality-runs/2026-05-31T21-28-07-154Z/report.md`,`passed=1 failed=0 skipped=0`。该路径复用 cc-haha provider 配置,不走 `QUALITY_GATE_PROVIDER_*` 明文 env-only 分支。 -- 2026-06-01 `check:native` 已补上打包后验包:根脚本现在会在 Electron `--dir` 打包后运行 `test:package-smoke:current`。最新 `bun run check:native` 通过,package-smoke notes 明确当前纯 `--dir` macOS artifact set 不强制 `app-update.yml`,也不代表 GUI 启动或 Gatekeeper approval。 -- 2026-06-01 final-definition 复测:Computer Use `list_apps` 正常,但 `get_app_state(Finder)` 仍返回 `cgWindowNotFound`;系统 `screencapture -x /tmp/cc-haha-cua-retake-final.png` 仍失败为 `could not create image from display`。当前机器继续不能承载真实 GUI 操作验收。 -- 2026-06-01 cross-platform-script 复测:Computer Use `list_apps` 正常且可见 `/Applications/Claude Code Haha.app`,`get_app_state(Finder)` 仍为 `cgWindowNotFound`,`get_app_state(Claude Code Haha)` 返回 `remoteConnection`,系统 `screencapture -x /tmp/cc-haha-computer-use-check.png` 仍失败为 `could not create image from display`。当前仍不能完成点击、输入、附件选择、托盘恢复、通知点击等 GUI 验收。 -- 2026-06-01 package-kind 复测:Computer Use `list_apps` 正常且显示 `/Applications/Claude Code Haha.app` 正在运行,`get_app_state(Claude Code Haha)` 和 `get_app_state(Finder)` 均返回 `cgWindowNotFound`,系统 `screencapture -x /tmp/cc-haha-computer-use-check-2.png` 仍失败为 `could not create image from display`。当前机器仍不能承载真实 GUI 操作验收。 -- 2026-06-01 Gatekeeper 诊断加固:release-readiness package-smoke 在 `spctl` 失败时会补充 `codesign` verify/details 摘要,后续 signed/notarized artifact 的失败可以直接从 quality log 判断是签名链、bundle 格式还是 notarization policy 问题。 -- 2026-06-01 Gatekeeper 诊断复验:当前 ad-hoc `.app` 仍被 `spctl` 拒绝为 `bundle format unrecognized, invalid, or unsuitable`,但 package-smoke 已输出 `codesign verification exited with status 0` 与 bundle identifier/signature detail 摘要,证明诊断链路可用;release blocker 仍是 Developer ID signing/notarization 或真实签名产物复验。 -- 2026-06-01 Gatekeeper 诊断后 Computer Use 复测:`list_apps` 可见运行中的 `/Applications/Claude Code Haha.app`,但 `get_app_state(Claude Code Haha)` 和 `get_app_state(Finder)` 均仍为 `cgWindowNotFound`,系统 `screencapture -x /tmp/cc-haha-computer-use-check-3.png` 仍失败为 `could not create image from display`。当前机器继续无法执行点击、输入、文件选择器、托盘/通知等 GUI 验收。 -- 2026-06-01 Security/Runtime Review 修复:Electron 移除了 renderer 可调用的 `previewEval` 任意 JS 注入通道,预览截图/元素选择改走结构化 `preview.message`;`shell.openPath` 改为只允许已存在的普通文件/目录,并拒绝 `.app`、脚本/安装器/Windows 可执行扩展和 POSIX executable 文件;Electron 暂不声明自定义 window controls,避免 Windows 原生标题栏与自定义标题栏双渲染。 -- 2026-06-01 release metadata 合并后 Computer Use 复测:`list_apps` 可见运行中的 `/Applications/Claude Code Haha.app`,但 `get_app_state(Claude Code Haha)` 和 `get_app_state(Finder)` 均仍为 `cgWindowNotFound`,系统 `screencapture -x /tmp/cc-haha-computer-use-check-4.png` 仍失败为 `could not create image from display`。当前机器继续无法执行点击、输入、文件选择器、托盘/通知等 GUI 验收。 -- 2026-06-01 mock update feed UI 流程补强:`UpdateChecker` 集成测试通过 Electron `desktopHost` mock update feed 驱动真实 `updateStore`,覆盖 check/download/install/relaunch 状态流、下载完成弹窗、点击安装后 prompt 退出。signed/release feed 仍需真实 signed/notarized artifact 复验。 -- 2026-06-01 mock update feed UI 补强后 Computer Use 复测:`list_apps` 可见运行中的 `/Applications/Claude Code Haha.app`,但 `get_app_state(Claude Code Haha)` 和 `get_app_state(Finder)` 均仍为 `cgWindowNotFound`,系统 `screencapture -x /tmp/cc-haha-computer-use-check-5.png` 仍失败为 `could not create image from display`。当前机器继续无法执行点击、输入、文件选择器、托盘/通知等 GUI 验收。 -- 2026-06-01 macOS Keychain 弹窗修复:用户截图显示 Electron/Chromium 反复请求 `claude-code-desktop Safe Storage`。原因是 Chromium profile safe storage 会访问 macOS Keychain;本项目 OAuth token 已走 sidecar file-backed storage,不依赖 Chromium cookie/password store。Electron main 现在在启动早期对 macOS 设置 `use-mock-keychain`,并把 `ModelSelector` 的官方 OAuth status 从挂载即请求改为 runtime 下拉打开时按需请求一次,避免启动/重渲染时反复触发敏感存储路径。验证:`cd desktop && bun run test -- --run src/components/controls/ModelSelector.test.tsx electron/services/keychain.test.ts` 12 tests passed;`cd desktop && bun run check:electron` 80 tests passed 并完成 main/preload bundle。 -- 2026-06-01 Keychain 修复后 Computer Use 复测:`bun run electron:dev` 启动当前 Electron dev 壳,`get_app_state(Electron)` 成功返回 `localhost:1420/` 主界面和模型选择器 `gpt-5.5 Sub2API-ChatGPT`;截图/AX 树中未再出现 `claude-code-desktop Safe Storage` 钥匙串授权弹窗。随后已终止 dev Electron/Vite 进程。 -- 2026-06-01 Keychain 当前态复验:`cd desktop && bun run test -- --run electron/services/keychain.test.ts src/components/controls/ModelSelector.test.tsx` 通过 12 tests;`cd desktop && bun run check:electron` 通过 83 tests 并完成 Electron main/preload/preview-preload bundle。Computer Use `get_app_state(Electron)` 返回 `Claude Code Companion` 主窗口和 `localhost:1420/` renderer,未出现 Safe Storage 钥匙串授权弹窗。 -- 2026-06-01 原生附件文件选择补强:Electron dialog IPC 改为使用当前 `BrowserWindow` 作为 parent window,composer 打开原生选择器前先关闭 plus 菜单,避免无 owner sheet 返回后菜单状态残留。验证:`bun test electron/services/dialogs.test.ts` 3 tests passed;`bun run test -- --run src/lib/composerAttachments.test.ts src/components/chat/ChatInput.test.tsx src/pages/EmptySession.test.tsx` 33 tests passed;`bun run check:electron` 80 tests passed;Computer Use 在 Electron dev 壳中成功选择 `/Users/nanmi/cc-haha-cua-attachment-smoke.txt` 并显示附件 chip,未出现 Safe Storage 弹窗。 -- 2026-06-01 packaged updater 噪音修复:Electron updater service 在 packaged `app-update.yml` 缺失时直接返回“无更新”,并将 electron-updater 内置 logger 设为 `null`,避免目录包或 GitHub release 暂缺 `latest-mac.yml` 时在启动日志里打印 404 stack;非 metadata 错误仍会抛出。验证:`cd desktop && bun test electron/services/updater.test.ts electron/services/singleInstance.test.ts` 13 tests passed;`cd desktop && bun run check:electron` 89 tests passed。 -- 2026-06-01 single-instance 验证绕过:新增显式 `CC_HAHA_ELECTRON_DISABLE_SINGLE_INSTANCE_LOCK=1` 供本地验证启动同 bundle 的 worktree/canonical app,不影响默认生产单实例行为,避免需要杀掉用户正在使用的 `/Applications/Claude Code Haha.app`。 -- 2026-06-01 canonical macOS release Computer Use 复测:`SKIP_INSTALL=1 SIGN_BUILD=0 desktop/scripts/build-macos-arm64.sh` 通过默认 `dmg zip`,release package-smoke PASS;用隔离配置和 `CC_HAHA_ELECTRON_DISABLE_SINGLE_INSTANCE_LOCK=1` 启动 `desktop/build-artifacts/macos-arm64/Claude Code Haha.app`,server sidecar 启动,Computer Use `get_app_state` 成功读取 `Claude Code Companion` 主窗口和 packaged renderer。启动日志没有 `app-update.yml` 或 `latest-mac.yml` updater stack。 -- 2026-06-01 canonical packaged 系统交互复测:在同一个 `desktop/build-artifacts/macos-arm64/Claude Code Haha.app` 隔离实例中,Computer Use 确认项目选择器已选中系统原生目录选择结果 `/private/tmp/cc-haha-electron-real-fixed-sRFUeO/project`;点击 `打开终端` 后终端面板显示 `运行中 /bin/zsh / /tmp/cc-haha-canonical-cua-flow.xTxSmt/claude-config`,输入 `printf 'canonical-terminal-ok\n'; pwd` 后画面可见 `canonical-terminal-ok` 和实际工作目录输出。该轮未出现 `claude-code-desktop Safe Storage` 钥匙串弹窗。 -- 2026-06-01 canonical packaged Computer Use 设置流程复测:用隔离 `CLAUDE_CONFIG_DIR` 启动 `desktop/build-artifacts/macos-arm64/Claude Code Haha.app`,Computer Use 打开设置页后进入 `Computer Use` tab。首次状态请求曾因 30s timeout 显示 `Failed to check status`,点击 `重试` 后页面正常显示 Python 3.14.5、venv 未创建、依赖未安装。通过页面 `安装环境` 按钮完成真实 server/setup 链路,随后 UI 显示 venv 已就绪、依赖包已安装、辅助功能权限已授权、屏幕录制权限未授权;`curl /api/computer-use/status` 返回 `venv.created=true`、`dependencies.installed=true`、`permissions.accessibility=true`、`permissions.screenRecording=false`。点击 `打开屏幕录制设置` 后 Computer Use 成功读取 macOS 系统设置窗口 `录屏与系统录音`,并看到 `Claude Code Haha` 权限项。 -- 2026-06-01 canonical packaged Computer Use 授权应用复测:复用隔离 packaged app,Computer Use 在设置页搜索 `Terminal`,应用列表返回 `Terminal / com.apple.Terminal`;点击该项后 UI 显示 check 状态,隔离配置 `cc-haha/computer-use-config.json` 与 `GET /api/computer-use/authorized-apps` 均返回 `authorizedApps[0].bundleId=com.apple.Terminal`,`grantFlags.clipboardRead=true`、`grantFlags.clipboardWrite=true`、`grantFlags.systemKeyCombos=true`。该轮覆盖了真实 UI 点击、输入、滚动、应用枚举和授权配置持久化。 -- 2026-06-01 notarization 诊断复验:`bun test scripts/quality-gate/package-smoke/index.test.ts` 15 tests passed;`bun run test:package-smoke --platform macos --package-kind release --artifacts-dir desktop/build-artifacts/macos-arm64 --require-macos-gatekeeper` 预期失败,但输出现在同时包含 `spctl`、`codesign` 和 `notarization ticket validation` 诊断,当前 ad-hoc app 被分类为没有 stapled notarization ticket。 -- 2026-06-01 Gatekeeper 当前态复跑:`bun run test:package-smoke --platform macos --package-kind release --artifacts-dir desktop/build-artifacts/macos-arm64` 通过,确认 release artifact 结构、`latest-mac.yml` 引用 zip、`app-update.yml`、sidecar 和 `node-pty` 完整;追加 `--require-macos-gatekeeper` 后仍失败,`codesign` verify/details 通过但 `spctl` 返回 `bundle format unrecognized, invalid, or unsuitable`,`stapler` 返回 `does not have a ticket stapled to it`。package-smoke 现已在 `spctl` 报 `Too many open files` 时用 raised file descriptor limit 自动重试,避免该临时诊断掩盖真实 Gatekeeper 结果。release-ready 仍需要 Developer ID signing/notarization 或真实签名产物复验。 -- 2026-06-01 Gatekeeper 诊断补强后 Computer Use 复测:用隔离 `CLAUDE_CONFIG_DIR` 和 `--user-data-dir` 启动 `desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app`,Computer Use 成功读取 `Claude Code Companion` 主窗口和 packaged `file://.../app.asar/dist/index.html` renderer,证明 package-smoke 诊断补强未破坏 packaged app 运行路径。 -- 2026-06-01 release gate 口径收紧:`quality:gate --mode release` 的当前平台 `desktop-package-smoke:macos` lane 现在会追加 `--require-macos-gatekeeper`,与 GitHub release workflow 上传前 Gatekeeper 检查保持一致。验证:`bun test scripts/quality-gate/runner.test.ts scripts/quality-gate/package-smoke/index.test.ts scripts/pr/release-workflow.test.ts` 通过 39 tests;`bun run quality:gate --mode release --dry-run --only 'desktop-package-smoke:*'` 报告命令包含 `--require-macos-gatekeeper`;实际运行 `bun run quality:gate --mode release --only 'desktop-package-smoke:*'` 失败于 Gatekeeper,报告为 `artifacts/quality-runs/2026-06-01T12-16-06-442Z/report.md`。 -- 2026-06-01 当前 release provider smoke 复验:`bun run quality:gate --mode release --allow-live --only 'provider-smoke:*' --provider-model sub2api-chatgpt:main:sub2api-chatgpt-main` 通过,报告为 `artifacts/quality-runs/2026-06-01T12-18-47-144Z/report.md`;随后用 Computer Use 启动隔离 packaged app,成功读取 `Claude Code Companion` 主窗口和 packaged `file://.../app.asar/dist/index.html` renderer。 -- 2026-06-01 governance gate 复验:`bun run check:policy` 通过 77 tests,并执行 `check:quarantine`,覆盖 release workflow、quality gate runner、provider/desktop smoke、change policy、quality contract 和 quarantine governance。随后用 Computer Use 再次启动隔离 packaged app;首次读取遇到 ScreenCaptureKit stream error,但 System Events 显示同一 PID 有 `Claude Code Companion` 窗口,重试后 Computer Use 成功读取 packaged `file://.../app.asar/dist/index.html` renderer。 -- 2026-06-01 本地 `main` 同步后复验:已把本地 `main` 最近 10 个提交的非重叠改动应用到当前迁移工作区,并手动合并 `ChatInput.tsx` / `previewEvents.ts` / `previewEvents.test.ts` 中的 append prefill 与 Electron `desktopHost.preview` 改造。验证:`cd desktop && bun run test -- --run src/components/chat/ChatInput.test.tsx src/lib/previewEvents.test.ts` 通过 24 tests;`bun test src/cli/__tests__/structuredIO.test.ts src/services/api/withRetry.test.ts src/utils/__tests__/imageResizer.test.ts src/utils/shell/powershellDetection.test.ts src/server/__tests__/providers.test.ts src/server/__tests__/conversation-service.test.ts src/server/__tests__/conversation-attachments.test.ts src/server/__tests__/websocket-handler.test.ts` 通过 124 tests;`cd adapters && bun test feishu/__tests__/streaming-card.test.ts` 通过 36 tests;`cd desktop && bun run check:electron` 通过 94 tests;`CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron:package:dir` 重新生成 packaged dir;`bun run test:package-smoke --platform macos --package-kind dir --artifacts-dir desktop/build-artifacts/electron` 通过;Computer Use 成功读取新打包的 `desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app` 主窗口和 packaged renderer。 -- 2026-06-01 release update blockmap 验证补强:`package-smoke` 的 release 模式现在强制检查 macOS `.zip/.dmg.blockmap`、Windows `.exe.blockmap` 和 Linux `.AppImage.blockmap`,避免只有 channel metadata 和安装包但缺少 electron-updater 差分更新文件。验证:`bun test scripts/quality-gate/package-smoke/index.test.ts` 通过 17 tests;`bun run test:package-smoke --platform macos --package-kind release --artifacts-dir desktop/build-artifacts/macos-arm64` 通过,并确认当前 macOS release artifact set 含 `.dmg.blockmap` 和 `.zip.blockmap`。 -- 2026-06-01 packaged update smoke 补强:新增 `CC_HAHA_ELECTRON_UPDATE_SMOKE_VERSION` 驱动的 Electron main updater stub,仅在显式 smoke env 下启用;`app-update.yml` 缺失不会阻断该 smoke stub,生产 `electron-updater` 仍保持缺失 metadata 时 no-op。验证:`cd desktop && bun run check:electron` 通过 94 tests;`CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron:package:dir` 生成新的 packaged dir;复制为唯一 bundle id 的 smoke app 后,packaged renderer `file://.../app.asar/dist/index.html` 通过 preload 调用 `desktopHost.updates.check/download/prepareInstall/install/relaunch`,JSONL 记录 `check`、`download-start`、`download-finish`、`quit-and-install`。同轮 Computer Use `list_apps` 可见唯一 smoke app,但 `get_app_state` 对该 app 仍超时,故真实“点击安装”仍不能声明通过。 -- 2026-06-01 packaged window smoke 诊断:新增 `CC_HAHA_ELECTRON_WINDOW_SMOKE_LOG`,main process 在 `after-create`、`after-initial-show`、`did-finish-load` 和 `after-final-show` 写入窗口快照。早前同一 packaged dir 启动日志显示 Electron `BrowserWindow` 已 `visible:true`,bounds 为 `1280x820`,URL 为 packaged `file://.../app.asar/dist/index.html`,但 System Events 曾读取到 `windows count=0` 且 Computer Use `get_app_state` 超时;随后用完整 `.app` 路径复测,Computer Use 成功读取 `Claude Code Companion` 主窗口和 packaged renderer,System Events 返回 `front=true count=1 names=Claude Code Companion`。当前结论是之前的卡点在 macOS AX/窗口捕获状态波动,不是 renderer、server、更新 preload smoke 或关闭智能体流程。 -- 2026-06-01 packaged notification click 复测:直接执行 app binary 时 smoke env 未进入最终 Electron app 进程,改用 `launchctl setenv` + `open -n ... --args --user-data-dir=` 后,packaged app 的 `CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_LOG` 正常写入 `scheduled` 和 `sent:true`。Computer Use 对 `/System/Library/CoreServices/NotificationCenter.app` 和 `SystemUIServer` 仍返回 `timeoutReached`,Finder 状态读取返回 ScreenCaptureKit stream error,JSONL 未出现 `action`;因此“点击通知回到目标 session”仍保持未完成。 -- 2026-06-01 通知点击再次复测:用 `launchctl setenv` 启动 worktree packaged app 后,JSONL 正常写入 `scheduled` 和 `sent:true`,但 Computer Use 对该 worktree app 连续返回 ScreenCaptureKit stream error;`list_apps` 能看到 worktree app running。随后复制当前 packaged app 为唯一 bundle id smoke app 并 ad-hoc 重签名,LaunchServices 能注册该 smoke app,但应用立即退出,系统日志显示 appDeath;因此本轮仍不能把真实 OS 通知点击回跳勾选为完成。 -- 2026-06-01 当前全量桌面/服务端门禁复验:`cd desktop && bun run check:desktop` 通过 desktop lint、151 个 Vitest 文件、1199 个测试和 production build;`bun run check:server` 通过 88 files / 969 tests。通知真实点击、signed/notarized Gatekeeper、Windows/Linux 实机 release smoke 仍保持未完成项。 -- 2026-06-01 通知点击 renderer ack 补强:新增 Electron `desktop:notification:action-ack` IPC,renderer 处理通知 action 并打开目标 session 后,会把 `{ target, payload }` 写入 `CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_LOG` 的 `renderer_ack` 事件。该补强不模拟 OS 点击,只让后续可点击通知的 macOS/Windows runner 能证明 main action 已进入 UI 导航链路。验证:focused notification/desktopHost tests 42 passed;`cd desktop && bun run check:electron` 通过 97 tests;`CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron:package:dir` 重新生成 packaged dir;`bun run test:package-smoke --platform macos --package-kind dir --artifacts-dir desktop/build-artifacts/electron` PASS。 -- 2026-06-01 renderer ack 补强后 Computer Use 复测:用隔离 `CLAUDE_CONFIG_DIR`、隔离 `--user-data-dir` 和 notification smoke env 启动当前 worktree packaged app 时,Computer Use 首次 `get_app_state` 返回 `timeoutReached`,直接执行也曾退出 137。随后用隔离 `CLAUDE_CONFIG_DIR` / `--user-data-dir` 重新直接运行同一 `.app` 后保持运行,Computer Use 成功读取 `Claude Code Companion` 主窗口,renderer URL 为当前 worktree 的 packaged `file://.../app.asar/dist/index.html`;点击设置入口后进入 packaged 设置页。该轮确认当前 Electron build 可被 Computer Use 读取和交互;`spctl -a -t execute desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app` 仍返回 `bundle format unrecognized, invalid, or unsuitable`,`codesign --verify --deep --strict` 通过,因此真实 OS 通知点击和 signed/notarized launch 仍未完成。 -- 2026-06-01 packaged synthetic notification action 复测:新增显式 smoke-only 开关 `CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_TRIGGER_ACTION=1`,只在测试环境中由 main process 对刚发送的通知 action 触发同一条 renderer ack 链路。用 `launchctl setenv` + `open -n desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app --args --user-data-dir=` 启动当前 worktree packaged app 后,Computer Use 成功读取 `Claude Code Companion`,renderer URL 为 `file:///Users/nanmi/.codex/worktrees/2392/claude-code-haha/desktop/build-artifacts/electron/mac-arm64/Claude%20Code%20Haha.app/Contents/Resources/app.asar/dist/index.html`;JSONL 记录 `scheduled`、`sent:true`、`synthetic_action` 和 `renderer_ack`。该 synthetic action 仅证明 packaged main -> renderer action -> ack 链路,不替代真实 OS 通知点击;真实“点击通知后回到目标 session”仍未完成。 - ---- - -## Review 收口记录 - -### 2026-05-31 SubAgent Review - -- [x] Security Review:收紧 packaged renderer entry,packaged app 不再接受 `ELECTRON_RENDERER_URL`;dev 模式只允许 local HTTP renderer。 -- [x] Security Review:preview bridge 增加大小上限、事件 allowlist、字段 schema、`data:image/*` 校验,外部页面不能向 host 注入无限大或未知事件。 -- [x] Security Review:packaged macOS `node-pty` runtime cache 增加 manifest 校验、tamper 后重建、cache/helper 权限收紧。 -- [x] Code Review:macOS “打开通知设置”改走 native command 和 allowlisted system settings URL。 -- [x] Code Review:Electron `preview.message()` 改为转发到 injected preview bridge,不再 silent no-op。 -- [x] Code Review:dev/release desktop workflows 在 Electron builder 后运行 `test:package-smoke`,避免上传缺少 sidecar 或 `node-pty` 关键资源的产物。 -- [x] Verification Review:新增 `docs/desktop/09-electron-migration-validation-checklist.md`,明确 `package-smoke` 只是结构预检,最终完成仍需要真实 packaged app + Computer Use/实机 smoke。 -- [x] Release gate follow-up:`desktop-smoke:agent-browser-chat:*` 保留为 baseline/browser confidence lane,不再作为 release 必需 lane;release mode 新增 `desktop-package-smoke:`,用当前平台 Electron packaged artifact 结构检查配合 Computer Use 真实 app 验收,避免用浏览器/Vite open 结果代表桌面构建可用性。 - ---- - -## 最终完成定义 - -- [x] `desktop/src` 生产代码不再直接依赖 `@tauri-apps/*`。 -- [x] Electron main/preload IPC 有 capability registry 和 payload validation。 -- [x] 本地 Bun server + REST/WebSocket contract 保留,renderer 没有把 session/chat/workspace/team 主链改成 IPC。 -- [x] 自动更新、通知、文件选择、外链打开、窗口/托盘/菜单、sidecar、terminal、preview、app mode、zoom 全部有测试或 smoke 证据。 -- [ ] macOS、Windows、Linux 发布产物可构建并启动。 -- [x] `bun run verify` 通过。 -- [x] release gate 通过,或 live provider blocker 被明确记录。 -- [ ] Computer Use 对真实打包 app 完成全面测试。 - -**最终定义复核(2026-06-01):** - -- 生产 Tauri 边界扫描通过:`rg -n "@tauri-apps|__TAURI_INTERNALS__|window\\.__TAURI__|from ['\\\"]@tauri" desktop/src --glob '!**/*.test.ts' --glob '!**/*.test.tsx' --glob '!**/lib/desktopHost/**' --glob '!src-tauri/**'` 无输出。 -- Electron IPC 复核通过:`desktop/electron/ipc/capabilities.ts` 为每个 `ELECTRON_IPC_CHANNELS` channel 定义 validator,`desktop/electron/main.ts` 的 `registerHandler()` 在进入 handler 前执行 `isElectronIpcChannel()` 和 `validateElectronIpcPayload()`;renderer `electronHost` 也在 preload bridge 调用前执行相同 payload validation。 -- REST/WebSocket 主干复核通过:session/workspace/team 仍在 `desktop/src/api/sessions.ts`、`desktop/src/api/teams.ts`、`desktop/src/api/websocket.ts` 调用 `/api/**` 和 `/ws/:sessionId`;Electron IPC 命中仅覆盖 host/system 能力、terminal 和 preview,不承载 chat/session/workspace/team 主业务。 -- 系统能力证据复核通过:Electron service/API tests 覆盖 updater、notifications、dialogs、shell、windows/tray/menu/single-instance、sidecar、terminal、preview、app mode、zoom;macOS `.dmg/.zip/latest-mac.yml` canonical release artifact set 已通过 `package-smoke`,canonical `.app` 已通过 Computer Use 窗口可见性 smoke;完整完成仍受 signed/notarized Gatekeeper、通知真实点击、Windows/Linux 实机发布格式 smoke 阻塞。 diff --git a/docs/desktop/09-electron-migration-validation-checklist.md b/docs/desktop/09-electron-migration-validation-checklist.md deleted file mode 100644 index 8838eb36..00000000 --- a/docs/desktop/09-electron-migration-validation-checklist.md +++ /dev/null @@ -1,172 +0,0 @@ -# Electron 迁移验证清单 - -> 用于迁移收口,不替代 `docs/desktop/08-electron-migration-tasks.md`。`package-smoke` 只验证包结构和关键资源,不声明 GUI 启动或交互成功。 - -交互式执行版见 [`electron-migration-qa-checklist.html`](/desktop/electron-migration-qa-checklist.html),可在本地浏览器直接打开并用 `localStorage` 保存勾选进度与问题记录。 - -## 已有自动化证据 - -- [x] `bun run check:native`:sidecar build、Electron TypeScript、83 个 Electron IPC/service 测试、main/preload/preview-preload bundle、Electron `--dir` 打包和当前平台 package-smoke 均通过。 -- [x] `bun run check:desktop`:desktop lint、151 个 Vitest 文件、1199 个测试、renderer production build 均通过。 -- [x] `bun test scripts/quality-gate/package-smoke/index.test.ts scripts/pr/release-workflow.test.ts`:package-smoke harness 与 release workflow 静态回归通过。 -- [x] `bun test scripts/quality-gate/package-smoke/index.test.ts scripts/pr/release-workflow.test.ts scripts/quality-gate/runner.test.ts`:25 tests passed;覆盖 release lane、显式 GitHub publish 配置和 `latest-mac.yml` 引用真实 artifact 的 package-smoke 回归。 -- [x] `bun test scripts/quality-gate/package-smoke/index.test.ts scripts/pr/release-workflow.test.ts scripts/release-update-metadata.test.ts`:26 tests passed;覆盖 Windows canonical output、Linux x64/arm64 `latest-linux*.yml` update metadata、release workflow Gatekeeper/signing preflight 和 post-matrix metadata republish。 -- [x] `cd desktop && CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron:package:dir`:macOS unpacked `.app` 可打包。 -- [x] `bun run test:package-smoke --platform macos`:macOS `.app` 结构、`app.asar`、unpacked sidecar、unpacked `node-pty` native module 和 `spawn-helper` 通过静态检查。 -- [x] macOS `.zip/latest-mac.yml` 发布产物复验:`MAC_TARGETS=zip SKIP_INSTALL=1 SIGN_BUILD=0 desktop/scripts/build-macos-arm64.sh` 通过,canonical 输出包含 `.app`、`.zip`、`.zip.blockmap` 和 `latest-mac.yml`,脚本末尾自动运行 canonical package-smoke 并 PASS。 -- [x] macOS directory-only native/package 当前态:`bun run check:native` 通过,覆盖 sidecar 构建/ad-hoc signing、Electron 83 tests、production build、`electron-builder --dir` 和 `package-smoke --package-kind dir --artifacts-dir desktop/build-artifacts/electron`。该项只证明 unpacked bundle 结构和关键资源,不证明 `.dmg`、Gatekeeper 或 signed release launch。 -- [x] macOS `.dmg/.zip/latest-mac.yml` 当前态复验:`SKIP_INSTALL=1 SIGN_BUILD=0 desktop/scripts/build-macos-arm64.sh` 已通过默认 `dmg zip` target;canonical `desktop/build-artifacts/macos-arm64` 包含 `.app`、`.dmg`、`.dmg.blockmap`、`.zip`、`.zip.blockmap` 和 `latest-mac.yml`,脚本末尾 release package-smoke PASS。 -- [x] macOS canonical packaged launch smoke:使用隔离 `CLAUDE_CONFIG_DIR`、隔离 `--user-data-dir` 和 `CC_HAHA_ELECTRON_DISABLE_SINGLE_INSTANCE_LOCK=1` 启动 `desktop/build-artifacts/macos-arm64/Claude Code Haha.app`,server sidecar 启动于临时端口,Computer Use `get_app_state` 成功读取 `Claude Code Companion` 主窗口和 packaged `file://.../app.asar/dist/index.html` renderer。 -- [x] macOS canonical packaged system interaction smoke:同一 canonical `.app` 隔离实例中,Computer Use 确认项目选择器显示系统目录选择结果 `/private/tmp/cc-haha-electron-real-fixed-sRFUeO/project`;打开终端后执行 `printf 'canonical-terminal-ok\n'; pwd`,画面可见 `canonical-terminal-ok` 与实际工作目录输出,且未出现 Safe Storage 钥匙串弹窗。 -- [x] 本地 `main` 同步后 targeted checks 通过:`cd desktop && bun run test -- src/components/workspace/WorkspaceFileOpenWith.test.tsx src/lib/previewEvents.test.ts electron/services/updater.test.ts`、`cd desktop && bun run check:electron`、`bun test src/server/middleware/cors.test.ts src/server/__tests__/h5-access-policy.test.ts`。 - -## Review 已收口 - -- [x] Packaged app 不再信任 `ELECTRON_RENDERER_URL`;只有 dev 模式允许 local HTTP renderer。 -- [x] Preview external page bridge 增加消息大小上限、事件类型 allowlist、字段 schema 和 data URL 校验。 -- [x] Packaged macOS `node-pty` runtime cache 会从 bundle manifest 校验,发现 tamper 后重建,cache 权限收紧。 -- [x] macOS 通知设置入口改走 native command + allowlisted system settings URL。 -- [x] Electron `preview.message()` 不再 silent no-op,会转发到 injected preview bridge。 -- [x] Dev/release desktop workflows 在 Electron builder 后运行 `test:package-smoke`,阻止缺失关键 runtime 资源的产物上传/发布。 -- [x] Packaged Electron `file://` renderer origin 已纳入 server CORS/H5 token local-origin policy,避免真实打包 app 的 chat WebSocket 在 H5 token mode 下被拒绝。 -- [x] Electron Builder publish 配置不再依赖 git remote autodetection;`artifactName` 改为无空格稳定文件名,package-smoke 会检查 `latest*.yml` 引用的本地 artifact 是否存在。 - -## 必跑本地门禁 - -- [x] `bun run check:native` -- [x] `bun run check:desktop` -- [x] `bun run check:docs` -- [x] `bun run verify`:最新为 `artifacts/quality-runs/2026-05-31T21-42-57-279Z/report.md`,`passed=9 failed=0 skipped=1`。 -- [x] `bun run check:coverage`:最新为 `artifacts/coverage/2026-05-31T21-46-15-873Z/coverage-report.md`,`passed=5 failed=0`。 -- [x] 同步本地 `main`、`file://` CORS/H5 修复和 Electron 迁移当前态后复跑 `bun run check:server`:88 files / 969 tests passed。 -- [x] 同步本地 `main` 与 updater no-op 修复后复跑 `cd desktop && bun run check:electron`:16 files / 76 tests passed。 -- [x] `bun run test:package-smoke --platform macos` -- [x] `cd desktop && bun run test -- --run scripts/dev-launcher.test.ts && bun run check:electron`:新增 Electron dev launcher 代理绕过回归,最新 Electron checks 为 17 files / 79 tests passed。 -- [x] `bun run test:package-smoke --platform macos`:结构检查仍通过,并明确提示该命令不做 Gatekeeper launch approval。 -- [x] `cd desktop && bun run test -- src/lib/desktopRuntime.test.ts src/components/shared/UpdateChecker.test.tsx src/lib/composerAttachments.test.ts src/components/chat/ChatInput.test.tsx src/pages/EmptySession.test.tsx src/components/layout/AppShell.test.tsx src/components/controls/PermissionModeSelector.test.tsx src/components/shared/RepositoryLaunchControls.test.tsx`:8 files / 75 tests passed;覆盖 Electron desktop runtime 判断、更新提示、native attachment picker 和移动布局分支。 -- [x] `cd desktop && bun run check:electron`:16 files / 77 tests passed;覆盖 Electron updater proxy contract、IPC payload validation、main/preload/preview-preload bundle。 -- [x] `bun test scripts/quality-gate/package-smoke/index.test.ts`:7 tests passed;发布型 macOS 包缺失 resources/app-update.yml 会失败,纯 `--dir` 开发包不强制该文件。 -- [x] `bun test scripts/quality-gate/package-smoke/index.test.ts scripts/pr/quality-contract.test.ts`:12 tests passed;覆盖 host platform 到 package-smoke platform 的映射,并锁定 `check:native` 必须包含 `electron:package:dir` 与 `test:package-smoke:current`。 -- [x] `bun run check:native`:最新复跑完成 sidecar build、Electron 77 tests、Electron `--dir` package,并执行 `bun run test:package-smoke:current` -> macOS `.app` 结构检查 PASS;notes 明确当前纯 `--dir` artifact set 不强制 `app-update.yml`,也不做 GUI/Gatekeeper launch approval。 -- [x] Linux `electron-builder --dir` package-smoke 回归:Linux 纯 `linux-unpacked` 开发目录包会检查 `app.asar`、unpacked sidecar 和 `node-pty` 后通过,不要求 AppImage/deb;发布型 AppImage/deb artifact set 仍要求 update metadata 和 `app-update.yml`。 -- [x] PR native workflow Electron 化:`desktop-native-checks` 不再安装 WebKitGTK/Rust/Rust cache,`build-sidecars` 不再用 `rustc -vV` 推导 host triple;`check:native` 只依赖 Bun/Electron sidecar/package-smoke 链路。 -- [x] package-smoke 显式包类型:`check:native` 通过 `--package-kind dir --artifacts-dir desktop/build-artifacts/electron` 验开发目录包;dev/release workflows 和平台构建脚本通过 `--package-kind release --artifacts-dir <精确输出目录>` 验发布包,避免旧 artifact 混入。 -- [x] release gate 验包收窄:`quality:gate --mode release` 现在包含 PR checks,并使用当前平台 canonical release dir 运行 `test:package-smoke --package-kind release --artifacts-dir desktop/build-artifacts/`,不再把 `desktop/build-artifacts/electron` 的开发目录包当 release 证据;macOS release lane 还会追加 `--require-macos-gatekeeper`,与 release workflow 上传前 Gatekeeper 检查保持一致。 -- [x] 当前平台 canonical release package-smoke 复验:`bun run test:package-smoke --platform macos --package-kind release --artifacts-dir desktop/build-artifacts/macos-arm64` 通过,确认 canonical zip/update metadata/app-update.yml/sidecar/node-pty 结构仍完整;该命令仍不声明 GUI/Gatekeeper launch success。 -- [x] release workflow preflight:GitHub tag/workflow_dispatch 打包矩阵前先运行 `bun run verify`,避免 tag release 绕过 PR-quality 基线。 -- [x] signing/notarization secrets preflight:独立非 matrix `signing-preflight` job 在打包矩阵前检查 Developer ID、notarization 和 Windows signing secrets;缺失时 GitHub Actions 直接报错并阻止所有平台 artifact 上传,不再等到 Electron Builder/Gatekeeper 阶段或发布 partial release 后才失败。 -- [x] 多架构 update metadata 上传冲突规避:release workflow 上传前将 `latest*.yml` 重命名为带 matrix label 的唯一 asset,避免 GitHub Release 中 `latest-mac.yml/latest-linux.yml` 同名覆盖或上传失败。 -- [x] 多架构标准 updater metadata 合并:`scripts/release-update-metadata.ts` 会在 matrix build 全部通过后重新发布标准 channel metadata;macOS 合并 x64/arm64 entries 到 `latest-mac.yml`,Linux 恢复 `latest-linux.yml` / `latest-linux-arm64.yml`,Windows 恢复 `latest.yml`。完整自动更新 release 仍需在 signed/notarized artifact 上复验。 -- [x] `MAC_TARGETS=zip SKIP_INSTALL=1 SIGN_BUILD=0 desktop/scripts/build-macos-arm64.sh`:canonical macOS zip/update artifact set 通过;脚本自动执行 `bun run test:package-smoke --platform macos --package-kind release --artifacts-dir desktop/build-artifacts/macos-arm64`,确认 `latest-mac.yml` 引用真实 zip,安装后 resources 包含 `app-update.yml`。 -- [x] `bun run quality:gate --mode baseline --allow-live --only 'provider-smoke:*' --provider-model sub2api-chatgpt:main:sub2api-chatgpt-main`:`artifacts/quality-runs/2026-05-31T21-28-07-154Z/report.md`,`passed=1 failed=0 skipped=0`,直接复用本机 cc-haha provider selector。 -- [x] 当前 release provider smoke 复验:`bun run quality:gate --mode release --allow-live --only 'provider-smoke:*' --provider-model sub2api-chatgpt:main:sub2api-chatgpt-main` 通过,报告为 `artifacts/quality-runs/2026-06-01T12-18-47-144Z/report.md`,`passed=1 failed=0 skipped=0`。 -- [x] `cd desktop && bun test electron/services/updater.test.ts`:10 tests passed;覆盖 unpacked `app-update.yml` 缺失、packaged update config 缺失时不调用 updater、GitHub release 缺少 `latest-mac.yml` 时降级为无更新、关闭 electron-updater 内置 logger,以及非 metadata updater 错误继续抛出。 -- [x] `cd desktop && bun run check:electron`:最新 97 tests passed;覆盖通知 smoke `close/failed` lifecycle JSONL、synthetic action + renderer ack JSONL、updater missing channel metadata 修复、Keychain guard、single-instance validation bypass、update smoke stub、window smoke 诊断和 Electron main/preload/preview-preload bundle。 -- [ ] `bun run test:package-smoke --platform macos --package-kind release --artifacts-dir desktop/build-artifacts/macos-arm64 --require-macos-gatekeeper`:当前失败;非 Gatekeeper 结构检查 PASS,包含 `latest-mac.yml` 引用 zip、安装后 `app-update.yml`、sidecar 和 `node-pty`。追加 Gatekeeper 后 `codesign` verify/details 通过,但 `spctl` 返回 `bundle format unrecognized, invalid, or unsuitable`,`stapler` 返回 `does not have a ticket stapled to it`。package-smoke 现已在 `spctl` 报 `Too many open files` 时用 raised file descriptor limit 自动重试,避免该临时诊断掩盖真实 Gatekeeper 结果。release-ready 需要 Developer ID signing/notarization 或在真实签名产物上复验。 -- [x] Gatekeeper 失败诊断:`--require-macos-gatekeeper` 现在在 `spctl` 失败时记录 `codesign --verify --deep --strict --verbose=2`、`codesign -dv --verbose=4` 和 `xcrun stapler validate` 摘要,便于 signed/notarized artifact 在 CI/release runner 上定位签名链、bundle 格式或 notarization ticket 问题。 -- [x] Gatekeeper 诊断复验:`bun run test:package-smoke --platform macos --package-kind release --artifacts-dir desktop/build-artifacts/macos-arm64 --require-macos-gatekeeper` 预期失败;输出中包含 `spctl Gatekeeper assessment exited with status 1`、`codesign verification exited with status 0`、bundle identifier/signature detail 摘要,以及 `notarization ticket validation exited with status 65` / `does not have a ticket stapled to it`。 -- [x] release workflow macOS 上传前强制运行 `bun run test:package-smoke --platform macos --package-kind release --artifacts-dir desktop/build-artifacts/electron --require-macos-gatekeeper`;开发构建仍只跑结构验包,因为 dev workflow 明确禁用签名自动发现。 -- [x] Security/Runtime Review 修复:Electron renderer 不再有 `previewEval` 任意 JS 注入 IPC;预览截图/元素选择走结构化 `preview.message`;`shell.openPath` 拒绝 app bundle、脚本/安装器/Windows 可执行扩展和 POSIX executable 文件;Electron `windowControls=false`,Windows 暂不显示自定义 window chrome。 -- [x] 历史 `bun run quality:gate --mode release --allow-live --provider-model sub2api-chatgpt:main:sub2api-chatgpt-main`:`artifacts/quality-runs/2026-05-31T20-29-45-758Z/report.md`,`passed=15 failed=0 skipped=0`。SubAgent Review 后 release gate 已收窄为 canonical release artifact 验包;该历史报告不能再作为当前 release-ready 证据。 -- [ ] 重新运行当前 `bun run quality:gate --mode release --allow-live --provider-model sub2api-chatgpt:main:sub2api-chatgpt-main`:需要 signed/notarized artifact、canonical release metadata 和可用 Computer Use 桌面会话。 -- [x] `bun run quality:gate --mode release --dry-run --only 'desktop-package-smoke:*'`:报告 `artifacts/quality-runs/2026-06-01T12-15-48-692Z/report.md`,确认 macOS release package-smoke 命令包含 `--require-macos-gatekeeper`。 -- [x] `bun run quality:gate --mode release --only 'desktop-package-smoke:*'`:报告 `artifacts/quality-runs/2026-06-01T12-16-06-442Z/report.md`,按预期失败于 Gatekeeper;结构检查均通过,`spctl` 返回 `bundle format unrecognized, invalid, or unsuitable`,`stapler` 返回 `does not have a ticket stapled to it`。 -- [x] `bun run check:policy`:77 tests passed,并执行 `check:quarantine`;覆盖 release workflow、quality gate runner、provider/desktop smoke、change policy、quality contract 和 quarantine governance。 -- [x] 本地 `main` 同步后 focused regression:已应用 `main` 最近 10 个提交的非重叠改动,并手动合并 `ChatInput.tsx` / `previewEvents.ts` / `previewEvents.test.ts` 冲突面。验证:desktop ChatInput/previewEvents 24 tests passed;server/CLI/image/powershell/provider 124 tests passed;Feishu streaming card 36 tests passed;`cd desktop && bun run check:electron` 94 tests passed。 -- [x] 本地 `main` 同步后重新打包:`CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron:package:dir` 完成当前源码 packaged dir;`bun run test:package-smoke --platform macos --package-kind dir --artifacts-dir desktop/build-artifacts/electron` PASS。 -- [x] release update blockmap 验证补强:`bun test scripts/quality-gate/package-smoke/index.test.ts` 通过 17 tests;`bun run test:package-smoke --platform macos --package-kind release --artifacts-dir desktop/build-artifacts/macos-arm64` PASS,并确认当前 macOS release artifact set 包含 `.dmg.blockmap` 和 `.zip.blockmap`。Windows `.exe.blockmap` 与 Linux `.AppImage.blockmap` 已由 fixture 回归覆盖,仍需实机 release artifact smoke。 -- [x] 通知点击 renderer ack 证据补强:Electron renderer 处理通知 action 并打开目标 session 后,会通过 `desktop:notification:action-ack` 回写 `renderer_ack` 到 `CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_LOG`,用于后续可点击 OS 通知环境证明 main action 已进入 UI 导航链路。验证:focused notification/desktopHost tests 42 passed;`cd desktop && bun run check:electron` 97 tests passed。 -- [x] `git diff --check` - -## macOS Computer Use Smoke - -- 2026-06-01 复测阻塞:早期 Computer Use 工具层对 `list_apps` 和 `get_app_state(Finder)` 返回 `NSOSStatusErrorDomain Code=-600 procNotFound`;后续 `list_apps` 恢复,但 `get_app_state(Electron)` 仍超时,`get_app_state(Finder)` 为 `cgWindowNotFound`,同时系统 `screencapture` 返回 `could not create image from display`。当前阻塞已定位为本机会话显示/截图能力不可用,不能把它解释为 Electron app 单点故障。 -- 2026-06-01 continuation 复测:Computer Use `list_apps` 可列出运行中应用,但 `get_app_state(Finder)` 仍为 `cgWindowNotFound`;系统 `screencapture` 仍返回 `could not create image from display`。当前仍无法进行点击、输入、选择文件等 Computer Use GUI 操作。 -- 2026-06-01 Electron dev smoke:本机代理环境会让 `electron:dev` 的 renderer 等待请求命中 502;已把默认 renderer URL 改为 `http://localhost:1420`,并对当前进程、Vite 子进程和 Electron 子进程补齐 `NO_PROXY/no_proxy=localhost,127.0.0.1,::1`。重启后 Electron dev 壳可启动 sidecar 和 renderer。 -- 2026-06-01 窗口可见性修正:Electron main 在创建主窗口后立即 `show/focus`,renderer load 完成后再补一次,避免隐藏窗口等待 renderer load 时被 Computer Use/系统自动化看成无窗口。 -- 2026-06-01 packaged launch policy:`open -n desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app` 被 macOS `AppleSystemPolicy` 直接终止;ad-hoc 和本机 Apple Development 重新签名的临时副本均未通过 `spctl`。因此当前 unpacked `.app` 只能证明结构完整,不能声明 release launchable。 -- 2026-06-01 DMG builder policy:清理所有旧 `Claude Code Haha` disk image 挂载后,zip-only Electron Builder 可成功产出 update metadata;DMG target 仍在本机 `hdiutil create` 超时,且失败后的临时读写 disk image 无法正常 detach。该项保持 macOS runner/DiskImages blocker。 -- 2026-06-01 final-definition 复测:Computer Use `list_apps` 仍可列出运行中应用,`get_app_state(Finder)` 仍返回 `cgWindowNotFound`;系统 `screencapture -x` 仍返回 `could not create image from display`。当前机器仍不能作为 Computer Use GUI 验收环境。 -- 2026-06-01 cross-platform-script 复测:Computer Use `list_apps` 可列出应用且包含 `/Applications/Claude Code Haha.app`,但 `get_app_state(Finder)` 仍返回 `cgWindowNotFound`,`get_app_state(Claude Code Haha)` 返回 `remoteConnection`,系统 `screencapture -x /tmp/cc-haha-computer-use-check.png` 仍失败为 `could not create image from display`。当前机器继续不能完成真实 GUI 操作验收。 -- 2026-06-01 package-kind 复测:Computer Use `list_apps` 可列出运行中的 `/Applications/Claude Code Haha.app`,但 `get_app_state(Claude Code Haha)` 和 `get_app_state(Finder)` 均返回 `cgWindowNotFound`;系统 `screencapture -x /tmp/cc-haha-computer-use-check-2.png` 仍失败为 `could not create image from display`。当前机器继续不能完成点击、输入、系统文件选择器、托盘/通知等 GUI 验收。 -- 2026-06-01 Gatekeeper 诊断后复测:Computer Use `list_apps` 可列出运行中的 `/Applications/Claude Code Haha.app`,但 `get_app_state(Claude Code Haha)` 和 `get_app_state(Finder)` 均返回 `cgWindowNotFound`;系统 `screencapture -x /tmp/cc-haha-computer-use-check-3.png` 仍失败为 `could not create image from display`。当前机器仍不是可用的 Computer Use GUI 验收环境。 -- 2026-06-01 release metadata 合并后复测:Computer Use `list_apps` 仍可列出运行中的 `/Applications/Claude Code Haha.app`,但 `get_app_state(Claude Code Haha)` 和 `get_app_state(Finder)` 仍返回 `cgWindowNotFound`;系统 `screencapture -x /tmp/cc-haha-computer-use-check-4.png` 仍失败为 `could not create image from display`。当前机器仍不能执行真实 GUI 操作验收。 -- 2026-06-01 mock update feed UI 补强后复测:Computer Use `list_apps` 仍可列出运行中的 `/Applications/Claude Code Haha.app`,但 `get_app_state(Claude Code Haha)` 和 `get_app_state(Finder)` 仍返回 `cgWindowNotFound`;系统 `screencapture -x /tmp/cc-haha-computer-use-check-5.png` 仍失败为 `could not create image from display`。当前机器仍不能执行真实 GUI 操作验收。 -- 2026-06-01 Keychain prompt mitigation:Electron main 在 macOS 启动早期启用 Chromium `use-mock-keychain`,避免 `claude-code-desktop Safe Storage` 反复弹登录钥匙串授权;`ModelSelector` 不再挂载即请求 Claude/OpenAI 官方 OAuth status,而是 runtime 下拉打开时按需请求一次。验证:focused ModelSelector/keychain tests 12 passed,`check:electron` 80 passed。 -- 2026-06-01 Keychain prompt Computer Use 复测:当前 Electron dev 壳启动后 `get_app_state(Electron)` 成功返回 `localhost:1420/` 主界面;Computer Use 截图和 AX 树没有出现 Safe Storage 钥匙串授权弹窗。 -- 2026-06-01 Keychain prompt 当前态复验:focused ModelSelector/keychain tests 12 passed;`check:electron` 83 passed;Computer Use `get_app_state(Electron)` 返回 `Claude Code Companion` 主窗口和 `localhost:1420/` renderer,未出现 Safe Storage 钥匙串授权弹窗。 -- 2026-06-01 附件选择 Computer Use 复测:当前 Electron dev 壳中点击 composer `添加文件或图片`,macOS 原生 `打开` 面板可用;选择 `/Users/nanmi/cc-haha-cua-attachment-smoke.txt` 后回到 composer,并显示 `cc-haha-cua-attachment-smoke.txt` 附件 chip,未出现 Safe Storage 钥匙串弹窗。 -- 2026-06-01 Python 路径选择 Computer Use 复测:当前 Electron dev 壳中点击 Computer Use 设置里的 `选择`,macOS 原生 `选择 Python 解释器` 面板可用;选择 `/Users/nanmi/cc-haha-cua-python3-smoke` 后设置页保存并解析到 `/opt/homebrew/Cellar/python@3.14/3.14.5/Frameworks/Python.framework/Versions/3.14/bin/python3.14`;随后恢复自动检测,`/api/computer-use/authorized-apps` 返回 `pythonPath: null`。 -- 2026-06-01 外链 Computer Use 复测:当前 Electron dev 壳中点击关于页 GitHub 项目卡片后,系统浏览器 Google Chrome 被拉到前台,地址栏为 `github.com/NanmiCoder/cc-haha`;Electron renderer 仍停留在 `localhost:1420/`,未在应用内导航外链。 -- 2026-06-01 窗口关闭/恢复 Computer Use 复测:当前 Electron dev 壳中点击 macOS 关闭按钮后,Electron 进程仍在 Computer Use `list_apps` 中保持 running 且主窗口消失;通过 macOS app activation 路径重新激活同一 Electron app 后,Computer Use 再次看到 `Claude Code Companion` 主窗口和 `localhost:1420/` renderer。Computer Use 当前不能稳定枚举 Dock,因此本轮验证的是与 Dock 点击等价的 app activation/show 主窗口路径。 -- 2026-06-01 通知点击 smoke hook:新增显式环境变量 `CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_SESSION_ID=`,用于让 Electron main process 发送带 session target 的真实 Electron 通知;focused tests 覆盖默认不发送、target payload 透传和窗口恢复链路。真实 OS 点击复测时,Computer Use 对 Notification Center/SystemUIServer 均超时,且 `get_app_state(Electron)` 本身会激活 app,因此不能作为点击通知证据;该项仍保持未勾选。 -- 2026-06-01 通知点击二次复测:用 `CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_DELAY_MS=30000` 启动 Electron dev 壳,Computer Use 先确认 `localhost:1420/` 主窗口可见,再点击 macOS close button;Electron 随后无主窗口,符合隐藏到后台预期。等待通知触发后,Computer Use 对 `SystemUIServer` 和 `/System/Library/CoreServices/NotificationCenter.app` 仍返回 `timeoutReached`,前台 Chrome 截图/AX 树也未出现可点击通知横幅,因此仍不能证明真实 OS 通知点击回跳。 -- 2026-06-01 packaged 通知 JSONL smoke:新增 `CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_LOG=` 后重新打包 packaged app;隔离配置启动后 Computer Use 确认 packaged `file://.../app.asar/dist/index.html` 主窗口可见,JSONL 记录 `scheduled` 与 `sent:true`。Computer Use 对 `SystemUIServer` 和 Notification Center 仍 `timeoutReached`,JSONL 未出现 `action`,因此仍不能勾选“点击通知后回到目标 session”。 -- 2026-06-01 packaged 通知点击当前态复测:canonical packaged app 使用 `CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_SESSION_ID=notification-click-smoke-session`、`CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_DELAY_MS=8000` 和 JSONL log 启动;Computer Use 关闭主窗口后 app 留在后台,JSONL 记录 `scheduled` 与 `sent:true`。Computer Use 对 `UserNotificationCenter.app` 返回安全策略拒绝,对 `NotificationCenter.app` 仍 `timeoutReached`,因此真实 OS 点击仍未完成。通知 smoke 现已记录 `lifecycle: close|failed`,后续可在可操作通知横幅的 macOS/Windows runner 上区分 close、failed 和 action。 -- 2026-06-01 packaged 通知点击 launchctl 复测:直接执行 app binary 时 smoke env 没有进入最终 Electron app 进程;改用 `launchctl setenv` + `open -n` 启动隔离 packaged app 后,JSONL 正常记录 `scheduled` 和 `sent:true`。Computer Use 对 Notification Center 与 SystemUIServer 仍 `timeoutReached`,Finder 状态读取返回 ScreenCaptureKit stream error,JSONL 未出现 `action`,所以真实点击回跳仍不能勾选。 -- 2026-06-01 通知点击再次复测:worktree packaged app 通过 `launchctl setenv` 启动后,通知 JSONL 正常记录 `scheduled` 和 `sent:true`;Computer Use 对 worktree app 连续返回 ScreenCaptureKit stream error,`list_apps` 仍能看到该 app running。复制当前 packaged app 为唯一 bundle id smoke app 并 ad-hoc 重签名后,LaunchServices 能注册 `Claude Code Haha Smoke`,但 app 立即退出,系统日志显示 appDeath;本轮仍没有真实 OS 点击 `action` 证据。 -- 2026-06-01 renderer ack 补强后 packaged 复测:重新打包 `desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app` 并通过 package-smoke dir PASS;首次以 notification smoke env 启动时 Computer Use 读取 worktree packaged app 返回 `timeoutReached`,直接执行也曾退出 137。随后用隔离 `CLAUDE_CONFIG_DIR` / `--user-data-dir` 重新直接运行同一 `.app` 后保持运行,Computer Use 成功读取 `Claude Code Companion` 主窗口,renderer URL 为 `file:///Users/nanmi/.codex/worktrees/2392/claude-code-haha/desktop/build-artifacts/electron/mac-arm64/Claude%20Code%20Haha.app/Contents/Resources/app.asar/dist/index.html`;点击设置入口后进入 packaged 设置页。`spctl` 仍返回 `bundle format unrecognized, invalid, or unsuitable`,`codesign --verify --deep --strict` 通过,因此 signed/notarized Gatekeeper 与真实 OS 通知点击仍未完成。 -- 2026-06-01 packaged synthetic notification action 复测:新增 `CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_TRIGGER_ACTION=1`,仅在显式 smoke 环境中由 main process 触发同一条 notification action -> renderer navigation -> `desktop:notification:action-ack` 诊断链路。用 `launchctl setenv` + `open -n desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app --args --user-data-dir=` 启动当前 worktree packaged app 后,Computer Use 成功读取 packaged `file://.../app.asar/dist/index.html` 主窗口;JSONL 记录 `scheduled`、`sent:true`、`synthetic_action` 与 `renderer_ack`。该 synthetic action 不代表真实 OS 通知点击,真实点击回跳仍未勾选。 -- 2026-06-01 DiskImages 复核:`hdiutil info` 仍显示 Electron Builder 失败遗留的 `.temp...Claude-Code-Haha-0.3.2-arm64.dmg` 挂载在 `/dev/disk4`,但无 `hdiutil` / `diskutil` / `electron-builder` 残留进程;此前 `hdiutil detach` 和 `diskutil` 对该设备会阻塞,继续强制处理会污染当前验证。 -- 2026-06-01 macOS build script hardening:默认 `SKIP_INSTALL=1 SIGN_BUILD=0 desktop/scripts/build-macos-arm64.sh` 现在在 stale `.temp...Claude-Code-Haha-0.3.2-arm64.dmg` 挂载存在时 fail fast,并提示改用 `MAC_TARGETS=zip` 验证 update zip;`MAC_TARGETS=zip` 路径通过且自动 package-smoke PASS。 -- 2026-06-01 macOS canonical release 复测:默认 `SKIP_INSTALL=1 SIGN_BUILD=0 desktop/scripts/build-macos-arm64.sh` 已完成 `.dmg + .zip + latest-mac.yml`,package-smoke release PASS。启动 canonical `.app` 后 Computer Use 成功读取 `Claude Code Companion` 主窗口;electron-updater 内置 logger 已关闭,缺失 GitHub `latest-mac.yml` 不再向启动日志输出 404 stack。 -- 2026-06-01 canonical packaged system interaction 复测:启动 `desktop/build-artifacts/macos-arm64/Claude Code Haha.app` 的隔离实例后,Computer Use 读取到 packaged `file://.../app.asar/dist/index.html` 主界面,项目选择器显示 `/private/tmp/cc-haha-electron-real-fixed-sRFUeO/project`;点击 `打开终端` 后终端面板运行 `/bin/zsh`,输入 `printf 'canonical-terminal-ok\n'; pwd` 后显示 `canonical-terminal-ok` 与 `/tmp/cc-haha-canonical-cua-flow.xTxSmt/claude-config`。该轮未出现 `claude-code-desktop Safe Storage` 钥匙串授权弹窗。 -- 2026-06-01 canonical packaged Computer Use 设置流程复测:启动 `desktop/build-artifacts/macos-arm64/Claude Code Haha.app` 的隔离实例后,用 Computer Use 进入 `设置 > Computer Use`。首次状态请求曾 30s timeout,点击 `重试` 后状态页正常;通过 `安装环境` 按钮完成 venv 创建与依赖安装。UI 显示 Python 3.14.5、虚拟环境已就绪、依赖包已安装、辅助功能权限已授权、屏幕录制权限未授权;`GET /api/computer-use/status` 返回 `venv.created=true`、`dependencies.installed=true`、`permissions.accessibility=true`、`permissions.screenRecording=false`。点击 `打开屏幕录制设置` 后,Computer Use 读取到 macOS 系统设置窗口 `录屏与系统录音`,列表中包含 `Claude Code Haha` 权限项。 -- 2026-06-01 canonical packaged Computer Use 授权应用复测:同一 packaged app 隔离实例中,Computer Use 在 `已授权应用` 搜索框输入 `Terminal`,列表返回 `Terminal / com.apple.Terminal`;点击后 UI 显示 check 状态,隔离 `cc-haha/computer-use-config.json` 与 `GET /api/computer-use/authorized-apps` 均返回 `authorizedApps[0].bundleId=com.apple.Terminal`,grant flags 为 `clipboardRead=true`、`clipboardWrite=true`、`systemKeyCombos=true`。该项覆盖真实 UI 输入、点击、滚动、应用枚举和授权配置持久化。 -- 2026-06-01 packaged menu/window lifecycle 复测:在隔离 `desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app` 中,Computer Use 点击 macOS app menu `Settings...` 后打开设置页;点击窗口关闭按钮后主窗口消失且同一 PID 继续运行,System Events 显示 `count=0`;随后 app activation 恢复窗口,Computer Use 再次读取到 packaged `file://.../app.asar/dist/index.html` 设置页。继续通过 app menu `Quit` 退出后,隔离配置写入 `window-state.json`;用同一 `CLAUDE_CONFIG_DIR` 和 `--user-data-dir` 重启,Computer Use 成功读取新 PID 的 packaged 主窗口,System Events 显示 `count=1 names=Claude Code Companion`。该项覆盖菜单打开设置、关闭隐藏、恢复窗口和重启后窗口仍可见;真实 tray 图标点击仍未单独完成。 -- 2026-06-01 Gatekeeper 诊断补强后 packaged launch 复测:用隔离 `CLAUDE_CONFIG_DIR` 和 `--user-data-dir` 启动 `desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app`,Computer Use 成功读取 `Claude Code Companion` 主窗口和 packaged `file://.../app.asar/dist/index.html` renderer,未出现 Safe Storage 钥匙串弹窗。 -- 2026-06-01 release provider smoke 后 packaged launch 复测:真实 provider smoke 通过后,再次用隔离 `CLAUDE_CONFIG_DIR` 和 `--user-data-dir` 启动 `desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app`,Computer Use 成功读取 packaged `file://.../app.asar/dist/index.html` 主窗口。 -- 2026-06-01 policy gate 后 packaged launch 复测:`check:policy` 通过后,再次用隔离 `CLAUDE_CONFIG_DIR` 和 `--user-data-dir` 启动 packaged app;Computer Use 首次读取遇到 ScreenCaptureKit stream error,System Events 同时显示同一 PID 有 `Claude Code Companion` 窗口,重试后 Computer Use 成功读取 packaged renderer。 -- 2026-06-01 本地 `main` 同步后 packaged launch 复测:重新打包 `desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app`,使用隔离 `CLAUDE_CONFIG_DIR=/tmp/cc-haha-main-sync-cua-1780317323/config` 和隔离 `--user-data-dir` 启动;Computer Use 成功读取 `Claude Code Companion` 主窗口和当前 packaged renderer,未出现 Safe Storage 钥匙串弹窗。 -- [x] 启动真实 packaged app:`desktop/build-artifacts/electron-smoke/mac-arm64/Claude Code Haha Smoke.app`。该 smoke bundle 使用同一构建产物重新打包,仅更换临时 appId/productName 以避开本机已运行的正式 app 单实例锁。 -- [x] 本地 `main` 同步后启动真实 packaged app:`desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app`,使用隔离 `--user-data-dir=/tmp/cc-haha-electron-real-fixed-sRFUeO/user-data` 和隔离 `CLAUDE_CONFIG_DIR`。 -- [x] 确认主界面从 packaged renderer 加载,local server sidecar 自动启动,无需手动 `SERVER_PORT`。 -- [x] 打开终端,执行 `printf electron-terminal-ok`,确认输出可见。 -- [x] 关闭终端后通过进程列表确认没有残留 `/tmp/cc-haha-electron-smoke-config` PTY shell。 -- [x] 选择工作目录,确认系统目录对话框可打开。 -- [x] 复制本机 cc-haha provider/model 配置到隔离临时配置目录,真实模型选择器显示 `gpt-5.5 Sub2API-ChatGPT`。 -- [x] 真实模型 chat smoke:会话 `e0d7abc2-27db-4ff1-9954-7923f6c3385e` 通过 WebSocket 启动 CLI subprocess,读取 `package.json` 与 `src/greeting.ts`,经 UI 权限审批执行 `bun test`,JSONL 记录 `1 pass / 0 fail`,Computer Use 看到最终回复“测试通过。”。 -- [x] 选择附件文件,确认文件可加入 composer。 -- [x] 打开 Python 路径选择,确认系统文件选择器可用。 -- [x] 点击外链或 OAuth 帮助链接,确认走系统浏览器。 -- [x] 关闭主窗口,确认 Dock/app activation 可恢复窗口;真实 tray 图标点击仍需可枚举 tray 环境复验。 -- [ ] 触发通知,点击通知后回到目标 session。 -- [x] 触发通知发送 smoke:packaged app 通过 `CC_HAHA_ELECTRON_NOTIFICATION_SMOKE_LOG` 记录 `scheduled` 和 `sent:true`;真实 OS 点击仍因 Computer Use 无法操作 Notification Center/SystemUIServer 而未完成。 -- [x] 打开 preview/workbench browser panel,导航 `https://example.com/`,确认 WebContentsView 加载成功。 -- [x] 点击 preview 截图,确认 `screenshot-full.png` 回填到 composer。 -- [x] packaged Computer Use 设置流程:隔离配置下完成环境安装,确认 venv/依赖状态、辅助功能/屏幕录制权限状态,并从应用打开 macOS `录屏与系统录音` 设置页。 -- [x] packaged Computer Use 授权应用流程:搜索并授权 `Terminal / com.apple.Terminal`,确认剪贴板和系统快捷键 grant flags 持久化。 -- [x] unpacked dir package 缺少 `app-update.yml` 时 update check 视为无更新,不阻塞 UI;非 metadata updater 错误仍会抛出。 -- [x] 更新代理 contract:Electron updater 会接收 renderer 传入的 manual proxy,并在切回 system proxy 时清理手动代理;IPC validator 只允许 `{ proxy: string }` 形状,拒绝空 proxy 或额外字段。 -- [x] 关闭 electron-updater 内置 logger,避免缺失远端 `latest-mac.yml` 时把 404 stack 打到 packaged app 启动日志;业务层仍把 metadata 缺失分类为“无更新”。 -- [x] 发布型包 updater metadata:如果 artifact set 中存在 release archive/update metadata,`package-smoke` 会强制检查安装后 resources/app-update.yml,防止“有 latest*.yml 但安装后永远无法检查更新”的包进入 release;macOS zip-only canonical artifact set 已通过该检查。 -- [x] packaged app updater 缺失 channel metadata smoke:重新打包 `desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app` 后,用隔离配置目录直接运行 app executable;Computer Use 确认 packaged renderer 从 `file://.../app.asar/dist/index.html` 加载,local server sidecar 自动启动,未出现 Safe Storage 钥匙串弹窗,日志未再出现 `ERR_UPDATER_CHANNEL_FILE_NOT_FOUND` IPC handler 崩错。纯 `--dir` 包缺少 `app-update.yml` 的提示仍按无更新处理。 -- [x] 触发 mock feed,确认 check/download/install/relaunch 状态流不阻塞 UI:`UpdateChecker` 集成测试通过 Electron `desktopHost` mock update feed 驱动真实 `updateStore`,覆盖下载完成弹窗、点击 install、prepare/install/relaunch 调用和 prompt 退出。signed/release update feed 仍需在真实 signed/notarized artifact 上复验。 -- [x] packaged main/preload update smoke:`CC_HAHA_ELECTRON_UPDATE_SMOKE_VERSION=9.9.9-smoke` 启动唯一 bundle id 的 packaged dir app,packaged renderer 通过 preload 调用 `desktopHost.updates.check/download/prepareInstall/install/relaunch`,JSONL 记录 `check`、`download-start`、`download-finish`、`quit-and-install`。Computer Use `list_apps` 可见该 smoke app,但 `get_app_state` 超时,真实桌面点击安装仍按未完成处理。 -- [x] packaged window smoke 诊断:`CC_HAHA_ELECTRON_WINDOW_SMOKE_LOG` 记录 packaged app 在 `after-initial-show` 和 `after-final-show` 均为 `visible:true`,bounds 为 `1280x820`,renderer URL 为 packaged `file://.../app.asar/dist/index.html`;早前 System Events 曾返回 `windows count=0` 且 Computer Use `get_app_state` 超时。随后用完整 `.app` 路径复测,Computer Use 成功读取 `Claude Code Companion` 主窗口和 packaged renderer,System Events 返回 `front=true count=1 names=Claude Code Companion`。因此之前卡点是 macOS AX/窗口捕获状态波动,不是 renderer、server sidecar 或 update preload smoke 失败。 - -## Windows 待实机 - -- [ ] 构建 NSIS 安装器并安装。 -- [x] Windows build script 已补 canonical package-smoke:`desktop/scripts/build-windows-x64.ps1` 会复制 installer/update metadata/blockmap/`win-unpacked` 到 `desktop/build-artifacts/windows-x64`,并默认运行 `bun run test:package-smoke --platform windows --package-kind release --artifacts-dir desktop/build-artifacts/windows-x64`。 -- [ ] 在 Windows runner/实机运行 `bun run test:package-smoke --platform windows` 或 build script 内置 canonical package-smoke。 -- [ ] 启动安装后的 app,验证 sidecar 自动启动。 -- [ ] 验证 sidecar 文件锁、更新前 stop process、通知、托盘、窗口隐藏/恢复、系统对话框。 -- [ ] 完成一次 Computer Use 设置和操作 smoke。 - -## Linux 待实机 - -- [ ] 构建 AppImage/deb。 -- [x] Linux build script 已补 canonical package-smoke:`desktop/scripts/build-linux.sh` 支持 `LINUX_ARCH=x64|arm64`,会复制 AppImage/deb/update metadata/blockmap/`linux-unpacked` 到 `desktop/build-artifacts/linux-`,并默认运行 `bun run test:package-smoke --platform linux --package-kind release --artifacts-dir desktop/build-artifacts/linux-`。 -- [x] Linux arm64 update metadata preflight 已补:`desktop/scripts/build-linux.sh` 复制 `latest-linux*.yml`,`package-smoke` 识别 `latest-linux.yml` 和 `latest-linux-arm64.yml` 并校验 metadata 引用的 AppImage/deb artifact;回归测试覆盖 canonical `linux-arm64` 输出。 -- [ ] 在 Linux runner/实机运行 `bun run test:package-smoke --platform linux` 或 build script 内置 canonical package-smoke。 -- [ ] 启动 AppImage/deb 安装后的 app,验证 sidecar 自动启动。 -- [ ] 验证 tray、通知、系统对话框、外链、更新策略;如平台能力降级,写入 release note。 - -## 完成判定 - -- [ ] macOS packaged app 完成 Computer Use smoke。历史上已完成启动、server、终端、目录选择、preview、真实模型 chat 与 unpacked updater no-op;dev 壳另已补附件选择、Python 路径选择、外链打开和窗口关闭后 app activation 恢复证据。最终完成仍需要可启动的 signed/notarized artifact、可截图/可点击桌面会话、通知点击回跳和完整 update feed 复验。 -- [ ] Windows packaged app 完成实机 smoke,或明确记录 release blocker。 -- [ ] Linux packaged app 完成实机 smoke,或明确记录 release blocker。 -- [x] `verify` 通过,并记录 quality report 与 coverage report 路径。最新 quality report:`artifacts/quality-runs/2026-05-31T21-42-57-279Z/report.md`;coverage report:`artifacts/coverage/2026-05-31T21-46-15-873Z/coverage-report.md`。 -- [x] live release gate 通过,或明确记录 provider/live access blocker。最新 `release --allow-live` 已通过:`artifacts/quality-runs/2026-05-31T20-29-45-758Z/report.md`,`passed=15 failed=0 skipped=0`。 diff --git a/docs/desktop/10-release-auto-update.md b/docs/desktop/10-release-auto-update.md deleted file mode 100644 index 14104105..00000000 --- a/docs/desktop/10-release-auto-update.md +++ /dev/null @@ -1,78 +0,0 @@ -# Electron 发布与自动更新 - -本页是维护者发版 runbook。桌面端版本来源是 `desktop/package.json`,正式发布必须让版本号、Git tag 和 `release-notes/vX.Y.Z.md` 严格一致。 - -## 更新链路 - -应用内更新由 `electron-updater` 驱动,发布产物托管在 GitHub Releases: - -| 平台 | 安装/更新目标 | Metadata | -|------|---------------|----------| -| macOS arm64/x64 | `dmg` 用于首次安装,`zip` 用于 Squirrel.Mac 更新 | `latest-mac.yml` | -| Windows x64 / ARM64 | NSIS `.exe` | `latest.yml` | -| Linux x64 | `.AppImage`,同时发布 `.deb` 供手动安装 | `latest-linux.yml` | -| Linux arm64 | `.AppImage`,同时发布 `.deb` 供手动安装 | `latest-linux-arm64.yml` | - -Release workflow 会先在各平台 matrix 中生成 `latest*.yml`,把同名 metadata 临时改名为 `latest-.yml`,最后由 `scripts/release-update-metadata.ts` 合并回 electron-updater 期望的标准文件名。不要改成 matrix job 直接发布 GitHub Release,否则 metadata 可能互相覆盖。 - -## Signing Secrets - -macOS signed/notarized release 依赖 GitHub Actions repository secrets: - -```text -MACOS_CERTIFICATE -MACOS_CERTIFICATE_PASSWORD -APPLE_ID -APPLE_APP_SPECIFIC_PASSWORD -APPLE_TEAM_ID -``` - -`MACOS_CERTIFICATE` 是 Developer ID Application `.p12` 的 base64 内容。当前项目不发布 `.pkg`,不需要 Developer ID Installer 证书。 - -Windows signing secrets 是可选项: - -```text -WINDOWS_CERTIFICATE -WINDOWS_CERTIFICATE_PASSWORD -``` - -缺少 Windows 签名时 NSIS 自动更新仍可工作,但用户可能看到 SmartScreen 提示。 - -## v0.4.3 首次切换 - -`v0.4.3` 是从旧 unsigned macOS 发布迁移到 Developer ID 签名/notarization 的第一个版本时,建议不要直接依赖旧版本自动更新: - -1. 用 release workflow 产出 signed/notarized `v0.4.3`。 -2. 在一台未安装开发证书的 macOS 机器上手动下载 `Claude-Code-Haha-0.4.3-mac-arm64.dmg`。 -3. 不执行 `xattr -d` 或 `xattr -cr`,直接安装并打开。 -4. 确认只出现标准下载来源确认,不出现无法验证开发者、文件损坏或 unidentified developer。 -5. 再把后续 `v0.4.4` 作为自动更新验证目标。 - -## v0.4.11 自动更新验证 - -发布 `v0.4.11` 时至少验证一条真实更新链路: - -1. 安装 GitHub Release 中的 `v0.4.10` 正式包。 -2. 发布 `v0.4.11` tag,让 `Release Desktop` workflow 完整通过。 -3. 打开 `v0.4.10` 应用,等待启动后自动检查,或进入 Settings 手动检查更新。 -4. 确认应用提示 `v0.4.11`,下载完成后点击安装并重启。 -5. 重启后确认 About/Settings 中版本为 `0.4.11`,并检查原有服务商、会话、Skills、Agents、记忆、自定义宠物和自定义数据目录仍然可用。 -6. 确认历史附件上下文、SubAgent 详情、Task 状态和 Agent 配置均可正常恢复;打开桌面宠物,验证悬浮窗口、任务面板和当前会话导航。 - -平台重点: - -- macOS:确认 Release job 使用 `Build signed macOS Electron release artifacts`,且 `Verify macOS launch policy` 通过。 -- Windows:确认 `latest.yml`、`.exe`、`.exe.blockmap` 都在 GitHub Release 中;未签名时 SmartScreen 不代表 updater 失败。 -- Linux:优先用 AppImage 验证自动更新;`.deb` 继续作为手动安装包发布。 - -## 发版前检查 - -发版前至少运行: - -```bash -bun run scripts/release.ts 0.4.11 --dry -bun test scripts/pr/release-workflow.test.ts scripts/release-update-metadata.test.ts scripts/quality-gate/package-smoke/index.test.ts -bun run check:policy -``` - -正式调用 `bun run scripts/release.ts 0.4.11` 前,先确认对应 `release-notes/v0.4.11.md` 已经存在。 diff --git a/docs/desktop/agents.md b/docs/desktop/agents.md new file mode 100644 index 00000000..c80e51f2 --- /dev/null +++ b/docs/desktop/agents.md @@ -0,0 +1,75 @@ +--- +title: 子 Agent 与任务拆分 +nav_title: 子 Agent +description: 什么时候该派子 Agent、内置的有哪些、怎么捏一个自己的。 +order: 3 +--- + +# 子 Agent 与任务拆分 + +子 Agent 就是 Claude 派出去的分身:给它一个明确的小任务,它自己带着独立上下文去干,干完只把结论交回来。 + +好处是主对话不会被一堆中间过程撑爆。比如「在这个仓库里找出所有调用 `validateUser` 的地方」,如果主 Agent 亲自去搜,几十个文件的内容全会挤进上下文;派个子 Agent 去,回来的只有一份清单。 + +## 什么时候该派 + +- **要翻很多文件才能回答的问题** — 找用法、理清依赖、统计某种模式在哪些地方出现。 +- **可以并行的独立工作** — 前端一个、后端一个、测试一个,同时开工。 +- **需要多个角度看同一件事** — 比如让几个 Agent 各自审一遍同一段代码。 + +反过来,你已经知道文件在哪、改哪一行,那就直接说,不用绕这一圈。 + +派出去的子 Agent 会出现在活动面板的「SubAgent」区块,工具活动实时冒泡,点进去能看它完整的运行记录和最终结果。后台跑的也一样,不用等它结束才知道在干什么。 + +## 内置 Agent + +不用配置就能用的几个: + +| 名称 | 干什么的 | +|---|---| +| `general-purpose` | 通用兜底。复杂问题研究、找代码、多步骤任务,不知道派谁就派它 | +| `Explore` | 专门快速探索代码库。按模式找文件、按关键词搜代码、回答「这块是怎么工作的」 | +| `Plan` | 架构师。设计实现方案,返回分步计划、关键文件和取舍 | +| `claude-code-guide` | 回答关于 Claude Code、Agent SDK 和 Claude API 本身的问题 | +| `verification` | 收工前的验收。跑构建、测试、linter,给出通过 / 失败 / 部分通过的结论 | +| `statusline-setup` | 配置 Claude Code 状态栏 | + +在会话里可以直接说「用 Explore 去找一下……」,也可以让 Claude 自己判断该派谁。 + +## 看已经装了哪些 + +![设置 → Agents:按来源分组的 Agent 浏览器](../images/app/settings-agents.webp) + +打开 设置 → Agents。顶部三张卡是总数、生效中、来源类型数,下面按来源分组: + +**用户** → **项目** → **本地** → **托管** → **插件** → **CLI 参数** → **内置** + +同名的 Agent 上面的来源会盖住下面的,被盖住的那个会标「被 X 覆盖」。日常主要看两组: + +- **用户** — 你自己建的,对所有项目生效,文件在 `~/.claude/agents/`。 +- **项目** — 只在当前项目生效,文件在项目里的 `.claude/agents/`,会跟着仓库一起分发。 + +点任意一条进详情页,能看到它的模型、思考强度、工具范围和完整系统提示词。内置和插件来源是只读的,详情页右上角会有一个「只读」标记。 + +## 捏一个自己的 + +![「创建 Agent」弹窗:作用域、模型、思考强度、工具、系统提示词](../images/app/agent-create.webp) + +点右上角「创建 Agent」,要填的字段: + +1. **配置范围** — 用户还是项目。选「项目」时下面会让你确认目标项目路径。 +2. **名称** — 1–64 位小写字母、数字、连字符或下划线,比如 `code-reviewer`。这是主 Agent 调用它时用的名字。 +3. **描述** — 说明主 Agent 应该在什么场景下委派给它。**这一条最重要**:主 Agent 就是靠它决定要不要派这个 Agent,写含糊了就永远不会被叫到。 +4. **系统提示词** — 定义这个 Agent 的职责、边界和预期输出。 +5. **模型** — 继承主 Agent,或指定 Haiku / Sonnet / Opus / Fable,也可以填自定义模型 ID。简单重复的活给 Haiku 更快更省。 +6. **思考强度** — 继承,或单独指定低 / 中 / 高 / 极高 / 最大。模型不支持某档时会自动降级或忽略。 +7. **工具** — 三选一:全部工具、不允许使用工具、自定义列表。选自定义时按读取与搜索 / 修改文件 / 执行命令 / 工作流分类勾选,下面还有一个自由输入框,用来填 MCP 工具名或者 `Bash(git:*)` 这样的权限规则。 +8. **颜色** — 用来在界面上区分,可选。 + +保存后写入对应目录的 Markdown 文件,桌面端会尝试刷新当前会话。刷新失败不会回滚已经写好的文件,重启后仍然有效。 + +:::tip +只给必需的工具。一个只负责读代码给结论的 Agent 不需要 Write 和 Bash——权限收窄了,它跑偏的空间也就小了。 +::: + +想了解 Agent 文件格式、来源优先级和继承规则,看[Agent 系统原理](../internals/agent.md)。 diff --git a/docs/desktop/computer-use.md b/docs/desktop/computer-use.md new file mode 100644 index 00000000..0b9ec022 --- /dev/null +++ b/docs/desktop/computer-use.md @@ -0,0 +1,92 @@ +--- +title: Computer Use +nav_title: Computer Use +description: 让 Claude 读屏幕、点鼠标、敲键盘,替你操作别的应用。 +order: 7 +--- + +# Computer Use + +开了 Computer Use,Claude 就能截屏看你的屏幕、移动鼠标、点击、输入文字,去操作那些没有 API 的应用——系统设置、原生笔记、Finder、第三方桌面软件都行。 + +它直接作用于你这台电脑,所以开之前请先看清楚要授权什么。 + +支持 macOS 和 Windows。Linux 暂无执行器。 + +## 准备环境 + +![设置 → Computer Use:环境检测、macOS 两项授权、已授权应用](../images/app/settings-computer-use.webp) + +打开 设置 → Computer Use,页面顶部是一排环境检查: + +1. **Python 3** — 需要本机装了 Python 3。没检测到时点「下载 Python 3」去装。装在 conda、pyenv 或其他自定义环境里的话,在「Python 解释器路径」里手动选可执行文件,之后会优先用它。 +2. **虚拟环境** 和 **依赖包** — 点「安装环境」,应用会自己建一个隔离的 venv 并装好平台依赖,不污染你的全局 Python。 +3. 全绿以后页面会显示「所有检查通过,Computer Use 已就绪」。 + +装完可以随时点「重新检测」核对状态。 + +## macOS 的两项系统授权 + +macOS 上还需要两个系统权限,缺一个都用不了: + +| 权限 | 用来干什么 | +|---|---| +| 辅助功能 | 移动鼠标、点击、输入文字 | +| 屏幕录制 | 截屏,也就是「让它看见」 | + +页面上有「打开辅助功能设置」和「打开屏幕录制设置」两个按钮,直接跳到系统设置对应位置。 + +:::warning +授权之后必须**完全退出并重新打开应用**才会生效。macOS 只在进程启动时读一次权限,不重启的话页面上会一直显示未授权。 +::: + +授权时注意勾的是真正启动 Claude Code Haha 的那个 App。屏幕录制的自动检测偶尔不稳定,如果系统设置里明明已经勾上、页面却还显示未授权,通常直接用就行。 + +## 预授权应用 + +默认情况下,Claude 每次想控制一个新应用,都会先弹一个「Computer Use 想控制这些应用」的确认框,写清它要控制哪些应用、为什么。你可以「本次会话允许」或者「拒绝」。 + +如果某几个应用你反复都会同意,可以在设置页的「已授权应用」里预先勾上。勾了以后 Claude 控制它们就不再弹窗。搜索框可以按名字过滤已安装的应用。 + +另外两个开关是单独的,不会因为授权了某个应用就自动获得: + +- **剪贴板访问** — 读写系统剪贴板。 +- **系统快捷键** — 发送系统级组合键。 + +:::danger +预授权等于永久放行。密码管理器、银行 App、企业 IM 这类应用不要放进列表——让它每次都问你一次。 +::: + +## 开始用 + +新建一条会话,用自然语言说清目标和允许操作的应用。从简单、可撤销的任务起步: + +```text +截取当前屏幕并告诉我你看到了什么。 +打开「备忘录」,新建一条标题为「测试」的空白笔记。 +帮我在系统设置里找到「显示器」那一页,但什么都不要改。 +``` + +Claude 的工作方式是「截图 → 判断 → 操作 → 再截图」,所以它会比人慢,也会偶尔点错。给它明确的边界(「只在 X 应用里操作」「不要保存」)比给它一个笼统目标更靠谱。 + +同一时间只能有一条会话控制鼠标键盘。看到「正在被其他会话使用」时,先停掉或跑完那条会话。 + +## 已知边界 + +- **没有全局中止热键。** 想停下来就点会话里的停止按钮(`⌘.`)。 +- **Windows 截图不过滤窗口。** macOS 的截图只保留已授权应用和桌面,Windows 上所有可见窗口都会进画面。开始前请关掉或最小化含敏感信息的窗口。 +- **浏览器和终端里的操作受限。** 浏览器只允许读(能看见但不能点),终端和 IDE 只允许点击(不能输入)。要操作网页请用浏览器扩展,要跑命令请让它用 Bash 工具。 +- **UI 变了要重新截图。** 页面变化之后不能沿用旧坐标。 + +## 排查 + +**页面一直提示缺少权限** +确认授权的是实际启动应用的那个 App,完全退出后重开,再点「重新检测」。 + +**环境装不上** +在「Python 解释器路径」里明确选一个 Python 3,确认它支持 `venv`,重新点「安装环境」。还不行就去 设置 → 诊断 看安装日志。 + +**能截图但点不动** +确认目标应用在已授权列表里,并且它现在是前台窗口。浏览器和终端类应用受上面说的分级限制。 + +想了解权限分级、Python 桥接和执行器的实现,看[Computer Use 架构](../internals/computer-use.md)。 diff --git a/docs/desktop/index.md b/docs/desktop/index.md index af29e2b1..f509fb00 100644 --- a/docs/desktop/index.md +++ b/docs/desktop/index.md @@ -1,77 +1,44 @@ -# 桌面端文档 +--- +title: 桌面端功能总览 +nav_title: 功能总览 +description: 一屏看懂桌面端有哪些能力,以及每个功能该翻哪一篇。 +order: 0 +--- -Claude Code Haha Desktop 是项目的主要使用入口。它把会话、项目、代码审阅、权限审批、模型服务商、Agent、技能、Computer Use、H5 与 IM 接入放在同一个 macOS、Windows 和 Linux 工作台中。 +# 桌面端功能总览 -![完整展开项目与历史侧栏的 Main Session,中央会话是主要工作区域](../images/desktop_ui/25_main_session.png) +桌面端把「和 Claude 说话」和「看它到底改了什么」放进同一个窗口:左边是项目和历史会话,中间是对话,右边随时能拉出文件、Diff 和网页预览。 -## 第一次使用 +这一篇是地图,不是说明书。每个功能只写一句话,想细看点进去。还没装好或还没连上模型,先去[开始使用](../start/index.md)。 -按下面的顺序可以最快完成一轮真实任务: +## 先认识三块地方 -1. 从 [GitHub Releases](https://github.com/NanmiCoder/cc-haha/releases) 下载适合系统与 CPU 架构的安装包,参照[安装指南](./04-installation.md)完成安装。 -2. 首次打开后进入「设置 → 服务商」,选择 Claude、ChatGPT 或 Grok 官方登录,也可以使用内置预设或 Custom 配置 API。 -3. 新建会话,选择一个工作目录、模型和权限模式。 -4. 先发送一个范围清楚的小任务,观察回复、工具调用、权限请求和右侧工作区。 -5. 熟悉主流程后,再启用 Auto、H5、IM、Computer Use 等扩大权限或访问范围的能力。 +- **左侧边栏**:品牌印章下面是「新建会话」「定时任务」「技能市场」,再往下是搜索条和你的项目、历史会话,最底部是「设置」。边界可以拖动改宽窄。 +- **标签栏**:多个会话并排,像浏览器一样切换。右侧四个按钮分别是活动面板、打开项目、打开终端、显示/隐藏工作区。 +- **输入框工具栏**:从左到右是附件、权限模式、运行位置、上下文用量环、模型与推理强度,最后是运行按钮。 -完整操作见[快速上手](./01-quick-start.md)。 +## 每天都会用到 -## 你可以完成什么 +- [会话、权限与审阅](./sessions.md) — 怎么开一条会话、工具调用卡怎么看、权限弹窗点哪个、改错了怎么撤销。 +- [工作区](./workspace.md) — 右侧那块面板:已更改文件、Diff 评审、行级评论、独立工作树、内置浏览器。 +- [设置速查](./settings.md) — 16 个分栏一栏一段,包括主题、语言、代理、终端、MCP、Token 用量。 -### 对话与代码工作台 +## 让它替你干更多活 -- 同时管理多个项目和会话,并在标签页之间切换。 -- 上传图片、文件、目录和 PDF;恢复会话后继续保留附件上下文。 -- 在右侧工作区搜索文件、查看变更与 Diff,并把单行或连续行评论带回聊天。 -- 使用 `Ctrl/Cmd + F` 在长对话中查找,或通过对话导航快速跳转。 +- [子 Agent 与任务拆分](./agents.md) — 什么时候该派 Agent,内置 Agent 有哪些,怎么捏一个自己的。 +- [技能与技能市场](./skills.md) — 技能是什么、和 Agent 有什么区别、从市场装技能前要看什么。 +- [定时任务](./schedule.md) — 让 Claude 每天早上自动跑一遍代码审查。 +- [Computer Use](./computer-use.md) — 让它读屏幕、点鼠标、敲键盘,替你操作别的应用。 -### 模型与执行控制 +## 换个设备继续 -- 使用 Claude、ChatGPT、Grok 官方登录,或配置 Anthropic / OpenAI 兼容 API。 -- 为当前会话选择模型、思考强度和五种权限模式。 -- 在活动面板查看后台任务、Task、SubAgent 和 Agent Team 的运行状态。 -- 浏览技能市场,管理用户级与项目级 Agent,并分别配置模型、思考强度和工具。 +- [手机 H5 与 IM 接力](./remote.md) — 扫码在手机浏览器里继续同一条会话,或者从微信、飞书、Telegram 直接对话。 +- [IM 接入](../im/index.md) — 五个平台各自的详细配置步骤。 -### 桌面与远程入口 +## 让它顺眼一点 -- 在授权后使用 Computer Use 控制桌面应用。 -- 使用互动桌面宠物观察任务状态并返回对应会话。 -- 通过 H5 在可信局域网或自己的反向代理中访问当前桌面服务。 -- 通过微信、钉钉、WhatsApp、Telegram 或飞书连接同一套本地会话。 +- [桌面宠物](./pets.md) — 一只悬浮在桌面上的小机器人,用动作告诉你任务跑到哪了。默认关闭。 -## 用户文档 +## 想知道它是怎么实现的 -| 文档 | 适合谁 | 内容 | -|------|--------|------| -| [快速上手](./01-quick-start.md) | 第一次打开桌面端的用户 | 从服务商配置到首轮任务,以及工作区、权限、Agent、宠物和快捷键 | -| [功能详解](./03-features.md) | 想系统了解能力边界的用户 | 对话、Diff、搜索、活动、技能、Agent、诊断、H5 与 IM | -| [安装指南](./04-installation.md) | 安装、升级或从源码运行的用户 | 各平台安装包、覆盖升级、同源 Web UI 与更新 | -| [常见问题](./05-FAQ.md) | 遇到连接、搜索、升级或恢复问题的用户 | 面向当前版本的排查顺序与求助材料 | -| [H5 访问](./06-h5-access.md) | 需要手机或浏览器远程入口的用户 | 端口、二维码、Token、同源部署与安全边界 | -| [桌面宠物](./pets.md) | 想让桌面伙伴跟随任务状态的用户 | 开启宠物、切换角色、调整外观、导入自定义宠物与理解交互边界 | - -相关专题: - -- [多 Agent 使用指南](../agent/01-usage-guide.md) -- [IM 接入总览](../im/index.md) -- [Computer Use](../features/computer-use.md) -- [第三方模型](../guide/third-party-models.md) - -## 使用边界 - -- Auto 与跳过权限都会扩大自动执行范围。Auto 会审查工具调用,但不能保证绝对安全;跳过权限只适合你完全信任的工作目录和任务。 -- H5 不是公开 SaaS 或多租户系统。持有有效 Token 的设备可以访问桌面服务暴露的核心能力。 -- 本地索引是可重建的派生数据,原始会话与设置仍是事实源。 -- 模型、上下文长度和思考强度的实际可用性取决于账号权限及服务商能力。 -- 桌面宠物运行在 Electron 桌面端,不会显示在 H5 中;单图导入只生成本地轻动画,不是 AI 逐帧动画。 - -## 维护者资料 - -下面的页面记录架构、迁移和发布流程,不是普通用户的操作指南: - -- [架构设计](./02-architecture.md) -- [Electron 迁移调研](./07-electron-migration-research.md) -- [Electron 迁移任务清单](./08-electron-migration-tasks.md) -- [Electron 迁移验证清单](./09-electron-migration-validation-checklist.md) -- [Electron 发布与自动更新](./10-release-auto-update.md) -- [迁移交互式验收清单](/desktop/electron-migration-qa-checklist.html) +上面几篇讲的是「怎么用」。想看架构、想改代码,从[架构总览](../internals/index.md)进,桌面端自己的进程边界在[桌面端架构](../internals/desktop.md)。 diff --git a/docs/desktop/pets.md b/docs/desktop/pets.md index 9bbdf505..fd8d06c7 100644 --- a/docs/desktop/pets.md +++ b/docs/desktop/pets.md @@ -1,150 +1,82 @@ +--- +title: 桌面宠物 +nav_title: 桌面宠物 +description: 一只悬浮在桌面上的小机器人,用动作告诉你任务跑到哪了。 +order: 8 +--- + # 桌面宠物 -桌面宠物是 Claude Code Haha Desktop 的透明悬浮窗口。它会用动作反映本机任务状态,并提供一个轻量的活跃任务入口。宠物不会替你审批权限,也不能在悬浮窗口里直接输入追问。 +一只悬浮在桌面上的小机器人。它会用动作反映本机任务的状态——在跑的时候低头忙活,等你审批的时候东张西望,出错了垂头丧气。你去做别的事时,瞄一眼就知道该不该切回来。 -::: info 适用范围 -宠物只在 Electron 桌面端运行,不会显示在 H5 页面中。它不会扩大当前会话、模型或工具的权限。 -::: +它只是个状态提示和跳转入口,不能替你批准权限,也不能在悬浮窗里直接提问。 -## 开启宠物 +## 打开它 -1. 打开 Claude Code Haha Desktop,点击左下角的设置按钮。 -2. 在设置侧边栏选择「宠物」。 -3. 从「内置宠物」中选择一个角色。 +宠物**默认关闭**。打开方式: + +1. 点侧边栏最底部的「设置」。 +2. 选「宠物」分栏。 +3. 从「内置宠物」里挑一个。 4. 打开「显示桌面宠物」。 -5. 按需要调整宠物大小、动画和任务面板。 -![桌面宠物设置页:显示开关、四个内置角色和外观选项](../images/desktop_ui/14_pet_settings_overview.png) +![设置 → 宠物:四只内置宠物与外观参数](../images/app/settings-pets.webp) -应用内置四个完整动画角色: +## 四只内置宠物 -| 角色 | 特点 | -|------|------| -| 搭搭 Dada | 沉稳的协作机器人 | -| 弧弧 Huhu | 拿着铅笔和计划本的路线机器人 | -| 补补 Bubu | 举着修补扳手的修复机器人 | -| 回回 Huihui | 抱着构建齿轮的构建机器人 | +| 角色 | 它是谁 | +|---|---| +| 搭搭 Dada | 沉稳的协作机器人,陪你把想法一块块搭起来 | +| 弧弧 Huhu | 拿着铅笔和计划本的路线机器人,复杂任务也能找到出口 | +| 补补 Bubu | 举着修补扳手的小机器人,最擅长发现并修好裂缝 | +| 回回 Huihui | 抱着构建齿轮的小机器人,新回复一到就精神满满 | -选择新角色后,已经打开的宠物窗口会同步更新。关闭「播放动画」只会停止动作,不会关闭宠物或任务入口。 +换角色时已经打开的宠物窗口会立刻同步。 -## 和宠物互动 +## 和它互动 -宠物窗口支持这些直接操作: +![桌宠悬浮在桌面上](../images/app/pet-desktop.webp) -- **悬停**:宠物空闲时会跳跃,并跟随指针改变视线。 -- **单击宠物**:唤起 Claude Code Haha 主窗口,同时播放挥手动作。 -- **拖动宠物**:按住宠物本体移动到桌面的其他位置;重新打开应用后会尽量恢复上次位置。 -- **右键宠物**:在系统菜单中选择「关闭宠物」。 -- **重新打开**:回到「设置 → 宠物」,重新打开「显示桌面宠物」。 +- **悬停** — 空闲时它会跳一下,视线跟着你的指针转。 +- **单击** — 唤起主窗口,同时挥个手。注意它只是把窗口叫出来,不会自动跳进某条会话。 +- **拖动** — 按住它挪到桌面别的位置,下次打开会尽量回到原处。 +- **右键** — 系统菜单里选「关闭宠物」。这只关掉悬浮窗,不会停掉任何任务。想再开回 设置 → 宠物。 -单击宠物只会唤起主窗口,不会自动进入某条会话。要返回具体任务,请使用宠物旁边的任务面板。 +打开「显示进行中的任务区域」后,有任务在跑时它旁边会出现一块任务面板,按状态分组: -## 查看活跃任务 +- **工作中** — 会话、后台任务或 Agent 正在跑。 +- **等待你处理** — 在等权限审批或其他操作。 +- **需要关注** — 最近一次运行失败了。 -开启「显示进行中的任务区域」后,有活跃任务时,宠物旁边会出现任务面板。关闭这个选项时,活跃任务会收起为数字徽标;点击徽标即可展开。 +点任务行会唤起主窗口并跳到那条会话。权限还是要在主窗口里批。关掉这个开关的话,有活跃任务时只保留一个数字角标,点角标能再展开。 -面板会反映当前仍需要关注的本机会话,例如: +## 外观参数 -- **工作中**:会话、后台任务或 Agent 正在运行。 -- **等待你处理**:会话正在等待权限审批或其他用户操作。 -- **需要关注**:会话最近一次运行失败或出现错误。 +- **宠物大小** — 96 到 192 像素之间。 +- **播放动画** — 关掉之后宠物还在,只是不动了。想要安静一点又不想彻底关掉时用它。 +- **显示进行中的任务区域** — 见上。 +- **默认收起** — 平时只露宠物,需要时再展开任务面板。 -点击任务行会唤起主窗口并返回对应会话。权限仍要在主窗口的会话界面中检查和批准,宠物本身不能批准或拒绝权限。 +## 做一只自己的 -任务完成并回到空闲状态后,会从活跃任务面板中消失;这不代表会话或历史记录被删除。面板只显示最近的活跃会话,不是完整的会话列表。 +点「你的宠物」右边的「添加宠物」。所有图片都在你自己的电脑上处理,不会上传,也不消耗对话额度。 -## 添加自定义宠物 - -在「你的宠物」右侧点击「添加宠物」,可以选择三种做法。图片全程在你自己的电脑上处理,不会上传,也不会消耗对话额度。 - -![创建自定义宠物:三种做法](../images/desktop_ui/15_pet_create_methods.png) - -无论选择哪种方式,都需要填写: - -- **宠物 ID**:最多 73 个字符,只使用小写字母、数字和单个连字符,例如 `docs-bot`。 -- **显示名称**:在宠物列表中显示的名称。 -- **宠物描述**:帮助你区分不同角色和用途。 - -创建成功后,新角色会出现在「你的宠物」中并被自动选中。 - -![成功导入并选中的自定义宠物](../images/desktop_ui/16_pet_custom_result.png) +不管选哪种做法都要先填三个字段:**宠物 ID**(只用小写字母、数字和中间的连字符,比如 `moon-cat`)、**显示名称**、**宠物描述**。 ### 做法一:用一张现成的图 -最省事,大约一分钟。选一张背景透明的静态 PNG 或 WebP,应用会在本地加上呼吸、漂浮和任务状态等轻动画。 +最省事,大概一分钟。选一张背景透明的静态 PNG 或 WebP,应用会给它加上呼吸和上下浮动的轻动画。 -图片需要满足: +这种宠物**不会跑、不会挥手、不会跟着鼠标转头**。想要完整动作,用下面两种。 -- 文件格式为静态 PNG 或 WebP,不支持 APNG 或动态 WebP。 -- 宽和高都在 `32–4096` 像素之间。 -- 总像素数不超过 `16,777,216`。 -- 文件大小不超过 `8 MB`。 +### 做法二:让 AI 画一张动作表 -这种宠物只会轻轻晃动,**不会真的跑起来,也不会跟着鼠标转头**。想要完整动作,用下面的做法二。 +大概十分钟,需要一个能画图的 AI。弹窗里已经准备好一段完整提示词和一张对照模板,点「复制这段提示词」就能直接发给 AI,只要把开头「角色」那两行换成你想要的样子。 -### 做法二:用 AI 画一只会跑会跳的 +要画的是一张 **8 列 × 9 行**的动作表,每格一个动作帧: -大约十分钟,需要一个能画图的 AI。弹窗里会把提示词、对照模板和检查清单都列出来,跟着走即可。 - -#### 第一步:让 AI 画一张动作表 - -打开任意能画图的 AI(即梦、Nano Banana、ChatGPT 画图、Midjourney、Stable Diffusion 都可以;这里只是举例,不构成推荐),把下面这段整个发给它,并把「角色」两行换成你想要的样子: - -```text -帮我画一张游戏角色动作表(sprite sheet)。 - -【角色】 -一只圆头圆脑的橘色小猫,戴着蓝色小围巾,Q 版三头身, -3D 卡通渲染,表面柔和有光泽,颜色明快。 -(把这两行换成你想要的角色,写得越具体越好) - -【整张图的要求】 -· 背景完全透明,不要背景色、不要格子线、不要文字、不要投影 -· 整张图平均分成 8 列 × 9 行,一共 72 个一样大的方格 -· 每格放一个动作帧,角色在格子里居中,四周留一点空隙 -· 所有格子里必须是同一只角色,体型、配色、画风完全一致 -· 用不到的格子保持完全透明 - -【每一行画什么】 -第 1 行 前 6 格:站着不动,轻微呼吸起伏 -第 2 行 全 8 格:向右跑的完整循环,角色始终朝右 -第 3 行 前 4 格:抬起手挥手打招呼 -第 4 行 前 5 格:下蹲、起跳、落地 -第 5 行 全 8 格:失落沮丧,垂头叹气 -第 6 行 前 6 格:原地等待,东张西望 -第 7 行 前 6 格:低头忙碌工作 -第 8 行 全 8 格:头和视线从正上方开始向右慢慢转,经过右上、右边、右下,转到接近正下方 -第 9 行 全 8 格:接着上一行,从正下方继续向左转,经过左下、左边、左上,转回接近正上方 -``` - -一次画不好很正常。可以让它重画,或者说「角色保持不变,只重画第 2 行」。 - -#### 第二步:对照模板检查 - -![动作表模板:8 列 9 行,标注了每一行该画什么](../images/desktop_ui/17_pet_action_sheet_zh.png) - -图出来以后,先看三件事,不对就让 AI 重画: - -1. **背景是透空的,不是白底。** 白底会在桌面上变成一个方块,这是最常见的问题。 -2. **横着 8 格、竖着 9 行**,每格一个动作。 -3. **九行里从头到尾是同一只**,没有中途换脸、换配色、换体型。 - -弹窗里点「把模板存下来」可以把这张模板保存到本地,方便对照排图。 - -#### 第三步:选进来 - -填好 ID、名称和描述,选中刚才那张图即可。**尺寸不用自己调**,详见下面的「尺寸会自动对齐」。 - -### 做法三:我已经有动作图了 - -已经画好动作表、或者手里有做好的成品图集时,用这条路径可以跳过教程,直接填表选图。校验规则和做法二完全一致。 - -### 动作表要画什么 - -作者只需要画 **9 行**,其余两行由应用补齐: - -| 行 | 内容 | 需要的帧数 | -|----|------|-----------| +| 行 | 内容 | 用到的格数 | +|---|---|---| | 1 | 待机:站着不动,轻微呼吸起伏 | 6 | | 2 | 向右跑:完整跑步循环,始终朝右 | 8 | | 3 | 挥手:抬手打招呼 | 4 | @@ -152,62 +84,37 @@ | 5 | 失败:沮丧、垂头、叹气 | 8 | | 6 | 等待:东张西望、原地踱步 | 6 | | 7 | 工作:低头忙碌 | 6 | -| 8 | 视线上半圈:从正上方顺时针转到接近正下方 | 8 | -| 9 | 视线下半圈:从正下方继续转回接近正上方 | 8 | +| 8 | 视线上半圈:从正上方转到接近正下方 | 8 | +| 9 | 视线下半圈:从正下方转回接近正上方 | 8 | -几点说明: +「向左跑」不用画,应用会把第 2 行水平镜像自动补出来。最后两行懒得画也行,重复待机的第一帧就好,只是它不会跟着鼠标转头。 -- **「向左跑」不用画。** 应用会把第 2 行水平镜像,自动生成向左跑的帧。 -- **最后两行懒得画也行。** 直接重复待机的第一帧即可,宠物只是不会跟着鼠标转头,其他动作照常。 -- 每行右侧用不到的格子保持完全透明。 +图出来以后对着弹窗里的模板检查三件事:背景是透空的不是白底、横 8 格竖 9 行、九行里从头到尾是同一只。不对就让 AI 重画,或者说「角色保持不变,只重画第 2 行」。 -### 尺寸会自动对齐 +### 做法三:我已经有动作图了 -选图之后,应用会在本地把动作表整理成运行时需要的 `1536 × 2288` 图集:按 8 列 × 9 行切格、等比缩放到每格 `192 × 208`、把角色在格子里居中、镜像补出向左跑的一行,并补齐运行时用到的其余行。 +已经画好了就走这条,跳过教程直接填表选图。校验规则和做法二完全一样。 -因此: +### 尺寸不用自己算 -- **长宽不必精确。** AI 出的常见尺寸(如 `1024 × 1152`)都可以,比例接近 8:9 时效果最好。参考尺寸是 `1536 × 1872`。 -- **已经是 `1536 × 2288` 的成品图集会原样保留**,不会被重新缩放。 -- 整理后的文件不超过 `8 MB`;超出时会自动改用 WebP 编码。 +选图之后应用会在本地把它整理成运行时需要的图集:按 8 列 × 9 行切格、等比缩放、把角色在格子里居中、镜像补出向左跑的一行,再补齐其余行。 -### 常见问题 +所以**长宽不必是某个精确数值**,AI 常出的 `1024 × 1152` 之类都能用,比例接近 8:9 时效果最好。已经是 `1536 × 2288` 的成品图集会原样保留,不会被重新缩放。 -| 提示 | 原因和处理 | -|------|-----------| -| 这张图没有透明背景…… | 图是白底或彩底导出的。让 AI 重新导出透明背景的 PNG,或用抠图工具去掉背景。 | -| 这张图没法按 8 列 × 9 行切开 | 行列数对不上。确认横着 8 格、竖着 9 行,或改用 `1536 × 2288` 的成品图集。 | -| 读不出这张图 | 选到了动图(APNG / 动态 WebP)或损坏文件。换一张静态 PNG 或 WebP。 | -| 图片太大了 | 原图超过 8 MB。先压缩再选,或让 AI 输出小一点的尺寸。 | -| 已经有一只用这个 ID 的宠物了 | 换一个宠物 ID,或先删掉原来那只(见下面的「存储与删除」)。 | +图片本身要满足:静态 PNG 或 WebP(动图不行)、长宽都在 32–4096 像素之间、总像素不超过 16,777,216、文件不超过 8 MB。 -尺寸、格式或图像内容不符合要求时,应用会拒绝创建并显示对应错误,不会把无效图片加入宠物列表。 +:::warning +最常见的失败是**白底**。白底导出的图在桌面上会变成一个方块,应用会直接拒绝导入。让 AI 重新导出透明背景的 PNG,或者用抠图工具去掉背景。 +::: -## 存储与删除 +## 存在哪、怎么删 -自定义宠物包默认保存在: +自定义宠物包在 `${CLAUDE_CONFIG_DIR:-~/.claude}/cc-haha/pets`,设置页底部有「打开文件夹」按钮。每只宠物一个独立子目录,里面是 `pet.json` 和图片。 -```text -${CLAUDE_CONFIG_DIR:-~/.claude}/cc-haha/pets -``` +界面上目前没有删除按钮。要删的话:先在设置页选回一只内置宠物,点「打开文件夹」,只删掉目标宠物那个子目录,回设置页点「刷新」。不要删整个 `pets` 或 `cc-haha` 目录。 -可以在宠物设置页底部点击「打开文件夹」查看实际目录。每只自定义宠物使用独立子目录,其中包含 `pet.json` 和对应图片。 +手工改过的 `pet.json` 或替换过的图片可能过不了校验,无效的包会被跳过并在设置页提示。 -当前设置页没有自定义宠物删除按钮。需要删除时: +## 边界 -1. 先在设置页选择一个内置宠物,避免继续使用准备删除的角色。 -2. 点击「打开文件夹」。 -3. 只删除目标宠物对应的子目录,不要删除整个 `~/.claude`、`cc-haha` 或 `pets` 目录。 -4. 返回设置页点击「刷新」。 - -手工修改 `pet.json`、替换图片或加入符号链接可能使宠物包校验失败。无效包会被跳过,并在设置页显示提示。 - -## 使用边界 - -- 宠物读取的是当前桌面服务中的任务状态,不是云端常驻监控。 -- 退出 Claude Code Haha 或本机休眠后,宠物不能继续运行任务。 -- 宠物只提供状态提示和会话跳转,不能审批权限、发送追问或替代完整活动面板。 -- 自定义图片导入在本机完成,但之后由你主动运行的会话、模型和集成仍遵循各自的数据与权限边界。 -- H5 可以继续访问对话主流程,但不会显示或远程控制桌面宠物。 - -如果宠物没有出现,先确认「显示桌面宠物」已经开启,再完全退出并重新打开应用。导入失败时,优先核对图片格式、尺寸、文件大小和宠物 ID。 +宠物只在桌面端跑,H5 页面里看不到它。退出应用或电脑休眠后它不会继续工作。窗口置顶、拖动和多显示器的行为受各操作系统限制。 diff --git a/docs/desktop/remote.md b/docs/desktop/remote.md new file mode 100644 index 00000000..e44bbbaa --- /dev/null +++ b/docs/desktop/remote.md @@ -0,0 +1,94 @@ +--- +title: 手机 H5 与 IM 接力 +nav_title: 手机与 IM +description: 扫码在手机上继续同一条会话,或者从微信、飞书直接对话。 +order: 9 +--- + +# 手机 H5 与 IM 接力 + +电脑上跑着的任务,想在手机上看进度、追加一句话,有两条路: + +- **H5 访问** — 手机浏览器打开同一套界面,会话、消息、附件、权限按钮都在。 +- **IM 接入** — 在微信、钉钉、WhatsApp、Telegram、飞书里直接和 Claude 对话。 + +两条路都需要你的电脑开着、应用在跑。它们把桌面服务开放出来,不是把数据搬到云端。 + +## H5 访问 + +![设置 → H5 访问:二维码与令牌](../images/app/settings-h5.webp) + +### 开启 + +1. 打开 设置 → H5 访问。 +2. 打开「启用 H5 访问」,读一遍风险提示后确认。 +3. 点「生成令牌」,页面上会出现二维码和 H5 链接。 +4. 用手机扫码,或者点「复制扫码链接」发到自己的设备上。 + +扫出来的链接里带着服务器地址和令牌,手机浏览器打开后会把连接信息存下来,之后直接打开就能用。 + +![手机端的对话界面与文件变更卡](../images/app/h5-session.webp) + +### 令牌是访问凭据 + +拿到这条链接的人就能访问你桌面上暴露出来的能力。所以: + +- 不要把带令牌的链接贴进群聊、公开 Issue、日志截图或任何公开页面。 +- 只在可信网络里开。公网使用请自己套 HTTPS、VPN 或访问控制,不要只靠一个长期令牌。 +- 怀疑泄露了就点「重新生成令牌」。旧二维码和旧令牌会立刻失效——只是关掉再打开 H5 不会换令牌。 + +不用的时候关掉。关掉会立即拒绝远程访问,但令牌保留着,下次开还是同一个。 + +:::warning +H5 默认关闭,它也不是公开服务。开启前先确认你在自己信得过的网络里。 +::: + +### 访问主机与固定端口 + +「访问主机 / IP」填电脑当前的局域网 IP,比如 `192.168.1.20`,端口用当前服务端口。换了 Wi-Fi 或插拔网线之后原 IP 可能失效,页面会提示你切到可用的那个。 + +如果你自己有反向代理,这里可以直接填完整 URL,比如 `https://cc.example.com`,并把这个域名加进「允许的来源」。 + +**固定端口**在这几种情况下值得设:手机书签要长期不变、防火墙只放行指定端口、Nginx 或 Caddy 固定转发一个上游端口。端口要在 1024–65535 之间,改完重启应用才生效——页面会告诉你现在还跑在哪个端口上。 + +### 手机锁屏不会打断任务 + +「断连保活」解决的是这个问题:手机锁屏、切后台或者短暂断网时,**正在执行的任务不会被停掉**,它会在后台跑完,重连就能看到结果。 + +只有当任务已经空闲、并且没有任何设备连着时,才会在保活时间到期后停掉对应进程。默认 30 秒,可配范围是 5 秒到 24 小时。出门远程操作可以调大一些,比如 600。 + +### 手机上能做什么 + +会话列表和项目切换、发消息、停止、看流式回复、图片和文件附件、权限按钮、AI 提问、`@` 引用文件、复制和 Fork——对话主流程都在。 + +桌面工作区、内嵌终端、原生「打开方式」、Computer Use 授权和桌面宠物不在 H5 里。 + +## IM 接入 + +![设置 → IM 接入:配对管理与五个平台](../images/app/settings-im.webp) + +设置 → IM 接入 支持五个平台,接入方式各不相同: + +| 平台 | 怎么接 | +|---|---| +| 微信 | 在设置页生成二维码,用微信扫码绑定账号 | +| 钉钉 | 扫码一键创建并授权机器人,也可以手动填 Client ID / Secret | +| WhatsApp | 生成二维码,在 WhatsApp 的「已关联设备」里扫码 | +| Telegram | 找 @BotFather 要一个 Bot Token 填进来 | +| 飞书 | 填 App ID 和 App Secret;没有机器人的话可以用页面上的模板一键创建 | + +### 绑定账号 ≠ 允许对话 + +这是最容易踩的一步:**扫码只绑定了账号能力,具体哪个人能和 Bot 对话是另一回事。** + +在「配对管理」里点「生成配对码」,然后用你自己的 IM 账号私聊 Bot 把这串码发过去,绑定才算完成。也可以在「允许的用户」里直接填用户 ID 列表。两者都为空时默认拒绝所有人——这是有意为之。 + +已配对的用户会列在下面,可以随时解绑,解绑后需要重新配对。 + +### 其他设置 + +- **默认项目** — 新 IM 会话用哪个工作目录。留空则用当前用户工作目录。 +- **流式卡片模式** — 实时更新消息内容,读起来更像在看它打字。 +- **权限请求** — 钉钉可以配互动卡片模板 ID,用卡片按钮授权;不配的话所有平台都用 `/allow`、`/always`、`/deny` 文本命令。 + +五个平台各自的申请流程、权限配置和排查步骤,见[IM 接入](../im/index.md)。 diff --git a/docs/desktop/schedule.md b/docs/desktop/schedule.md new file mode 100644 index 00000000..1bc837c4 --- /dev/null +++ b/docs/desktop/schedule.md @@ -0,0 +1,56 @@ +--- +title: 定时任务 +nav_title: 定时任务 +description: 让 Claude 按计划自动跑,比如每天早上审一遍昨天的提交。 +order: 5 +--- + +# 定时任务 + +把一段提示词存下来,让它按时间自动跑。典型用法是每天早上审查昨天的提交、每周整理一次待办、每小时检查一遍某个服务的日志。 + +## 从哪进去 + +点侧边栏的「定时任务」打开任务列表页,右上角「+ 新建任务」。 + +任务列表页顶部有一条常驻提示,说的就是下面这件事。 + +:::warning +定时任务只在桌面应用打开、且电脑处于唤醒状态时运行。合上盖子、退出应用、关机,任务都不会跑,也不会补跑。它不是云端调度器。 +::: + +## 新建任务的每个字段 + +![「新建定时任务」弹窗:名称、描述、提示词、频率与通知](../images/app/schedule-create.webp) + +- **名称**(必填)— 列表里显示的标识,建议用短横线风格,比如 `daily-code-review`。 +- **描述**(必填)— 一句话说明这个任务干什么,方便以后自己认出来。 +- **提示词** — 真正发给 Claude 的内容。把上下文写足:要看哪些东西、关注什么、输出成什么格式。它跑的时候你不在旁边,没人给它补充说明。 +- **权限** — 固定为「所有权限」,表单里不让选。提示词编辑器下面会直接标出这次运行的目录范围。因此:**工作目录一定要选到最小范围,提示词也要先自己读一遍**。 +- **模型** — 单独指定,不跟随会话的默认模型。日常巡检类任务用便宜的模型就够。 +- **工作目录** — 任务运行的项目目录。也可以开「独立工作树」,让任务在隔离的 Git worktree 里跑,不碰你的主分支。 +- **频率** — 每 N 分钟 / 每 N 小时 / 每天 / 工作日(周一至周五)/ 指定星期几 / 每月 / 自定义 Cron 表达式。选「每天」这类时右边会多出一个时间选择器。自定义 Cron 的格式是「分 时 日 月 星期」,填错了下面会当场提示无效。 +- **完成后推送通知** — 打开后可选通知渠道:桌面系统通知,以及已经配好的 IM 渠道。没配过 IM 会提示你去 设置 → IM 接入 配置,步骤见[手机 H5 与 IM 接力](./remote.md)。 + +系统通知需要在 设置 → 通用 里先打开「启用系统通知」并授予系统权限。 + +任务会带一点随机延迟触发,避免整点一堆任务同时开跑。 + +## 跑起来之后 + +任务卡片上有这些操作: + +- **立即执行** — 不等下次触发,现在就跑一遍。新建任务后建议先手动跑一次,确认提示词写对了。 +- **日志** — 打开执行记录,每次运行一行,带状态(运行中 / 已完成 / 失败 / 超时)和耗时。点「摘要」看输出片段,点「查看完整对话」跳到那次运行对应的完整会话。 +- **编辑** — 改任何字段,包括频率。 +- **禁用 / 启用** — 暂时停掉但不删。 +- **删除** — 连同它的所有日志一起永久删除。 + +列表页顶部三个数字是总任务数、活跃、已禁用。 + +## 几个实践建议 + +1. **先手动跑一次再定频率。** 提示词的毛病在第一次运行就会暴露。 +2. **工作目录选窄一点。** 任务以完整权限运行,目录范围就是它的活动边界。 +3. **频率别太密。** 每分钟跑一次的任务很快会烧掉不少 token,也会把日志刷满。 +4. **让它输出结论而不是过程。** 在提示词里明确要求「最后用三行总结」,通知里才看得下去。 diff --git a/docs/desktop/sessions.md b/docs/desktop/sessions.md new file mode 100644 index 00000000..105fb810 --- /dev/null +++ b/docs/desktop/sessions.md @@ -0,0 +1,100 @@ +--- +title: 会话、权限与审阅 +nav_title: 会话与权限 +description: 一条会话从提问到审阅改动的全过程,以及权限弹窗该怎么点。 +order: 1 +--- + +# 会话、权限与审阅 + +一条会话就是你和 Claude 的一次完整合作:你提要求,它读文件、跑命令、改代码,每一步都留在对话里可以回看。这一篇讲会话界面上的每个东西是干什么的。 + +## 开一条会话 + +点侧边栏的「新建会话」,或按 `⌘N`(Windows / Linux 是 `Ctrl+N`)。空会话首屏只要求你做一件事:选一个项目目录。选完就能提问了,模型和权限沿用你在设置里的默认值。 + +会话开出来是一个标签页,可以像浏览器一样开很多个并排跑。标签上有小圆点表示这条会话还在运行;关一个正在跑的标签会先问你是「保持运行」还是「停止并关闭」。 + +会话标题下面那行小字是元信息:项目路径、分支、模型。想换项目就新建一条会话,一条会话绑定一个目录。 + +## 读懂对话流 + +![一条完整会话:用户提问、工具调用卡、思考块、文件编辑与内联 diff](../images/app/session-main.webp) + +Claude 干活时不是一句话回你,中间会插进来几种卡片: + +- **工具调用卡**:读文件、搜索、执行命令都会留一张卡。连续的同类操作会自动合并成一行,比如「读取了 6 个文件」「编辑了 3 个文件」,点开才展开细节。日常扫一眼就好,出问题时再展开看参数和输出。 +- **思考块**:模型在动手前的推理过程,标题写「思考中」,结束后变成「已思考」。默认折叠。 +- **文件编辑**:直接在对话里显示内联 diff,绿加红减,不用切到别处看。 +- **Claude 需要你的输入**:它拿不准时会反问,给几个选项按钮,也可以自己输入回答。 + +对话很长时,`⌘F` 打开页内查找,可以在当前对话里跳上一个 / 下一个匹配。`⌘K` 是全局搜索,跨所有历史会话找内容。 + +## 权限弹窗:三个按钮怎么选 + +![「允许 Claude Edit index.html?」权限询问卡,下方是变更预览](../images/app/session-permission.webp) + +默认权限模式下,Claude 每次要改文件或跑高风险命令,都会停下来问你。弹窗里会先给出这次改动的预览,然后是三个按钮: + +- **允许** — 只放行这一次。下次同样的操作还会再问。 +- **本次会话允许** — 这条会话里同类操作以后都不问了。关掉会话就失效。 +- **拒绝** — 不执行。Claude 会收到拒绝信号,换个做法继续。 + +拿不准就选「允许」,多问几次不亏。看不懂它要干什么时点「显示完整输入」,能看到原始参数。 + +### 五档权限模式 + +输入框工具栏上的权限按钮可以整体调松紧: + +| 模式 | 它会做什么 | +|---|---| +| 询问权限 | CLI 请求时确认文件编辑和高风险命令 | +| 自动接受编辑 | Claude 无需询问即可写入磁盘 | +| 自动模式 | Claude 会审查工具调用,并执行其认为安全的操作 | +| 计划模式 | 仅架构和推理,不操作文件 | +| 跳过权限 | 对 Shell 和文件系统的完整工具访问 | + +「自动模式」和「跳过权限」第一次开启需要确认一次。计划模式下 Claude 只出方案不动文件,方案写完会弹出「准备开始编码?」,你可以批准或者让它继续改方案。 + +会话正在跑的时候权限模式是锁住的,等这一轮结束才能改。 + +:::warning +「跳过权限」等于把 Shell 和整个文件系统交出去,只在你能随时恢复的隔离环境里用。 +::: + +## 改错了怎么撤 + +Claude 每完成一轮,对话里会出现一张「{n} 个文件已更改」的卡片,列出这一轮碰过的文件。卡片上有两个动作: + +- **撤销当前轮次** — 回滚最近一次回复,并把这一轮改过的文件恢复回去。 +- **回滚到这一轮之前** — 对历史轮次用,把会话和文件一起退回那个检查点。 + +点了会先弹确认框。有些轮次没有可用的文件检查点,这时只回滚对话,不动文件——提示语里会说清楚。 + +## 活动面板:它现在在忙什么 + +标签栏右侧第一个按钮打开活动面板,里面按区块列出这条会话的所有并行工作: + +- **任务** — Claude 自己维护的待办清单,顶部显示「任务进度 3/7」。 +- **SubAgent** — 它派出去的子 Agent,点进去能看子 Agent 完整的运行记录。 +- **后台任务** — 挂在后台跑的命令和工作流,可以单独停掉某一个。 +- **团队** — 用到 Agent Team 时,每个成员一行,可以点进去直接给某个成员发消息。 + +后台跑的子 Agent 的工具活动也会冒泡到这里,不用等它跑完才知道它在干什么。 + +## 输入框能干的事 + +![输入 `/` 弹出的斜杠命令面板](../images/app/composer-slash.webp) + +- **`/` 斜杠命令** — 输入 `/` 弹出命令面板。`/status` 看会话状态和用量、`/context` 看上下文占用、`/compact` 压缩上下文、`/review` 审查改动、`/commit` 提交、`/memory` 打开项目记忆、`/doctor` 打开诊断检查。 +- **`@` 引用文件** — 输入 `@` 弹出文件搜索,选中的文件会以路径形式附到消息上。 +- **附件** — 点 `+` 选文件,或者直接把文件拖进输入框、把截图粘贴进来。图片、PDF、目录都收。 +- **上下文用量环** — 那个小圆环显示当前上下文用掉多少,鼠标停上去能看到已用 / 剩余 / 窗口大小。快满了就 `/compact` 一下。 +- **模型与推理强度** — 随时换模型;推理强度有低、中、高、极高、最大五档,不支持的模型会自动忽略。 +- **运行位置** — 显示当前项目和分支。Git 项目可以在这里切分支,或者打开「独立工作树」,把这次试验隔离在主分支之外,详见[工作区](./workspace.md)。 + +发送方式默认是 Enter 发送、Shift+Enter 换行,可以在 设置 → 通用 里改成 `Ctrl/Cmd+Enter` 发送。`⌘.` 停止当前生成。 + +## Fork 一条对话 + +历史消息上有「Fork 一个新对话」。从这条消息分叉出一条新会话,之前的上下文都在,之后的走向重来。想换个思路试试但又不想丢掉现在这条线时用它。 diff --git a/docs/desktop/settings.md b/docs/desktop/settings.md new file mode 100644 index 00000000..8371b7ae --- /dev/null +++ b/docs/desktop/settings.md @@ -0,0 +1,127 @@ +--- +title: 设置速查 +nav_title: 设置 +description: 16 个设置分栏,每栏能配什么、什么时候需要动它。 +order: 6 +--- + +# 设置速查 + +点侧边栏最底部的「设置」打开。左边 16 个分栏,顺序固定。这一篇按这个顺序过一遍,说清每栏能配什么、什么时候才需要动它。 + +## 服务商 + +管理模型接入。支持 Claude 官方、ChatGPT 官方、Grok 官方三种账号登录(不需要 API 密钥),也支持填 API 密钥接入任意 Anthropic / OpenAI 兼容的服务商。 + +第一次用必须来这里,之后基本不用动。详细步骤见[连接模型服务](../start/models.md)。 + +## 通用 + +最常动的一栏,配的都是「用起来顺不顺手」的东西。 + +![设置 → 通用:配色主题、语言、输出风格、默认权限](../images/app/settings-general.webp) + +- **配色主题** — 六套:纯白(默认)、纸墨、经典暖色、青瓷、墨夜、墨夜蓝。另有「跟随系统」开关,打开后可以分别指定浅色模式和深色模式各用哪一套。 +- **语言** — 界面显示语言。 +- **回复语言** — 让 Claude 始终用某种语言回复,和界面语言分开设。 +- **输出风格** — 默认 / 解释型 / 学习型。「解释型」会额外讲清实现选择和代码库里的模式,「学习型」会让你自己动手写一小段。改完对正在跑的会话不生效,新建会话才用新风格。 +- **默认会话权限** — 新建会话时用哪一档权限模式。每条会话里还能单独改。 +- **推理强度** 与 **思考模式** — 新会话的默认思考档位。关掉思考模式后,DeepSeek 这类需要显式非思考参数的模型会收到对应设置。 +- **消息发送方式** — Enter 发送(Shift+Enter 换行),或 `Ctrl/Cmd+Enter` 发送。 +- **系统通知** — 授权确认、回复完成、定时任务结果走系统通知中心。开启时会请求系统权限。 +- **网络** — 三选一:直连(明确绕过系统代理)、系统代理(按目标地址动态遵循系统规则)、手动代理(填 `http://user:password@127.0.0.1:7890` 这样的地址)。下面还有「AI 请求超时」,默认值不够用时可以加大到 1800 秒。应用自身的更新下载走另一套代理设置,在「关于」里。 +- **WebSearch** — 联网搜索走哪条路。自动模式会对 Claude 模型优先用原生 WebSearch,失败或换成非 Claude 模型时再用 Tavily / Brave,这两个需要你自己填 API Key。 +- **自动做梦** — 后台定期整理和压缩记忆文件。默认关闭,因为它会额外消耗 token。 +- **界面缩放** — 整体放大缩小,也可以用 `⌘+` / `⌘-` 快捷键,`⌘0` 回到 100%。 +- **数据存储位置** — 低频高级设置。默认用系统目录 `~/.claude`,也可以改成你指定的绝对路径。切换后会话记录、技能、MCP、插件、服务商配置全部从新目录读取,需要重启应用生效,两个目录之间不会自动合并或迁移。 + +![同一条会话在「墨夜」深色主题下的样子](../images/app/session-dark.webp) + +## H5 访问 + +在手机浏览器里继续同一条会话。默认关闭。见[手机 H5 与 IM 接力](./remote.md)。 + +## IM 接入 + +从微信、钉钉、WhatsApp、Telegram、飞书直接和 Claude 对话,并管理配对用户。见[手机 H5 与 IM 接力](./remote.md)和[IM 接入](../im/index.md)。 + +## 终端 + +内嵌一个真实的宿主机 Shell,用来装插件、技能、MCP 这类需要命令行的东西。桌面端已经内置 `claude-haha` 命令,文档里写 `claude <参数>` 的地方都可以换成 `claude-haha <参数>`。 + +Windows 用户可以在这里指定启动 Shell(系统默认 / PowerShell 7 / Windows PowerShell / 命令提示符 / 自定义可执行文件),以及一个 Bash 路径——工具调用 `grep`、`sed` 这类 Unix 命令时会用到,通常指向 Git Bash。 + +## MCP + +添加外部工具和数据源。支持 STDIO、Streamable HTTP、SSE 三种传输方式,配置范围和 CLI 保持一致: + +- **项目私有** — 只对你生效,但绑定到某一个项目。 +- **项目共享** — 写进项目的 `.mcp.json`,团队成员共享。 +- **全局用户** — 写进你的全局配置,所有项目生效。 + +顶部三个数字是服务总数、当前已连接、需要处理。STDIO 类型的命令会直接在你的机器上运行,Node、Python、Bun 这些运行时需要你自己装好并保证在 PATH 里。 + +## Agents + +浏览已安装的 Agent,创建自己的。见[子 Agent 与任务拆分](./agents.md)。 + +## 技能 + +本机所有可用技能,按来源分组,可以直接读技能的正文和源码。见[技能与技能市场](./skills.md)。 + +## 记忆 + +查看和编辑 Claude 为每个项目写的 Markdown 记忆文件。左边选项目,中间选文件,右边编辑或预览渲染结果。这些文件在 `~/.claude/projects//memory/`,CLI 运行时会加载它们。 + +会话里也能直接用 `/memory` 跳到这里。想知道记忆是怎么写入和召回的,看[记忆系统](../internals/memory.md)。 + +## 插件 + +插件把技能、Agent、Hook、MCP 服务打包在一起。这里能看已安装插件、健康状态和它们各自暴露了哪些能力,支持启用、禁用、更新、卸载,也能多选批量操作。 + +启用或禁用之后要点一次「应用变更」,才会把插件变更重新应用到当前运行时。 + +## 宠物 + +一只悬浮在桌面上的小机器人。默认关闭。见[桌面宠物](./pets.md)。 + +## Computer Use + +让 Claude 读屏幕、点鼠标、敲键盘。装好运行环境并授予系统权限之前用不了。见[Computer Use](./computer-use.md)。 + +## Token 用量 + +![设置 → Token 用量:热力图与统计卡](../images/app/settings-usage.webp) + +基于本机 Claude Code 会话记录统计出来的用量看板,全部在本地算,不上传。 + +- 顶部是累计 Token 数、峰值、最长任务时长、连续活跃天数。 +- 中间是热力图,可以切每日 / 每周 / 累计三种口径,点某一天看当天的会话数、Token、消息和工具调用。 +- 下面是活动洞察:活跃率、最常用模型、用过哪些技能、新增与缓存命中的 Token 比例、估算成本。估算成本不含未定价的模型,界面上会写明跳过了几个。 + +## Trace + +记录每条会话的模型请求链路——请求、响应、状态事件、耗时都留档,用来排查卡住、失败和异常等待。开关不在这一栏:要先在 设置 → 通用 里打开「收集 Agent Trace」,这一栏才会有数据。 + +开启后新会话会把精简记录写到本机 traces 目录。已有记录在关掉之后仍然能看,只是不再写新的。Trace 列表支持搜索、筛选(全部 / LLM / 工具 / 错误)、单独在新窗口打开,也能删除某个会话的 Trace 而不影响聊天记录。 + +## 诊断 + +出问题时来这里。记录服务端和 CLI 的启动、服务商、会话运行错误。 + +- 顶部是日志大小、事件数、24 小时内的警告数、保留策略。 +- 「最近事件」列出具体错误,每条带事件 ID,可以单独复制。 +- **导出诊断包** / **复制错误摘要** / **复制 Issue 报告** — 提 issue 时用后两个,格式已经整理好。 +- **Doctor** — 检查用户和当前项目的配置状态,只读不修改,给出健康 / 未配置 / 缺失 / 无效的清单。会话里输入 `/doctor` 也能直接打开。 +- **重置安全 UI 状态** — 只清标签页、主题、缩放这几个可再生的界面键。聊天历史、模型配置、技能、MCP、IM 和 OAuth 始终受保护,不会被它碰到。 +- **本地索引** — 显示 SQLite 派生索引的状态和大小,可以重建。重建只影响索引,不删源对话。 + +:::info +Issue 报告和导出包会尽力脱敏,省略聊天内容、文件内容、完整环境变量和 API 密钥。分享前还是自己扫一眼,看有没有内网域名、用户名或路径。 +::: + +## 关于 + +版本号、更新日志、GitHub 仓库、反馈入口。 + +「应用更新」会检查 GitHub Releases,下载后自动重启安装。更新下载走的是独立的代理设置,和 设置 → 通用 里的网络设置互不影响——公司网络下更新卡住时,来这里配「高级更新代理」。 diff --git a/docs/desktop/skills.md b/docs/desktop/skills.md new file mode 100644 index 00000000..bad03d8c --- /dev/null +++ b/docs/desktop/skills.md @@ -0,0 +1,76 @@ +--- +title: 技能与技能市场 +nav_title: 技能 +description: 技能是给 Claude 的一套现成做法,从市场装之前要看清楚什么。 +order: 4 +--- + +# 技能与技能市场 + +技能是别人写好的一套做法,装上以后 Claude 遇到对应场景就会照着做。比如一个「PDF 处理」技能会告诉它用哪个库、按什么顺序拆页、遇到加密文件怎么办——不用你每次都重新交代一遍。 + +**和 Agent 的区别**:Agent 是一个会干活的分身,有自己的上下文和工具范围;技能是一段知识和流程,被 Agent 加载来用。派 Agent 是「找个人去办」,装技能是「给他一本手册」。 + +## 技能市场 + +![技能市场:卡片列表、安全徽标和来源筛选](../images/app/skill-market.webp) + +点侧边栏的「技能市场」。它同时聚合 **ClawHub** 和 **SkillHub** 两个来源,列表往下滚会自动加载更多,没有「加载更多」按钮。 + +顶部三个筛选: + +- **来源** — 全部 / ClawHub / SkillHub。 +- **安全状态** — 见下。 +- **安装状态** — 全部技能 / 已安装 / 未安装。 + +搜索框按名称和关键词搜。 + +### 安全徽标怎么读 + +每张卡片右上角有一个安全标记,它反映的是**来源方**的扫描结果,不是本应用的审计结论: + +| 徽标 | 含义 | +|---|---| +| 已认证 | 发布者已认证,且通过来源方安全扫描 | +| 扫描无风险 | 来源方安全扫描未发现风险 | +| 未审计 | 来源方没提供安全审计数据 | +| 存在风险 | 来源方扫描标记它有潜在风险 | + +「未审计」不等于不安全,只是没人查过。「存在风险」的技能除非你自己看懂了它在干什么,否则别装。 + +## 安装之前 + +技能会带来新的工具、脚本和外部依赖,装上以后 Claude 就可能去执行它们。市场页面顶部那条免责声明说的是实话:这些技能来自社区第三方来源,本应用不对内容做安全审计。 + +安装前建议: + +1. 在详情页切到「文件」标签,把 `SKILL.md` 和配套脚本读一遍。 +2. 拿不准就让 Claude 先扫一遍——「帮我看看这个技能的文件里有没有可疑的东西」。 +3. 确认你真的需要它。装得多不等于更强,每个技能都会占上下文。 + +点「安装」会弹确认框,里面写清了安装位置、安全提示和「安装完成后,技能将在新会话中生效」。**已经开着的会话不会立刻拿到新技能**,需要新建一条。 + +卸载在同一个位置,会删掉本地对应目录下的文件。 + +## 已安装的技能 + +设置 → 技能 里能看到本机所有可用技能,按来源分组: + +- **用户** — 你装的,在 `~/.claude/skills/`。 +- **项目** — 跟着仓库来的。 +- **插件** — 某个插件打包带来的。 +- **内置** — 应用自带的。 + +每条会显示入口文件、文件数和预估 token 占用。点进去可以在「文档模式」和「代码模式」之间切换,直接读技能的正文和源码文件。 + +### `.agents/skills` 跨客户端约定 + +除了 `~/.claude/skills/`,桌面端也会读 `~/.agents/skills/`。这是一个开放标准目录,Codex、Cursor、Gemini CLI 等客户端共享同一份技能——装一次,几个工具都能用。来自这个目录的技能在列表里会带一个 `.agents` 标记。 + +项目里的 `.agents/skills/` 同理,随仓库分发,不是你自己装的。 + +:::warning +跨客户端目录意味着别的工具装的技能这里也会生效。定期扫一眼 设置 → 技能 的列表,确认里面没有你不认识的东西。 +::: + +想了解技能的加载机制和文件格式,看[技能系统原理](../internals/skills.md)。 diff --git a/docs/desktop/workspace.md b/docs/desktop/workspace.md new file mode 100644 index 00000000..2111ae9c --- /dev/null +++ b/docs/desktop/workspace.md @@ -0,0 +1,77 @@ +--- +title: 工作区 +nav_title: 工作区 +description: 右侧工作区:看改了哪些文件、逐行评审 Diff、在应用里预览网页。 +order: 2 +--- + +# 工作区 + +对话告诉你 Claude 说了什么,工作区告诉你它真的改了什么。它是右侧一块可以随时拉出来的面板,和对话并排,不用切窗口。 + +## 打开工作区 + +点标签栏右侧的文件夹图标。再点一次收起。面板左边界可以拖动改宽窄。 + +面板顶部有个「文件 ⇄ 浏览器」的切换: + +- **文件** — 看项目文件和 Git 改动,做 Diff 评审。 +- **浏览器** — 内置浏览器,用来预览刚改完的页面。 + +## 已更改文件 / 所有文件 + +![工作区「已更改文件」列表,每行带状态标记和增删行数](../images/app/workspace-changes.webp) + +文件模式下有两个视图: + +- **已更改文件** — 只列这个 Git 仓库里当前有改动的文件,每行带状态(已修改 / 已新增 / 已删除 / 已重命名 / 未跟踪)和增删行数。这是审阅时最常待的地方。 +- **所有文件** — 完整目录树。上面的搜索框可以搜整个项目的文件名,也能搜还没展开的目录。 + +点文件名打开预览,双列显示 Diff 或单文件内容。预览标签会累积,像编辑器的标签一样切换。 + +不是 Git 仓库时「已更改文件」会直接说明,不是出错。 + +任何文件都可以右键复制路径,或者点「添加到聊天」把它作为上下文塞回输入框。 + +## Diff 评审:给某一行留话 + +![Diff 评审界面,左右对照并带语法高亮](../images/app/workspace-diff.webp) + +Diff 保留旧行和新行,带语法高亮。真正有用的是行级评论: + +1. 点某一行,右边出现评论框。 +2. 想评一段而不是一行,按住 `Shift` 点起止行——只能选同一侧、同一变更块里的连续行。 +3. 写下你希望这里怎么改,点「提交」。 +4. 评论会连同文件路径、行号和那几行代码一起回到输入框,直接发给 Claude。 + +这比在对话里描述「那个函数里判断空值那行」准确得多。 + +如果你在写评论的过程中 Claude 又改了这个文件,界面会提示差异已更新,需要重新选行——这是防止评论落到错误的位置。 + +:::tip +被拒绝的工具调用不会写盘,也就不会出现在已更改文件里。交付前还是建议自己再过一遍 `git diff`。 +::: + +## 独立工作树:把试验关在笼子里 + +在输入框的运行位置里可以选「独立工作树」。选了以后,这条会话会拿到一个隔离的 Git worktree,Claude 的所有改动都发生在那里,你当前分支和工作目录一个字都不会动。 + +什么时候用它: + +- 想让 Claude 大改一版,但不确定要不要留。 +- 当前目录有未提交的改动,直接切分支会被 Git 拦住。 +- 目标分支已经在别的工作树里检出了。 + +会话结束后临时工作区会被清理,历史记录仍然可以看,但那时想继续就得回原项目新建会话——界面会提示你。 + +## 内置浏览器 + +![内置浏览器预览刚改完的页面](../images/app/workspace-preview.webp) + +把工作区切到「浏览器」,地址栏里填本地开发地址或者任意网址就能预览。这里有三个专门为「让 Claude 看见」设计的按钮: + +- **截图** — 把当前页面画面带回会话,Claude 就能看到渲染结果。 +- **选择元素** — 在页面上点一个元素,它的选择器、位置和截图会一起作为上下文交给 Claude。改样式时特别省事。 +- **缩放** — 调预览比例,看响应式布局。 + +浏览器里的登录态和 Cookie 和普通浏览器一样是真实的。做公开演示或者截图发出去之前,请换成不需要登录的页面。 diff --git a/docs/en/agent/index.md b/docs/en/agent/index.md deleted file mode 100644 index cd0e85fb..00000000 --- a/docs/en/agent/index.md +++ /dev/null @@ -1,129 +0,0 @@ -# Claude Code Multi-Agent System Documentation - -> Complete guide and technical reference for multi-agent orchestration - ---- - -## Documentation Index - -### [01-usage-guide.md](./01-usage-guide.md) — Usage Guide - -A comprehensive user-facing manual covering: - -- **Agent Tool**: Parameter reference, spawn methods, background execution -- **Six Built-in Agents**: general-purpose, Explore, Plan, verification, claude-code-guide, statusline-setup -- **Background Tasks**: Asynchronous execution, progress tracking, completion notifications -- **Agent Teams**: Team creation, member collaboration, message communication -- **Worktree Isolation**: Independent environments, branch management, secure contexts -- **Custom Agents**: Definition format, tool pool configuration, system prompts - -**Target Audience**: All Claude Code users - ---- - -### [02-implementation.md](./02-implementation.md) — Implementation Details - -A deep technical reference for developers covering: - -- **Architecture Overview**: 5 agent categories, 4 spawn paths -- **Agent Spawn Flow**: Detailed walkthrough of Sync / Async / Fork / Teammate paths -- **Tool Pool System**: Three-layer filtering, constant definitions, permission mapping -- **Context Passing**: CacheSafeParams, system prompt construction, fork cache optimization -- **Teams Internals**: TeamFile structure, mailbox system, inbox polling, message routing -- **Background Task Engine**: LocalAgentTask lifecycle, progress tracking, notification queue -- **Permission Synchronization**: Team-level permissions, mode propagation, bubble mode -- **End-to-End Data Flow**: From Agent Tool invocation to result delivery - -**Target Audience**: Contributors, architects, and developers seeking deep implementation understanding - ---- - -### [03-agent-framework.md](./03-agent-framework.md) — Agent Framework Deep Dive - -Deconstructing the architecture behind Claude Code's agent framework from source code, covering: - -- **Core Agent Loop**: AsyncGenerator state machine, five-phase while(true) loop -- **System Prompt Engineering**: Layered construction, cache boundary, CLAUDE.md loading -- **Tool System Design**: Full lifecycle management, three-stage registration, 7-step execution pipeline -- **Context Management & Compression**: Four-level progressive compression, system context injection -- **Skills & Plugin Ecosystem**: Skill definition and discovery, plugins, hooks, MCP integration -- **Permission & Security Model**: Layered permission model, rule pattern matching -- **Fault Recovery Mechanisms**: 6 built-in recovery strategies, model fallback -- **Comparison with LangChain/ReAct**: Architecture paradigm differences, why not ReAct -- **Why Claude Code Is So Good**: 7 core design principles - -**Target Audience**: Architects studying AI agent design, AI application developers, technical researchers - ---- - -## Illustration Notes - -All diagrams use a dark background (#1a1a2e) with Claude Code Haha orange-blue accent (#FF7A00), consistent with Claude Code's official documentation style. - -| Image | Description | Document | -|-------|-------------|----------| -| `01-agent-overview.png` | Multi-Agent System Overview — Architecture panorama | Usage Guide | -| `02-agent-types.png` | Six Built-in Agents — Type comparison matrix | Usage Guide | -| `03-spawn-flow.png` | Agent Spawn Flow — Four-path decision tree | Usage Guide | -| `04-agent-teams.png` | Agent Teams Collaboration — Team communication topology | Usage Guide | -| `05-architecture.png` | Implementation Architecture — Core module relationships | Implementation | -| `06-context-passing.png` | Context Passing — CacheSafeParams data flow | Implementation | -| `07-tool-pool.png` | Tool Pool System — Three-layer filtering pipeline | Implementation | -| `08-background-task.png` | Background Task Engine — Lifecycle state machine | Implementation | -| `09-teams-mailbox.png` | Teams Mailbox System — Message routing topology | Implementation | -| `10-fork-cache.png` | Fork Cache Optimization — Byte-level consistent sharing | Implementation | -| `11-agent-framework-overview.png` | Agent Framework Overview — Core component relationships | Framework Deep Dive | -| `12-agent-core-loop.png` | Core Agent Loop — Five-phase state machine | Framework Deep Dive | -| `13-system-prompt-pipeline.png` | System Prompt Pipeline — Layered cache pipeline | Framework Deep Dive | -| `14-context-compression.png` | Context Compression — Four-level progressive strategy | Framework Deep Dive | - ---- - -## Quick Start - -### For Users - -1. Read the [Usage Guide](./01-usage-guide.md) -2. Learn about the six built-in agents and their use cases -3. Try spawning subagents using the Agent tool in a conversation -4. Explore multi-agent collaboration with Agent Teams - -### For Developers - -1. Read the [Implementation Details](./02-implementation.md) -2. Browse the source code: - - `src/tools/AgentTool/` — Agent tool implementation - - `src/tools/TeamCreateTool/` — Team creation - - `src/tools/SendMessageTool/` — Inter-agent communication - - `src/utils/swarm/` — Swarm collaboration infrastructure - - `src/utils/forkedAgent.ts` — Fork agent context - - `src/tasks/` — Task management system -3. Understand the four spawn paths and context passing mechanisms - ---- - -## Core Concepts Quick Reference - -| Concept | Description | -|---------|-------------| -| **Agent Tool** | Primary entry point — accepts a prompt + subagent_type to spawn a subagent | -| **Subagent** | An independent child agent that executes tasks with its own tool pool and permissions | -| **Fork Agent** | A forked agent that inherits the parent's full context and shares prompt cache | -| **Teammate** | A collaborative member within an Agent Team, communicating via mailbox | -| **Worktree** | Git worktree isolation mode providing an independent file environment | -| **LocalAgentTask** | Local agent task state, tracking running/completed/failed status | -| **DreamTask** | Automatic memory consolidation task that runs periodically in the background | -| **CacheSafeParams** | Cache-safe parameters ensuring byte-level consistency of API request prefixes | -| **TeamFile** | Team configuration file storing the member list and permissions | -| **Mailbox** | File-based message queue supporting asynchronous communication between teammates | - ---- - -## Related Resources - -- [Claude Code Haha Home](/) -- [Memory System Documentation](/en/memory/01-usage-guide) -- [Agent Tool Source Code](https://github.com/NanmiCoder/cc-haha/tree/main/src/tools/AgentTool/) -- [Swarm Infrastructure](https://github.com/NanmiCoder/cc-haha/tree/main/src/utils/swarm/) -- [Task Management System](https://github.com/NanmiCoder/cc-haha/tree/main/src/tasks/) -- [GitHub Issues](https://github.com/NanmiCoder/cc-haha/issues) diff --git a/docs/en/channel/index.md b/docs/en/channel/index.md deleted file mode 100644 index 987cb773..00000000 --- a/docs/en/channel/index.md +++ /dev/null @@ -1,56 +0,0 @@ -# Channel System Research - -This section documents the upstream Claude Code Channel/MCP architecture. It is not the recommended setup path for the IM integrations shipped by this repository. - -To connect WeChat, DingTalk, WhatsApp, Telegram, or Feishu to the current Desktop app, start with [IM Integrations](../im/). - -## Current product path - -The working integration in this repository is: - -```text -Desktop settings - → /api/adapters - → ~/.claude/adapters.json - → adapters/* - → pairing and allowlist - → HTTP session creation - → /ws/:sessionId - → Claude Code session -``` - -This adapter path is smaller and separate from the upstream Channel mechanism. It matches the Desktop server’s REST and WebSocket architecture and keeps platform authorization in dedicated sidecar processes. - -## Documents - -### [Channel System Architecture](./01-channel-system.md) - -A source-level analysis of the upstream system, including: - -- MCP Channel capability and notification flow; -- inbound XML wrapping and outbound tool calls; -- runtime, OAuth, organization, session, and plugin gates; -- permission relay and short request IDs; -- plugin registration and trust boundaries. - -### Historical IM Gateway proposal - -The Chinese documentation retains an early IM Gateway proposal as architecture history. It explains why the implementation moved toward independent adapters and `/ws/:sessionId`. It is not a current setup guide and is intentionally not presented as an English user workflow. - -## When to read this section - -Use these pages when you are: - -- studying the upstream Claude Code Channel design; -- comparing Channel plugins with the repository’s adapter implementation; -- designing a future protocol or plugin integration; -- reviewing the security boundaries of remote Agent control. - -For a working bot or linked account, use: - -- [IM Integration overview](../im/) -- [Telegram](../im/telegram.md) -- [Feishu](../im/feishu.md) -- [WeChat](../im/wechat.md) -- [DingTalk](../im/dingtalk.md) -- [WhatsApp](../im/whatsapp.md) diff --git a/docs/en/guide/env-vars.md b/docs/en/cli/env.md similarity index 92% rename from docs/en/guide/env-vars.md rename to docs/en/cli/env.md index fb92d65e..086b759f 100644 --- a/docs/en/guide/env-vars.md +++ b/docs/en/cli/env.md @@ -1,3 +1,10 @@ +--- +title: Environment Variables +nav_title: Env Vars +description: Authentication, model, Azure, and local-runtime variables, plus the real precedence order. +order: 2 +--- + # Environment Variables Claude Code Haha has two configuration paths: @@ -39,7 +46,7 @@ Azure OpenAI uses a dedicated Responses API path: Example: -```bash +```ini CLAUDE_CODE_USE_AZURE_OPENAI=1 AZURE_OPENAI_BASE_URL=https://your-resource.cognitiveservices.azure.com AZURE_OPENAI_API_VERSION=2025-04-01-preview @@ -57,7 +64,7 @@ AZURE_OPENAI_CODEX_DEPLOYMENT=your_codex_deployment | `DISABLE_TELEMETRY` | Set to `1` to disable telemetry | | `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` | Set to `1` to disable non-essential network requests | -See [Local Server](../reference/local-server.md) for `SERVER_HOST`, `SERVER_PORT`, `SERVER_AUTH_REQUIRED`, and related variables. +See [Local Server](../internals/server.md) for `SERVER_HOST`, `SERVER_PORT`, `SERVER_AUTH_REQUIRED`, and related variables. ## Configuration methods @@ -71,7 +78,7 @@ Desktop stores the provider index at: Provider-managed environment data is written to an isolated Haha configuration. You do not need to copy it into `~/.claude/settings.json`. When the CLI finds an active provider, it reuses its credentials, models, and protocol settings. Providers using `openai_chat` or `openai_responses` automatically use a loopback proxy. -See [Third-Party Models](./third-party-models.md) for the setup flow. +See [Third-Party Models](../start/models.md) for the setup flow. ### Repository `.env` @@ -83,7 +90,7 @@ cp .env.example .env Minimal example for an Anthropic-compatible endpoint: -```bash +```ini ANTHROPIC_AUTH_TOKEN=sk-example ANTHROPIC_BASE_URL=https://provider.example.com/anthropic ANTHROPIC_MODEL=provider-model @@ -127,5 +134,5 @@ Keep one primary provider configuration source. Desktop users should use the Pro - Never commit `.env`, provider configuration, or a `settings.json` containing secrets. - Do not expose full tokens in screenshots, issues, logs, or diagnostic archives. - Use `CLAUDE_CONFIG_DIR` to isolate tests from real user configuration. -- `--print` skips the workspace trust dialog and must only run in trusted directories. See [CLI Reference](./cli-reference.md). +- `--print` skips the workspace trust dialog and must only run in trusted directories. See [CLI Reference](./reference.md). - Do not treat CORS as authentication when exposing the local server. Enable an H5 token or explicit authentication. diff --git a/docs/en/cli/index.md b/docs/en/cli/index.md new file mode 100644 index 00000000..9edf4a15 --- /dev/null +++ b/docs/en/cli/index.md @@ -0,0 +1,115 @@ +--- +title: Install and Run +nav_title: Install and Run +description: Run the CLI from source - install dependencies, configure a provider, launch from any directory. +order: 0 +--- + +# Install and Run + +The CLI is the core of Claude Code Haha — every desktop session runs one underneath. If you only want the graphical app, installing that is enough; see [Download and install](../start/install.md). The steps below are for people who want a terminal workflow, `--print` automation, or a source checkout to read and contribute to. + +The CLI runs from source only. There is no separate installer for it. + +## Get the source + +Install [Git](https://git-scm.com/downloads) and [Bun](https://bun.sh) first, then: + +```bash +git clone https://github.com/NanmiCoder/cc-haha.git +cd cc-haha +bun install +``` + +## Configure a model provider + +```bash +cp .env.example .env +``` + +Edit `.env` with at least one working authentication method, base URL, and model. A minimal Anthropic-compatible setup looks like this: + +```ini +ANTHROPIC_AUTH_TOKEN=sk-example +ANTHROPIC_BASE_URL=https://provider.example.com/anthropic +ANTHROPIC_MODEL=provider-model +``` + +See [Environment variables](./env.md) for what each variable means, how the authentication headers differ, and how to configure Azure and other protocols. If you already configured a provider in the desktop app, the CLI reuses it and you do not need a `.env` at all. + +Never commit a real API key, and never paste one into an issue, a screenshot, or a diagnostics bundle. + +## Start and verify + +macOS, Linux, or Git Bash: + +```bash +./bin/claude-haha +./bin/claude-haha -p "Summarize the directory structure of this project" +``` + +Windows PowerShell or cmd: + +```powershell +bun --env-file=.env ./src/entrypoints/cli.tsx +``` + +Once you see streaming output and tool calls, the provider, the project directory, and the CLI are connected. + +## Run from any directory + +`./bin/claude-haha` only works inside the checkout. Put it on your `PATH` and you can type `claude-haha` in any project directory — the CLI treats the current working directory as the project root. + +On macOS and Linux, add this to `~/.bashrc` or `~/.zshrc`: + +```bash +# Option 1: add to PATH (recommended) +export PATH="$HOME/path/to/claude-code-haha/bin:$PATH" + +# Option 2: alias +alias claude-haha="$HOME/path/to/claude-code-haha/bin/claude-haha" +``` + +Reload the shell config: + +```bash +source ~/.zshrc # or source ~/.bashrc +``` + +On Windows, add the same `PATH` line to `~/.bashrc` under Git Bash: + +```bash +export PATH="$HOME/path/to/claude-code-haha/bin:$PATH" +``` + +To verify, start it from a different directory and ask what the current directory is: + +```bash +cd ~/your-other-project +claude-haha +``` + +### Windows with a WSL toolchain + +If `claude-haha` runs on Windows or Git Bash while Node, Python, uv, and bun live inside WSL, call them through WSL explicitly: + +```bash +wsl -e bash -lc 'node --version && python3 --version' +``` + +When the CLI detects a `wsl` / `wsl.exe` invocation, it sets `MSYS2_ARG_CONV_EXCL=*` so Git Bash does not rewrite WSL paths such as `/home/...` into `C:/Program Files/Git/home/...`. + +To route Bash tool commands through WSL by default, set this before startup: + +```bash +export CLAUDE_CODE_SHELL_PREFIX='wsl -e bash -lc' +``` + +Computer Use still controls Windows desktop apps, so CLI tools inside WSL do not need an entry in `computer-use-config.json`. If you only need the WSL toolchain and no desktop control, pass `--no-computer-use` or turn it off under Settings → Computer Use. + +## Next steps + +- [Command reference](./reference.md): flags, headless mode, and recovery mode +- [Environment variables](./env.md): the full list of authentication, model, and runtime variables +- [Architecture overview](../internals/index.md): how the CLI, server, desktop shell, and adapters divide the work +- [Contributing and quality gates](../internals/contributing.md): what to run before opening a PR diff --git a/docs/en/guide/cli-reference.md b/docs/en/cli/reference.md similarity index 97% rename from docs/en/guide/cli-reference.md rename to docs/en/cli/reference.md index 0333243e..4f786d31 100644 --- a/docs/en/guide/cli-reference.md +++ b/docs/en/cli/reference.md @@ -1,4 +1,11 @@ -# CLI Reference +--- +title: Command Reference +nav_title: Commands +description: Interactive sessions, --print headless mode, recovery mode, and common command-line flags. +order: 1 +--- + +# Command Reference Claude Code Haha starts an interactive session by default. With `--print`, it can also run as a non-interactive agent in scripts, CI, or another program. The command in a source checkout is `./bin/claude-haha`; the installed executable name depends on the installation method. diff --git a/docs/en/desktop/01-quick-start.md b/docs/en/desktop/01-quick-start.md deleted file mode 100644 index 214d84e7..00000000 --- a/docs/en/desktop/01-quick-start.md +++ /dev/null @@ -1,138 +0,0 @@ -# Desktop Quick Start - -This walkthrough takes you from installation to a working session. - -![The Main Session with its project and history sidebar expanded, keeping the conversation, composer, model, and permission controls at the center](../../images/desktop_ui/25_main_session.png) - -## 1. Install and open the app - -Download the package for your platform from [GitHub Releases](https://github.com/NanmiCoder/cc-haha/releases). Detailed package names and platform notes are in the [Installation Guide](./04-installation.md). - -On first launch: - -- macOS may show the standard downloaded-app confirmation. Official releases from `v0.4.3` onward are signed and notarized. -- An unsigned Windows build may show SmartScreen. Confirm that the package came from the project’s GitHub Release before choosing **More info → Run anyway**. -- Linux AppImage users may need FUSE, depending on the distribution. - -## 2. Confirm the data location - -Desktop uses `~/.claude` by default for sessions, providers, settings, Skills, Agents, memory, tasks, traces, and adapter state. Most users should keep this default. If you need portable or isolated storage, select an explicit custom directory in General settings and restart when prompted. - -Do not point automated tests or temporary experiments at your real user data directory. - -## 3. Connect a model provider - -Open **Settings → Providers**. There are four main paths: - -| Path | Use it when | -|---|---| -| Claude official login | You want models available to your Claude account | -| ChatGPT official login | You want the current GPT and Codex catalog available to your account | -| Grok official login | You want the xAI model catalog available to your account | -| Custom provider | You have an Anthropic-compatible or OpenAI-compatible endpoint and API key | - -Custom providers can use `anthropic`, `openai_chat`, or `openai_responses` format. Presets are editable, and the final model list still depends on the endpoint. - -Use **Test connection** before starting a session. Never paste API keys into chat messages or screenshots. - -## 4. Create a session - -Select **New session**, then choose: - -- a project directory; -- the current working tree or an isolated Worktree when Git options are available; -- a model and supported reasoning effort; -- a permission mode. - -Sessions are bound to their working directory. The sidebar groups history by project and time, and open sessions can be kept in separate tabs. - -## 5. Choose a permission mode - -The current desktop supports five modes: - -![The New Session permission menu showing Ask, Accept Edits, Auto, Plan, and Bypass Permissions](../../images/desktop_ui/18_permission_modes.png) - -| Mode | Behavior | -|---|---| -| Ask | Requests approval before protected operations | -| Accept Edits | Automatically accepts eligible file edits, while other protected actions can still ask | -| Plan | Lets the model investigate and propose a plan without carrying out normal implementation work | -| Auto | Reviews operations automatically and allows or blocks them according to the Auto policy | -| Bypass Permissions | Skips eligible approval prompts; explicit denials and tools that require user interaction still apply | - -Auto and Bypass Permissions expand what can run without a click. Read the confirmation dialog and use them only in a project and environment you trust. The mode cannot be changed while an active turn makes that unsafe. - -## 6. Chat and review work - -The composer supports: - -- text and multiline input; -- pasted images, drag-and-drop, and file selection; -- `/` commands; -- `@` workspace file search; -- model and effort selection when supported. - -While a turn runs, review tool calls, permission requests, tasks, SubAgents, teams, sources, changed files, and previews. Use the workspace panel for files and diffs, and the activity panel for background work. - -## 7. Explore the workspace - -The workspace can search the complete project without requiring every directory to be expanded first. Packaged desktop builds include the appropriate ripgrep binary. - -![The expanded workbench listing real Git changes, file types, and line counts](../../images/desktop_ui/22_workspace_changed_files.png) - -You can: - -- preview files and local attachments; -- open files with the system default app or a configured editor; -- select one or several diff lines and send review comments back to chat; -- inspect the current branch, Worktree, and changed-file summary; -- open the embedded terminal and browser preview. - -![A real code diff with the inline local-comment editor open on a changed line](../../images/desktop_ui/23_workspace_diff_review.png) - -## 8. Manage Skills, Agents, and pets - -### Install Skills - -**Skills** shows installed Skills and a marketplace for discovering, reviewing, installing, and removing supported third-party Skills. Review source and risk information before installation. - -![The Skills marketplace with source status, the third-party warning, filters, security labels, and installation states](../../images/desktop_ui/21_skill_marketplace.png) - -### Create or edit an Agent - -Open **Settings → Agents**, then select **Create Agent**. - -![Create Agent dialog with scope, model, reasoning effort, tools, and system prompt fields](../../images/desktop_ui/17_agent_create.png) - -1. Choose **User** scope to reuse the Agent across projects, or **Project** scope to keep it with the current project. -2. Add a name, description, and system prompt that clearly define the Agent's responsibility and expected output. -3. Inherit the main session model and effort, or select a model and supported effort specifically for this Agent. -4. Keep all tools, disable tools, or choose searchable built-in, MCP, and custom tool rules. -5. Save the Agent. User Agents are stored in `~/.claude/agents/`; project Agents are stored in the current project's `.claude/agents/`. - -Built-in, plugin, and policy Agents remain read-only. See the [Multi-Agent Usage Guide](../agent/01-usage-guide.md#6-custom-agents) for definition formats, precedence, and inheritance. - -### Turn on the desktop pet - -Open **Settings → Pets** and enable **Show desktop pet**. - -![Desktop Pet settings showing four built-in pets and appearance controls](../../images/desktop_ui/14_pet_settings_overview.png) - -1. Choose Dada, Huhu, Bubu, or Huihui. -2. Adjust the size and animation setting. -3. Enable the active-task panel if you want it to open automatically while a task is running. The panel remains hidden when no task is active. -4. Hover for a small reaction, click the pet to focus the main window, or drag it to another position. Right-click the pet to close it; return to **Settings → Pets** to show it again. - -Select **Add pet** to make one of your own: use a transparent PNG or WebP you already have, or follow the in-dialog prompt to have any drawing AI produce an action sheet and pick that (sizes are aligned for you). Everything happens locally, without calling the current chat model or using chat quota. Pets run only in the Electron desktop app and are not supported in H5. - -Follow the complete [Desktop Pet Guide](./pets.md) for custom-image requirements, task states, and interaction details. - -## 9. Optional remote access - -- Use [IM integrations](../im/) for explicitly paired private-chat users. -- Use [H5 Access](./06-h5-access.md) only on a trusted LAN or behind a reverse proxy you control. -- Local loopback Web UI access is separate from H5 and does not grant remote access. - -## 10. If something fails - -Open **Settings → Diagnostics** to inspect the desktop runtime, local index, sidecar, providers, and recoverable state. Read [FAQ](./05-FAQ.md) before manually editing stored JSON. diff --git a/docs/en/desktop/03-features.md b/docs/en/desktop/03-features.md deleted file mode 100644 index 2d501bab..00000000 --- a/docs/en/desktop/03-features.md +++ /dev/null @@ -1,143 +0,0 @@ -# Desktop Feature Guide - -This page summarizes the current user-visible Desktop surface. - -## Sessions and chat - -- Multiple tabs and project-filtered session history -- Streaming Markdown, code, Mermaid, tool calls, thinking blocks, and permission requests -- Image, file, directory, and PDF attachments with context preserved across restart and resume -- Slash commands, `@` file search, model selection, and supported reasoning-effort levels -- Stop, resume, search, navigation outline, and checkpoint-based undo for the current turn -- Restored scroll position and stable search in long, virtualized conversations - -## Permission controls - -Desktop supports Ask, Accept Edits, Plan, Auto, and Bypass Permissions. Auto evaluates operations according to its policy; Bypass skips only approvals that are eligible to be skipped. Explicit denials, protected boundaries, and tools that require direct user interaction remain enforced. - -![The New Session permission menu showing all five execution modes](../../images/desktop_ui/18_permission_modes.png) - -The selected mode is visible in the session composer. Higher-autonomy modes require an explicit warning confirmation. - -## Workspace and code review - -The workspace panel combines: - -![The expanded workbench listing changed files, file types, and Git line counts](../../images/desktop_ui/22_workspace_changed_files.png) - -- full-project file search using the packaged ripgrep binary; -- a file tree, previews, and external-editor actions; -- changed-file summaries and inline diffs; -- single-line or contiguous-line review comments sent back into chat; -- embedded terminal and local browser preview. - -Branch and Current/Isolated Worktree selection belongs to the new-session launch controls. The workspace then shows the selected session's files, Git status, and diffs. - -![A real code diff with the inline local-comment editor open](../../images/desktop_ui/23_workspace_diff_review.png) - -### Browser Preview - -Switch the workbench from **Files** to **Browser** to open a local development page or HTTPS URL inside the app. - -![The embedded Browser Preview showing a safe documentation example page, address bar, screenshot action, and element picker](../../images/desktop_ui/24_browser_preview.png) - -- **Screenshot** sends the current page image back to the session. -- **Select element** captures a page element, selector, and screenshot as explicit Agent context. -- Treat authenticated pages, cookies, and external-site content as sensitive. Use a public demo page for documentation screenshots. - -## Tasks, SubAgents, and activity - -The activity panel collects foreground tasks, background tasks, SubAgents, team members, and sources without flooding the main transcript. - -Running SubAgents can be opened before they finish. Their status, output, tool activity, duration, and terminal state continue to refresh. Completed, failed, and stopped terminal states are persisted so they do not revert to “running” after restart. - -![The scheduled-task form with prompt, model, working directory, full-permission warning, frequency, and notification controls](../../images/desktop_ui/20_scheduled_task.png) - -Scheduled tasks support cron-based runs, model selection, enable/disable controls, manual execution, notifications, and history. New scheduled tasks currently run with Bypass Permissions; the form does not provide a permission selector. Use the smallest practical working directory and review the prompt before saving. Tasks run only while the Desktop app and computer remain available. - -## Providers and models - -Provider settings support: - -- Claude, OpenAI, and Grok official login; -- editable built-in presets; -- Custom Anthropic, OpenAI Chat, and OpenAI Responses endpoints; -- per-provider model mappings, context metadata, proxy mode, and supported effort levels; -- connection testing and runtime model discovery where available. - -The visible model catalog and supported effort levels depend on the current account, provider, and runtime response. Release-specific model names belong in release notes rather than this evergreen guide. - -## Skills and marketplace - -The Skills area groups local Skills by source and exposes their metadata and files. The marketplace aggregates supported third-party sources with search, filters, details, file previews, installation confirmation, local installation state, and risk information. - -![The Skills marketplace showing source health, the third-party warning, filters, security status, and install state](../../images/desktop_ui/21_skill_marketplace.png) - -Marketplace metadata is not a security guarantee. Review the source and requested behavior before installing a Skill. - -## Agent management - -The Agents page shows built-in, user, project, plugin, and policy sources together with override and effective-state information. - -![Create Agent dialog showing scope, prompt, model, effort, and tool controls](../../images/desktop_ui/17_agent_create.png) - -Editable user and project Agents support: - -- reusable user scope or current-project scope; -- model inheritance or an explicit model ID; -- supported effort from `low` through `max`; -- searchable tool selection plus custom and MCP tool rules; -- a system prompt and description. - -User Agents are stored in `~/.claude/agents/`, while project Agents are stored in the current project's `.claude/agents/`. Built-in, plugin, and policy definitions remain read-only. Saving a definition and refreshing a running session are separate outcomes; a refresh failure does not silently discard a saved file. - -## Desktop pets - -The optional pet window is a native transparent Electron surface. Open **Settings → Pets**, enable the window, and choose one of the four built-in companions: Dada, Huhu, Bubu, or Huihui. - -![Desktop Pet settings with the enable switch, built-in companions, and appearance controls](../../images/desktop_ui/14_pet_settings_overview.png) - -The pet reflects working, waiting-for-you, failed, and idle states. While a task is active, its task panel can show the session title and status; selecting a row returns to that session. The panel remains hidden when no task is active. If automatic display is disabled, an activity-count badge lets you open it when work is running. - -The window also supports direct interaction: - -- hover to trigger a small reaction and idle gaze tracking; -- click to wave and focus the main window; -- drag to move the window, with its position restored after restart; -- right-click to close it, then use **Settings → Pets** to show it again. - -### Add a custom pet - -Select **Add pet** to choose a local creation path. - -![Add pet dialog with three ways to make a pet](../../images/desktop_ui/15_pet_create_methods.png) - -- **Use a picture you already have** turns a static transparent PNG or WebP into a lightweight locally animated pet. It will not run or track the cursor. -- **Draw one with AI that runs and jumps** supplies a copyable prompt, a reference template and a checklist, so any drawing AI can produce an 8-column × 9-row action sheet you then pick. -- **I already have an action sheet** skips the walkthrough and imports a sheet or finished atlas directly. - -The last two paths slice, rescale and mirror the sheet locally on import, so exact source dimensions are not required; a finished `1536×2288` atlas is kept as-is. - -Imports stay local and do not call the selected chat model. After a successful import, the custom companion appears under **Your pets** and becomes the selected pet. - -![A locally imported custom pet selected in the Your pets list](../../images/desktop_ui/16_pet_custom_result.png) - -Size, animation, active-task-panel, and window-position preferences are restored between launches. Pets run only in the Electron desktop app and are not supported in H5. See the [Desktop Pet Guide](./pets.md) for image limits, atlas layout, storage, and troubleshooting. - -## Local data and search - -A rebuildable SQLite projection accelerates session lists, global search, activity statistics, scheduled runs, teams, and traces. Original JSON and JSONL files remain authoritative. If the index is unavailable or damaged, readers can fall back to source files, and Diagnostics provides a confirmed rebuild action. - -Rebuilding the index does not delete chats, settings, Skills, memory, tasks, or traces. - -## IM and H5 - -The desktop can launch sidecar adapters for WeChat, DingTalk, WhatsApp, Telegram, and Feishu. Each platform is private-chat oriented and requires an allowlisted or paired user. See [IM Integrations](../im/). - -[H5 Access](./06-h5-access.md) serves the browser UI from the local server for a trusted LAN or controlled reverse proxy. It uses a separate token and origin policy and is not a public multi-user account system. - -## Diagnostics and recovery - -Diagnostics surfaces local-index health, sidecar and runtime information, fallback state, storage information, and safe rebuild actions. Startup failures that cannot be recovered automatically should remain visible rather than leaving only a background process. - -Doctor and repair flows are intentionally conservative: protected user data is not silently reset. diff --git a/docs/en/desktop/04-installation.md b/docs/en/desktop/04-installation.md deleted file mode 100644 index fd9cc1f1..00000000 --- a/docs/en/desktop/04-installation.md +++ /dev/null @@ -1,103 +0,0 @@ -# Installation - -Claude Code Haha Desktop is built with Electron and ships packages for macOS, Windows, and Linux. Official macOS releases from `v0.4.3` onward use Developer ID signing and notarization; older or temporary builds may still require manual approval. - -## Download - -Open [GitHub Releases](https://github.com/NanmiCoder/cc-haha/releases) and select the package for your platform: - -| Platform | Package | -|---|---| -| macOS Apple Silicon | `Claude-Code-Haha--mac-arm64.dmg` | -| macOS Intel | `Claude-Code-Haha--mac-x64.dmg` | -| Windows x64 | `Claude-Code-Haha--win-x64.exe` | -| Windows ARM64 | `Claude-Code-Haha--win-arm64.exe` | -| Linux x64 | `...-linux-x86_64.AppImage` or `...-linux-amd64.deb` | -| Linux ARM64 | `...-linux-arm64.AppImage` or `...-linux-arm64.deb` | - -On macOS, **Apple M-series** means arm64; **Intel** means x64. - -## macOS - -Open the DMG and drag the app to `Applications`. An official signed release should only show the normal downloaded-app confirmation. - -For an older or explicitly unsigned temporary build, macOS may report that the app is damaged or cannot verify the developer. After confirming the package source, either use **System Settings → Privacy & Security → Open Anyway**, or run: - -```bash -xattr -cr /Applications/Claude\ Code\ Haha.app -``` - -Do not use this workaround for an untrusted download. - -## Windows - -Run the `.exe` installer. If an unsigned package triggers SmartScreen, verify that it came from the expected GitHub Release, then select **More info → Run anyway**. - -The installer supports x64 and ARM64 packages. Close running Claude Code Haha processes before an overwrite install. User data is kept separately from application files. - -## Linux - -For AppImage: - -```bash -chmod +x Claude-Code-Haha--linux-x86_64.AppImage -./Claude-Code-Haha--linux-x86_64.AppImage -``` - -If FUSE is missing, Ubuntu 22.04 and earlier normally use `libfuse2`; Ubuntu 24.04 and later normally use `libfuse2t64`. - -For a deb package: - -```bash -sudo apt install ./Claude-Code-Haha--linux-amd64.deb -``` - -## Local Web UI - -For source development, run the server from the repository root and Vite from `desktop/`: - -```bash -SERVER_PORT=3456 bun run src/server/index.ts -``` - -In a second terminal: - -```bash -cd desktop -bun run dev --host 127.0.0.1 --port 2024 -``` - -Open `http://127.0.0.1:2024`. - -True loopback access from the same machine does not require H5 to be enabled or an H5 token. This trust does not extend to LAN addresses or reverse proxies. - -## Headless Linux over SSH - -Keep both processes bound to loopback: - -```bash -SERVER_PORT=3456 bun run src/server/index.ts - -cd desktop -bun run dev --host 127.0.0.1 --port 2024 -``` - -Forward both ports from your own computer: - -```bash -ssh -L 2024:127.0.0.1:2024 -L 3456:127.0.0.1:3456 user@example.com -``` - -Then open: - -```text -http://127.0.0.1:2024/?serverUrl=http%3A%2F%2F127.0.0.1%3A3456 -``` - -This does not expose the service to the LAN. To serve the built Web UI to a trusted LAN or reverse proxy, follow [H5 Access](./06-h5-access.md#enable-h5-without-the-desktop-ui). - -## Updates and data - -Official releases check GitHub Releases for updates. In-place updates and overwrite installs are designed to preserve local settings and sessions. - -The default data location is under `~/.claude`. A custom data directory can be selected in the app. Before changing storage manually, use the in-app controls and read the recovery guidance in [FAQ](./05-FAQ.md). diff --git a/docs/en/desktop/05-FAQ.md b/docs/en/desktop/05-FAQ.md deleted file mode 100644 index 64315081..00000000 --- a/docs/en/desktop/05-FAQ.md +++ /dev/null @@ -1,122 +0,0 @@ -# Desktop FAQ - -## Where is my data stored? - -The default data root is under `~/.claude`. It contains user-owned sessions, provider settings, Skills, Agents, memory, tasks, traces, adapter configuration, and derived desktop state. - -Use the General settings control when changing the data directory. Do not delete or replace the directory as a first troubleshooting step. - -## Why does macOS say the app is damaged? - -Official macOS releases from `v0.4.3` onward are signed and notarized. This message is more likely with an older or temporary unsigned package. Confirm that the file came from the project’s GitHub Release, then follow the macOS steps in [Installation](./04-installation.md#macos). - -## Why does Windows show SmartScreen? - -Windows signing is not a release requirement for every build. An unsigned installer can trigger SmartScreen even when the package is intact. Verify the GitHub Release and architecture before selecting **More info → Run anyway**. - -## A Custom provider returns 401. What should I check? - -Open **Settings → Providers**, verify the Base URL, API format, API key, and model ID, then run **Test connection**. - -Authentication headers differ by provider: - -- standard API-key providers normally use the API key field; -- bearer-token endpoints require the corresponding provider/auth configuration; -- OpenAI-compatible endpoints must use the correct `openai_chat` or `openai_responses` format; -- Kimi Code uses the current K3 Coding API preset and model metadata. - -Prefer editing or recreating the provider in the UI over manually rewriting stored JSON. Never share the stored credential or a diagnostic screenshot containing it. - -## Why is an official model missing? - -Official-login catalogs are filtered by the models available to the account and by runtime discovery. A model listed in the app’s metadata is not a guarantee that every account can call it. - -Sign out and back in, refresh the catalog, and confirm account access. For Custom providers, verify the endpoint’s real model ID and capability support. - -## Why was my reasoning effort reduced or ignored? - -Effort is applied only when the selected model and provider support it. Per-Agent effort follows the same rule. Unsupported combinations can be normalized, reduced, or omitted rather than forcing an invalid API request. - -## My old sessions are missing from the list. Were they deleted? - -Usually not. The SQLite local index is a rebuildable projection; original session files remain authoritative. Open **Settings → Diagnostics**, inspect index state, and use the confirmed rebuild action if needed. - -Rebuilding the index does not delete source sessions, settings, Skills, memory, tasks, or traces. - -## The app opens to a blank or startup error screen - -Restart once and open Diagnostics if the app recovers. If a visible startup error remains: - -1. record the exact error and platform; -2. confirm the package architecture; -3. check that security software did not quarantine a sidecar; -4. avoid deleting the data directory; -5. report the issue with the release version and sanitized logs. - -The desktop has bounded renderer and sidecar recovery, but it intentionally leaves an unrecoverable startup failure visible. - -## H5 asks for a token - -Remote browser access requires H5 to be enabled and a current token. True same-machine loopback development access is separate and does not require H5. - -Regenerating the token invalidates the previous one. See [H5 Access](./06-h5-access.md). - -## An IM bot says I am unauthorized - -Binding a bot or linked account does not authorize every chat user. The user must be in `allowedUsers` or complete pairing with the current six-character code. Codes expire after 60 minutes, are one-time use, and are rate limited after repeated failures. - -See [IM Integrations](../im/). - -## Will updates delete my providers or chats? - -Official in-app and overwrite updates are designed to preserve the user data directory. Back up important user-owned data before unusual manual migrations, and do not treat application build directories as the data source. - -## Desktop pets - -See [Desktop Pets](./pets.md) for the complete enable, customization, and import workflow. - -### Why is the active-task panel missing? - -The panel lists only non-idle sessions, such as tasks that are running, waiting for you, or need attention. It hides automatically when every task is complete or idle; enabling **Show active task panel** does not keep historical tasks visible. - -Confirm **Settings → Pets → Show active task panel** is enabled, then start a real task. Status refreshes periodically; if it still does not appear after a few seconds, hide and show the pet again. - -### Why is my pet not moving? - -Confirm **Settings → Pets → Play animations** is enabled. The app also respects the operating system’s reduced-motion preference, which can reduce or disable animation. - -A custom pet made from one image receives lightweight breathing, floating, and task-state motion. It does not have the complete frame-by-frame actions of a v2 atlas pet. Hover over or click the pet to trigger interactions such as jumping or waving. - -### Why did my custom-pet import fail? - -Check each requirement: - -- A single image must be PNG or WebP, 32–4096 pixels on each side, no larger than 8 MB, and no more than 16,777,216 total pixels. -- An action sheet must use an 8-column × 9-row layout. Exact pixel dimensions are not required — it is sliced and rescaled on import. A finished 1536×2288 atlas is kept as-is. -- An action sheet must have a transparent background. White or coloured backdrops are rejected because the pet would render as a square on the desktop. -- The pet ID may be at most 73 characters, contain only lowercase letters, numbers, and single hyphens, and must not duplicate an existing ID. -- Display name and description are required. - -The import reads only the local file you confirm in the system picker. Fix the source image and create the pet again instead of manually editing its manifest. - -### How do I restore a pet after closing it? - -Right-clicking the pet and choosing close, or turning off **Settings → Pets → Show desktop pet**, saves the disabled state. Turn that setting on again to restore the pet; reinstalling the app is not necessary. - -If the pet was dragged onto another display, hide and show it again. Its saved position is clamped into the currently visible work area when the pet window is recreated. - -### How do I delete a custom pet? - -There is no in-app delete button in the current version. Select a built-in pet first, choose **Settings → Pets → Open folder**, delete only the folder for the custom pet, then return to Settings and select **Refresh**. - -Delete only the confirmed pet directory under `${CLAUDE_CONFIG_DIR:-~/.claude}/cc-haha/pets`. Do not delete the entire `~/.claude` directory. Built-in pets ship with the app and are not removed here. - -### Does “Draw one with AI that runs and jumps” use my chat quota? - -No. That path never calls the chat model selected for the session, and the app does not request any image-generation service on your behalf. It gives you a prompt and a reference template; you generate the picture in whichever drawing tool you like and then pick the file. Assembly and import both happen on your own machine. - -### Can the pet approve permissions or send replies? - -No. The pet window reports task state, focuses the main window, and opens the corresponding session. It has no message composer and cannot approve file, command, or Computer Use permissions. - -When it shows **Waiting for you**, open the task in the main window, review the full context, approve or reject the request, and reply there. The pet never bypasses the selected permission mode. diff --git a/docs/en/desktop/06-h5-access.md b/docs/en/desktop/06-h5-access.md deleted file mode 100644 index d3df9876..00000000 --- a/docs/en/desktop/06-h5-access.md +++ /dev/null @@ -1,120 +0,0 @@ -# H5 Access - -H5 is an optional browser entry point to the same local chat UI used by Desktop. It is intended for a trusted LAN or a reverse proxy you control. - -It is not a public SaaS authentication system. Anyone with the reachable service URL and current H5 token can access the exposed chat capabilities. - -## Enable H5 - -1. Open **Settings → H5 Access**. -2. Enable H5. -3. Generate a token. -4. Set the LAN access host or public base URL used to generate a launch link and QR code. -5. Choose the current port, or configure a fixed port when you need a stable bookmark or reverse proxy. - -Desktop same-origin access automatically allows the origin derived from the configured public base URL. `allowedOrigins` is an advanced local-server API option for custom cross-origin deployments; it is not a field in the Desktop H5 settings page. - -For a same-origin LAN deployment, a typical public base URL is: - -```text -http://192.168.1.20:3456 -``` - -The generated launch URL contains the server URL and token. Treat it like an API credential and do not post it publicly. - -H5 settings are stored in `~/.claude/cc-haha/settings.json`. The full token is persisted so paired devices can reconnect after restart. Only trusted local control surfaces should be able to read it back. - -## Enable H5 without the Desktop UI - -Build the Web UI, then start the server from the repository root: - -```bash -cd desktop -bun run build -cd .. -SERVER_HOST=0.0.0.0 SERVER_PORT=3456 bun run src/server/index.ts -``` - -The server serves `desktop/dist`. If it is started outside the repository root, set `CLAUDE_H5_DIST_DIR` to the absolute `desktop/dist` path. - -From another terminal on the server itself, generate a token: - -```bash -curl -sS -X POST http://127.0.0.1:3456/api/h5-access/enable -``` - -Configure exact origins and an optional public base URL: - -```bash -curl -sS -X PUT http://127.0.0.1:3456/api/h5-access \ - -H 'Content-Type: application/json' \ - --data '{ - "allowedOrigins": ["https://cc.example.com"], - "publicBaseUrl": "https://cc.example.com" - }' -``` - -Read the current configuration and full token only from server loopback: - -```bash -curl -sS http://127.0.0.1:3456/api/h5-access -``` - -If SSH port forwarding is sufficient, keep the service on `127.0.0.1` and use the [headless installation path](./04-installation.md#headless-linux-over-ssh) instead of enabling H5. - -## Browser launch URL - -For same-origin hosting, the page and API share the public server root: - -```text -https://cc.example.com/?serverUrl=https%3A%2F%2Fcc.example.com -``` - -The generated QR link also includes `h5Token`. On first connection, a manually opened page can ask for: - -| Field | Meaning | -|---|---| -| Server URL | Reachable Desktop server or reverse-proxy URL | -| H5 Token | Current token generated by the trusted local settings surface | - -The browser stores the Server URL and token in its local storage and sends authorization with REST and WebSocket traffic. - -## Reverse proxy requirements - -Use HTTPS and proxy all required routes, including: - -- `/api/*` -- `/proxy/*` -- `/ws/*` with WebSocket upgrade -- the built Web UI - -Preserve the public `Host` and normal proxy information. For example: - -```nginx -proxy_set_header Host $host; -proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; -proxy_set_header X-Forwarded-Proto $scheme; -``` - -Do not rewrite `Host` to loopback while stripping every forwarding header. The server needs enough information to distinguish a remote proxied request from a trusted local request. - -## Mobile scope - -H5 prioritizes the chat flow: - -- session navigation uses a compact drawer; -- composer, stop, attachments, and permission actions remain touch accessible; -- `@` file results adapt to mobile width; -- desktop-only workspace and terminal panels are not the primary mobile surface; -- Desktop pets do not run in H5. - -## Security checklist - -- Keep H5 disabled unless it is needed. -- Use exact allowed origins; do not use a wildcard. -- Use HTTPS outside a trusted LAN. -- Share the token separately from the public URL. -- Regenerate the token when access should be revoked. -- Do not expose H5 directly to the public internet as if it were a multi-user account service. - -Disabling H5 blocks remote access but keeps the current token for a later re-enable. Regenerating the token revokes the old token and QR links immediately. diff --git a/docs/en/desktop/agents.md b/docs/en/desktop/agents.md new file mode 100644 index 00000000..a63bd597 --- /dev/null +++ b/docs/en/desktop/agents.md @@ -0,0 +1,75 @@ +--- +title: Subagents +nav_title: Subagents +description: When to delegate, which agents ship built in, and how to write your own. +order: 3 +--- + +# Subagents + +A subagent is a copy of Claude sent off with one clearly scoped job. It works in its own context and reports back only the conclusion. + +The point is that your main conversation doesn't get flooded. Ask "find every call site of `validateUser` in this repo" and, if the main agent searches itself, dozens of files end up in the context window. Delegate it and all that comes back is the list. + +## When to delegate + +- **Questions that require reading a lot of files** — finding usages, untangling dependencies, counting where a pattern occurs. +- **Independent work that can run in parallel** — frontend, backend, and tests at the same time. +- **The same thing from several angles** — several agents each reviewing the same code. + +Conversely: if you already know the file and the line, just say so. No need for the detour. + +Delegated agents appear under **SubAgents** in the Activity panel with their tool activity streaming live. Open one to read its full transcript and final result. Background agents work the same way — you don't have to wait for them to finish to see what they're doing. + +## Built-in agents + +Available without any setup: + +| Name | What it's for | +|---|---| +| `general-purpose` | The catch-all. Researching complex questions, searching for code, multi-step tasks | +| `Explore` | Fast codebase exploration. Find files by pattern, search code by keyword, answer "how does this part work" | +| `Plan` | The architect. Designs an implementation strategy and returns step-by-step plans, critical files, and trade-offs | +| `claude-code-guide` | Answers questions about Claude Code, the Agent SDK, and the Claude API themselves | +| `verification` | Sign-off before you call it done. Runs builds, tests, and linters and returns PASS / FAIL / PARTIAL with evidence | +| `statusline-setup` | Configures the Claude Code status line | + +You can name one directly ("use Explore to find…") or let Claude decide who to send. + +## Seeing what's installed + +![Settings → Agents: the agent browser grouped by source](../../images/app/settings-agents.webp) + +Open **Settings → Agents**. Three cards at the top show total agents, how many are active, and how many sources are in play. Below that, agents are grouped by source in a fixed order: + +**User** → **Project** → **Local** → **Managed** → **Plugin** → **CLI arg** → **Built-in** + +When two agents share a name, the higher source wins and the shadowed one is tagged "Overridden by X". Day to day, two groups matter: + +- **User** — the ones you wrote, available in every project, stored in `~/.claude/agents/`. +- **Project** — scoped to the current project, stored in its `.claude/agents/`, shipped with the repo. + +Click any row for its detail page: model, effort, tool scope, and the full system prompt. Built-in and plugin agents are read-only and show a lock pill instead of edit controls. + +## Writing your own + +![The Create Agent dialog: scope, model, effort, tools, system prompt](../../images/app/agent-create.webp) + +Click **Create Agent** in the top right. The fields: + +1. **Scope** — user or project. Choosing project asks you to confirm the target path. +2. **Name** — 1–64 lowercase letters, digits, hyphens, or underscores, e.g. `code-reviewer`. This is what the main agent calls it by. +3. **Description** — when the main agent should delegate to it. **This is the field that matters most**: it's what the main agent reads to decide whether to call this agent at all. Write it vaguely and the agent will never be used. +4. **System prompt** — its responsibilities, boundaries, and expected output. +5. **Model** — inherit from the main agent, or pick Haiku / Sonnet / Opus / Fable, or enter a custom model ID. Simple repetitive work is faster and cheaper on Haiku. +6. **Effort** — inherit, or set low / medium / high / xhigh / max. Models that don't support a level downgrade or ignore it. +7. **Tools** — all tools, no tools, or a custom list. The custom picker groups built-in tools by read and search, modify files, execute commands, and workflow, with a free-text field below for MCP tool names or permission rules like `Bash(git:*)`. +8. **Color** — optional, purely for telling agents apart in the UI. + +Saving writes a Markdown file into the matching directory and refreshes the current session. A failed refresh doesn't roll back the file — it still applies after a restart. + +:::tip +Grant only the tools the job needs. An agent whose job is to read code and report back doesn't need Write or Bash. Narrow the permissions and you narrow how far it can go wrong. +::: + +For the agent file format, source precedence, and inheritance rules, see [Agent internals](../internals/agent.md). diff --git a/docs/en/desktop/computer-use.md b/docs/en/desktop/computer-use.md new file mode 100644 index 00000000..e8422e2f --- /dev/null +++ b/docs/en/desktop/computer-use.md @@ -0,0 +1,92 @@ +--- +title: Computer Use +nav_title: Computer Use +description: Let Claude read your screen, move the mouse, and type into other apps. +order: 7 +--- + +# Computer Use + +With Computer Use enabled, Claude can take screenshots of your screen, move the mouse, click, and type — driving applications that have no API at all: system settings, native note apps, Finder, third-party desktop software. + +It acts on this computer, so read what you're authorizing before you turn it on. + +macOS and Windows are supported. There is no Linux executor yet. + +## Preparing the environment + +![Settings → Computer Use: environment checks, the two macOS permissions, authorized apps](../../images/app/settings-computer-use.webp) + +Open **Settings → Computer Use**. The top of the page is a row of environment checks: + +1. **Python 3** — required on your machine. If it isn't found, click **Download Python 3**. If your Python lives in conda, pyenv, or another custom environment, pick the executable under **Python interpreter path** and it will be preferred from then on. +2. **Virtual environment** and **Dependencies** — click **Install Environment** and the app creates an isolated venv and installs the platform dependencies. Your global Python is left alone. +3. When everything is green the page says all checks passed and Computer Use is ready. + +**Re-check** re-runs the detection at any time. + +## The two macOS permissions + +macOS additionally requires two system permissions. Neither is optional: + +| Permission | What it's for | +|---|---| +| Accessibility | Moving the mouse, clicking, typing | +| Screen Recording | Taking screenshots — i.e. letting it see | + +The page has **Open accessibility settings** and **Open screen recording settings** buttons that jump straight to the right pane. + +:::warning +After granting either one you must **fully quit and reopen the app**. macOS reads these permissions once at process start, so without a restart the page will keep reporting them as not granted. +::: + +Make sure you're granting the permission to the app that actually launches Claude Code Haha. Screen Recording detection is occasionally unreliable — if the system settings clearly show it granted but the page still says otherwise, it generally works anyway. + +## Pre-authorized apps + +By default, every time Claude wants to control a new app it raises a "Computer Use wants to control these apps" prompt naming the apps and the reason. You can **Allow for session** or **Deny**. + +For apps you keep approving, tick them under **Authorized Apps** in settings and Claude will control them without prompting. The search box filters your installed apps. + +Two more grants are separate and never come along with an app authorization: + +- **Clipboard access** — reading and writing the system clipboard. +- **System key combos** — sending system-level shortcuts. + +:::danger +Pre-authorization is permanent approval. Keep password managers, banking apps, and corporate chat off that list — make it ask, every time. +::: + +## Getting started + +Start a session and describe the goal and the allowed apps in plain language. Begin with something small and reversible: + +```text +Take a screenshot and tell me what you see. +Open Notes and create an empty note titled "test". +Find the Displays pane in System Settings, but don't change anything. +``` + +Claude works in a screenshot → decide → act → screenshot loop, so it's slower than you are and will occasionally misclick. Explicit boundaries ("only inside app X", "don't save") work far better than a broad goal. + +Only one session can drive the mouse and keyboard at a time. If you see that another session holds it, stop or finish that session first. + +## Known limits + +- **There is no global abort hotkey.** Use the stop button in the session (`⌘.`). +- **Windows screenshots aren't filtered.** On macOS a screenshot keeps only authorized apps and the desktop; on Windows every visible window is captured. Close or minimize anything sensitive first. +- **Browsers and terminals are restricted.** Browsers are read-only (visible but not clickable) and terminals and IDEs are click-only (no typing). Use the browser extension for web pages and the Bash tool for commands. +- **Re-screenshot after the UI changes.** Old coordinates don't survive a page change. + +## Troubleshooting + +**The page keeps saying permissions are missing** +Confirm you granted them to the app that actually launches Claude Code Haha, fully quit and reopen, then click **Re-check**. + +**The environment won't install** +Pick an explicit Python 3 under **Python interpreter path**, confirm it supports `venv`, and click **Install Environment** again. If it still fails, check the install log in **Settings → Diagnostics**. + +**Screenshots work but clicks don't** +Make sure the target app is in the authorized list and is currently in the foreground. Browsers and terminals are subject to the tier restrictions above. + +For the permission tiers, the Python bridge, and the executors, see [Computer Use architecture](../internals/computer-use.md). diff --git a/docs/en/desktop/index.md b/docs/en/desktop/index.md index a1077ea9..e92f0d32 100644 --- a/docs/en/desktop/index.md +++ b/docs/en/desktop/index.md @@ -1,32 +1,44 @@ -# Desktop App +--- +title: Desktop feature map +nav_title: Feature map +description: Everything the desktop app can do, on one screen, with a link to each page. +order: 0 +--- -Claude Code Haha Desktop is the Electron-based workbench for local coding sessions. It combines chat, projects, workspace review, terminals, providers, Skills, Agents, background activity, scheduled tasks, IM adapters, and optional H5 access in one application. +# Desktop feature map -This guide describes the current Desktop experience. For exact changes between releases, see [GitHub Releases](https://github.com/NanmiCoder/cc-haha/releases). +The desktop app puts "talking to Claude" and "seeing what it actually changed" in the same window: projects and history on the left, the conversation in the middle, files, diffs, and a browser preview one click away on the right. -## Start here +This page is a map, not a manual. One line per feature — click through for the details. If you haven't installed it or connected a model yet, start with [Get started](../start/index.md). -- [Quick Start](./01-quick-start.md) — install the app, connect a provider, open a project, and send the first message -- [Feature Guide](./03-features.md) — sessions, workspace tools, Skills, Agents, pets, activity, search, and diagnostics -- [Installation](./04-installation.md) — platform packages, first-launch prompts, Web UI, and headless Linux -- [FAQ](./05-FAQ.md) — provider, data, startup, update, and recovery questions -- [H5 Access](./06-h5-access.md) — optional browser access from a trusted LAN or reverse proxy -- [Desktop Pets](./pets.md) — enable a companion, follow active tasks, customize its appearance, and import your own pet -- [IM Integrations](../im/) — connect WeChat, DingTalk, WhatsApp, Telegram, or Feishu +## Three places to know -## Typical workflow +- **Sidebar** — under the brand seal: New session, Scheduled, Skills Market. Below that, the search bar and your projects and past sessions. Settings sits at the bottom. Drag the edge to resize it. +- **Tab bar** — sessions side by side, switched like browser tabs. The four buttons on the right are Activity, Open project, Open terminal, and Show/hide workspace. +- **Composer toolbar** — left to right: attachments, permission mode, launch location, context usage ring, model and effort, and the run button. -1. Install the package for your operating system. -2. Complete first-run setup and choose where local data should be stored. -3. Sign in with Claude, OpenAI, or Grok, or add a Custom provider. -4. Create a session and select a project directory, branch, or isolated Worktree. -5. Review tool calls, file changes, previews, tasks, and SubAgent activity while the session runs. -6. Use Diagnostics when a provider, local index, sidecar, or desktop runtime needs attention. +## What you'll use daily -## Important boundaries +- [Sessions, permissions, and review](./sessions.md) — starting a session, reading tool cards, answering permission prompts, undoing a turn that went wrong. +- [Workspace](./workspace.md) — the right-hand panel: changed files, diff review, line comments, isolated worktrees, built-in browser. +- [Settings reference](./settings.md) — all 16 tabs, one paragraph each: themes, languages, proxy, terminal, MCP, token usage. -- Desktop data stays local unless a configured provider or integration needs to send it to an external service. -- H5 is not a public SaaS login system. It exposes the local service to anyone who has the configured URL and token. -- IM adapters deny access unless a user is explicitly allowlisted or paired. -- Desktop pets are an Electron feature and do not run in H5. -- Model availability, context size, and reasoning effort still depend on the selected account and provider. +## Getting more work out of it + +- [Subagents](./agents.md) — when to delegate, which agents ship built in, how to write your own. +- [Skills and the Skills Market](./skills.md) — what a skill is, how it differs from an agent, what to check before installing one. +- [Scheduled tasks](./schedule.md) — have Claude review yesterday's commits every morning. +- [Computer Use](./computer-use.md) — let it read the screen, move the mouse, and type into other apps. + +## Continuing somewhere else + +- [Phone (H5) and IM](./remote.md) — scan a QR code to continue the same session in your phone's browser, or chat from WeChat, Feishu, or Telegram. +- [IM integrations](../im/index.md) — full setup steps for each of the five platforms. + +## Making it yours + +- [Desktop pet](./pets.md) — a little robot that floats on your desktop and shows you how the current task is going. Off by default. + +## Curious how it works inside + +The pages above cover usage. For architecture and source, start at the [architecture overview](../internals/index.md); the desktop process boundaries are in [Desktop Architecture](../internals/desktop.md). diff --git a/docs/en/desktop/pets.md b/docs/en/desktop/pets.md index 3c656873..c77fd97b 100644 --- a/docs/en/desktop/pets.md +++ b/docs/en/desktop/pets.md @@ -1,203 +1,120 @@ -# Desktop Pets +--- +title: Desktop pet +nav_title: Desktop pet +description: A little robot on your desktop that shows you how the current task is going. +order: 8 +--- -Desktop Pets are optional companions that live in a small transparent window outside the main app. They reflect a few useful session states, provide a quick way back to active sessions, and can be replaced with your own local artwork. +# Desktop pet -Pets are an **Electron Desktop feature**. They do not appear in H5 or the browser-only Web UI. +A small robot that floats above your desktop and acts out what's happening on this machine — head down and busy while a task runs, glancing around while it waits for your approval, slumped over when something failed. Glance at it while you're doing something else and you'll know whether to switch back. -![Desktop Pet settings with the four built-in companions and appearance controls](../../images/desktop_ui/14_pet_settings_overview.png) +It's a status indicator and a shortcut, nothing more. It can't approve permissions for you, and you can't type into it. -## Enable a pet +## Turning it on -1. Open **Settings → Pets**. -2. Turn on **Show desktop pet**. -3. Choose one of the four built-in companions: - - **Dada**, the coding companion; - - **Huhu**, the planning companion; - - **Bubu**, the fixing companion; - - **Huihui**, the building companion. -4. Adjust the size between `96px` and `192px`. -5. Choose whether to play animations and show the active-task panel. +The pet is **off by default**: -The selected pet, size, window position, animation preference, and task-panel preference are restored between launches. +1. Click **Settings** at the bottom of the sidebar. +2. Choose the **Pets** tab. +3. Pick one from **Built-in pets**. +4. Turn on **Show desktop pet**. -## Interact with the floating pet +![Settings → Pets: four built-in pets and appearance controls](../../images/app/settings-pets.webp) -| Action | Result | +## The four built-in pets + +| Character | Who it is | |---|---| -| Move the pointer near an idle pet | Its gaze follows the pointer; entering the pet also triggers a short jump | -| Click the pet | Bring the main Claude Code Haha window forward and play a wave | -| Drag the pet | Move the floating window; the pet runs in the drag direction | -| Right-click the pet | Open the menu for closing the pet window | -| Click the numbered task badge | Expand the active-session panel | -| Click a session in the panel | Return to that session in the main window | -| Click the arrow below the task list | Collapse the panel back to the numbered badge | +| Dada | A steady coding robot that helps build ideas one block at a time | +| Huhu | A pencil-and-notepad planning robot that maps a way through complex tasks | +| Bubu | A wrench-carrying repair robot with a knack for spotting and fixing cracks | +| Huihui | A gear-carrying build robot that perks up whenever a new reply arrives | -The pet can visibly distinguish working, waiting for you, failed, and idle states. The panel is a navigator, not an approval surface: return to the main app to handle any pending interaction, approve or deny a tool, stop work, or inspect full output. +Switching characters updates an already-open pet window immediately. -When there are no active sessions, the task panel stays hidden. Disabling **Show active task panel** keeps active work behind the numbered badge instead of removing or stopping it. +## Interacting with it -The animation switch affects the pet only. Turning it off does not stop sessions. Claude Code Haha also respects the operating system’s reduced-motion preference. +![The pet floating on the desktop](../../images/app/pet-desktop.webp) -## Create a custom pet +- **Hover** — while idle it hops, and its gaze follows your pointer. +- **Click** — brings the main window forward with a wave. Note that it only raises the window; it doesn't jump into a particular session. +- **Drag** — move it anywhere on the desktop. It tries to return to the same spot next launch. +- **Right-click** — choose **Close pet** from the system menu. That closes the floating window only; no task is stopped. To bring it back, return to **Settings → Pets**. -Select **Add pet** under **Your pets**. The dialog offers three ways to make one. Images are processed entirely on your own computer: nothing is uploaded, and no chat quota is used. +With **Show active task panel** enabled, a panel appears beside the pet whenever work is in flight, grouped by state: -![Custom pet creation dialog with three ways to make a pet](../../images/desktop_ui/15_pet_create_methods.png) +- **Working** — a session, background task, or agent is running. +- **Waiting for you** — something needs a permission approval or other input. +- **Needs attention** — the last run failed. -Before choosing a file, enter: +Clicking a row raises the main window and opens that session. Permissions are still approved in the main window. With the panel off, active work collapses to a numeric badge you can click to expand. -- a Pet ID of at most 73 characters, containing only lowercase letters, numbers, and single hyphens, such as `docs-helper`; -- a display name; -- a short description. +## Appearance controls -The ID must be unique among your custom pets. +- **Pet size** — anywhere between 96 and 192 pixels. +- **Play animations** — turn it off and the pet stays but stops moving. Useful when you want it quieter without dismissing it. +- **Show active task panel** — see above. +- **Collapsed by default** — show just the pet and expand the task panel only when you want it. -### Option 1: use a picture you already have +## Making your own -The quickest route, about a minute. Pick a static image with a transparent background and the app adds breathing, floating and status motion locally. +Click **Add pet** to the right of **Your pets**. Every image is processed on your own machine — nothing is uploaded, and nothing costs conversation credits. -The image must have: +All three routes ask for the same three fields first: a **pet ID** (lowercase letters, digits, and single hyphens, e.g. `moon-cat`), a **display name**, and a **description**. -- PNG or WebP format (APNG and animated WebP are rejected); -- width and height between `32px` and `4096px`; -- no more than `16,777,216` total pixels; -- a file size no larger than `8MB`. +### Route 1: use an image you already have -This kind of pet only sways gently. It **will not run, and it will not track your cursor**. For full motion, use option 2. +The quickest — about a minute. Pick a static PNG or WebP with a transparent background and the app adds gentle breathing and floating motion. -### Option 2: draw one with AI that runs and jumps +This kind of pet **won't run, wave, or track your cursor**. For that, use one of the routes below. -About ten minutes, and you need an AI that can draw. The dialog walks you through the prompt, the reference template and the checks. +### Route 2: have an AI draw an action sheet -#### Step 1: have an AI draw an action sheet +About ten minutes and an image-generating AI. The dialog already contains a complete prompt and a reference template; click **Copy this prompt** and paste it into the AI, replacing the two "character" lines with what you want. -Open any AI that can draw (Nano Banana, ChatGPT image generation, Midjourney, Stable Diffusion and Jimeng all work — these are examples, not endorsements) and send it the whole block below, replacing the two "Character" lines with what you want: +What it needs to draw is an **8-column by 9-row** action sheet, one frame per cell: -```text -Draw me a game character action sheet (sprite sheet). - -[Character] -A round-headed orange kitten wearing a small blue scarf, chibi -three-heads-tall proportions, 3D cartoon render, soft glossy -surface, bright cheerful colours. -(Replace these lines with your own character - the more specific the better) - -[Whole image] -- Fully transparent background: no backdrop colour, no grid lines, no text, no drop shadow -- Divide the image evenly into 8 columns x 9 rows, 72 equally sized cells -- One action frame per cell, character centred with a little margin around it -- Every cell must show the same character with identical proportions, colours and art style -- Leave unused cells fully transparent - -[What to draw in each row] -Row 1, first 6 cells: standing still with a gentle breathing bob -Row 2, all 8 cells: a full run cycle facing right, always facing right -Row 3, first 4 cells: raising a hand and waving hello -Row 4, first 5 cells: crouch, leap, land -Row 5, all 8 cells: dejected and downcast, head lowered, sighing -Row 6, first 6 cells: waiting in place, glancing around -Row 7, first 6 cells: head down, busy working -Row 8, all 8 cells: head and gaze starting straight up, turning slowly to the right through upper-right, right and lower-right, ending near straight down -Row 9, all 8 cells: continuing from straight down, turning left through lower-left, left and upper-left, back to near straight up -``` - -Getting it wrong on the first try is normal. Ask for a redraw, or say "keep the character, redraw row 2 only". - -#### Step 2: check it against the template - -![Action sheet template: 8 columns by 9 rows, labelled with what each row should contain](../../images/desktop_ui/17_pet_action_sheet_en.png) - -When the picture is ready, check three things before importing: - -1. **The background is see-through, not white.** A white backdrop becomes a square on your desktop; this is the most common mistake. -2. **8 cells across, 9 rows down**, one action per cell. -3. **The same character throughout**, with no change of face, colours or proportions. - -**Save the template** in the dialog writes this reference image to disk so you can lay out frames against it. - -#### Step 3: pick the file - -Fill in the ID, name and description, then select the picture. **You do not need to resize anything** — see "Sizes are aligned for you" below. - -### Option 3: I already have an action sheet - -If you have drawn a sheet already, or hold a finished atlas, this path skips the walkthrough and goes straight to the form. Validation is identical to option 2. - -### What to draw in each row - -You only draw **nine rows**; the app derives the rest: - -| Row | Content | Frames needed | -|-----|---------|---------------| -| 1 | Idle: standing still with a gentle breathing bob | 6 | -| 2 | Run right: full run cycle, always facing right | 8 | -| 3 | Wave: raise a hand and greet | 4 | +| Row | Content | Frames used | +|---|---|---| +| 1 | Idle: standing still with a slight breathing motion | 6 | +| 2 | Run right: a full run cycle, always facing right | 8 | +| 3 | Wave: raising a hand in greeting | 4 | | 4 | Jump: crouch, leap, land | 5 | -| 5 | Fail: discouraged, head down, sighing | 8 | -| 6 | Wait: looking around, shifting in place | 6 | +| 5 | Fail: dejected, head down, sighing | 8 | +| 6 | Wait: looking around, pacing in place | 6 | | 7 | Work: head down, busy | 6 | -| 8 | Gaze, upper half: straight up turning clockwise to near straight down | 8 | -| 9 | Gaze, lower half: continuing from straight down back to near straight up | 8 | +| 8 | Gaze, upper arc: from straight up around to nearly straight down | 8 | +| 9 | Gaze, lower arc: from straight down back around to nearly straight up | 8 | -Notes: +"Run left" doesn't need drawing — the app mirrors row 2 horizontally. The last two rows are optional too; repeat the first idle frame and the pet simply won't track your cursor. -- **You do not draw "run left".** The app mirrors row 2 horizontally to produce it. -- **The last two rows are optional in practice.** Repeat the first idle frame and the pet simply will not track your cursor; everything else still works. -- Leave unused cells at the right of each row fully transparent. +When the image comes back, check three things against the template in the dialog: the background is genuinely transparent rather than white, it really is 8 across by 9 down, and it's the same character in all nine rows. If not, ask the AI to redraw — "keep the character, redraw row 2 only" works well. -### Sizes are aligned for you +### Route 3: I already have an action sheet -After you pick the file, the app assembles the runtime atlas locally: it slices the sheet on an 8 × 9 grid, rescales each cell to `192 × 208`, centres the character, mirrors the run row, and fills in the remaining runtime rows to reach `1536 × 2288`. +Skip the tutorial and go straight to the form. The validation rules are identical to route 2. -That means: +### You don't have to compute the size -- **Exact dimensions are not required.** Common AI output sizes such as `1024 × 1152` work; a ratio close to 8:9 gives the best result. The reference size is `1536 × 1872`. -- **A finished `1536 × 2288` atlas is kept byte-for-byte** and is never resampled. -- The assembled file stays under `8MB`; WebP encoding is used automatically if a lossless PNG would exceed it. +Once you pick the image, the app assembles the runtime atlas locally: it slices the 8×9 grid, scales proportionally, centers the character in each cell, mirrors the run-left row, and fills in the remaining rows. -### Common problems +So the **dimensions don't need to be exact**. Common AI output sizes like `1024 × 1152` work fine, and anything close to an 8:9 ratio works best. An existing `1536 × 2288` atlas is kept as-is and never rescaled. -| Message | Cause and fix | -|---------|---------------| -| This image has no transparent background… | The sheet was exported on a white or coloured backdrop. Ask for a transparent PNG, or remove the background with an editor. | -| This image cannot be sliced into 8 columns by 9 rows | The row or column count is off. Confirm 8 across and 9 down, or use a finished `1536 × 2288` atlas. | -| That image could not be read | An animated file (APNG / animated WebP) or a corrupt one. Use a static PNG or WebP. | -| That image is too big | The source exceeds 8 MB. Compress it, or ask for smaller output. | -| A pet with this ID already exists | Choose a different Pet ID, or remove the existing one (see "Storage and removal"). | +The image itself must be: a static PNG or WebP (no animated formats), between 32 and 4096 pixels on each side, at most 16,777,216 pixels total, and under 8 MB. -When the size, format, or image content does not meet these rules, the app refuses to create the pet and shows the matching error instead of adding an invalid entry. +:::warning +The most common failure is a **white background**. A white-backed image becomes a rectangle sitting on your desktop, so the app rejects it outright. Have the AI re-export a transparent PNG, or remove the background yourself. +::: -After a successful import, the new pet is selected automatically and appears under **Your pets**. +## Where they live, and deleting one -![A locally imported custom pet selected in Desktop Pet settings](../../images/desktop_ui/16_pet_custom_result.png) +Custom pet packages live in `${CLAUDE_CONFIG_DIR:-~/.claude}/cc-haha/pets`, and there's an **Open folder** button at the bottom of the settings page. Each pet gets its own subdirectory containing `pet.json` and its images. -## Storage and removal +There's no delete button in the UI yet. To remove one: select a built-in pet first, click **Open folder**, delete only that pet's subdirectory, then click **Refresh** back in settings. Don't delete the whole `pets` or `cc-haha` directory. -Custom pet packages are stored under: +A hand-edited `pet.json` or a swapped image may fail validation; invalid packages are skipped and reported in the settings page. -```text -${CLAUDE_CONFIG_DIR:-~/.claude}/cc-haha/pets -``` +## Limits -Use **Open folder** in Pet settings to open the resolved directory. This is also the current removal path: - -1. Select a built-in pet first if the package you are removing is active. -2. Open the custom pet folder. -3. Remove only the custom pet’s own package directory. -4. Return to Pet settings and select **Refresh**. - -Closing or disabling the floating window does not delete a custom pet. If a selected package goes missing, the app falls back to a built-in pet. - -The loader skips invalid or unsafe packages and reports how many folders could not be loaded. Do not replace package files while an import is still running, and avoid symbolic links or unsupported animated image formats. - -## Privacy, safety, and boundaries - -- Image selection, validation, copying, and lightweight animation are local operations. -- Importing a pet does not send the image to the selected chat model. -- A pet can show summarized task state and navigate to a session, but it cannot approve permissions, answer model questions, or control a task directly. -- Pets do not run in H5, IM integrations, or a browser-only deployment. -- The pet observes the local Desktop service; it is not a cloud monitor and does not stay active after the Desktop app exits. -- Always-on-top behavior, dragging across displays, and window placement can vary by operating system and desktop environment. -- A successful application build does not by itself prove pet-window behavior on every supported operating system. - -If the pet does not move, check both **Play animations** and the operating system’s reduced-motion setting. If it does not appear at all, turn **Show desktop pet** off and on once, then restart the Desktop app before editing stored files manually. +The pet is desktop-only and never appears in H5. It stops working when you quit the app or the machine sleeps. Always-on-top behavior, dragging, and multi-monitor placement are all subject to what each operating system allows. diff --git a/docs/en/desktop/remote.md b/docs/en/desktop/remote.md new file mode 100644 index 00000000..4b426662 --- /dev/null +++ b/docs/en/desktop/remote.md @@ -0,0 +1,96 @@ +--- +title: Phone (H5) and IM +nav_title: Phone and IM +description: Continue a session in your phone's browser, or chat from WeChat, Feishu, or Telegram. +order: 9 +--- + +# Phone (H5) and IM + +A task is running on your computer and you want to check on it, or add one more instruction, from your phone. Two routes: + +- **H5 Access** — open the same interface in a mobile browser: sessions, messages, attachments, permission buttons, all of it. +- **IM Adapters** — talk to Claude directly inside WeChat, DingTalk, WhatsApp, Telegram, or Feishu. + +Both require your computer to be on with the app running. They expose the local desktop service; they don't move anything to a cloud. + +## H5 Access + +![Settings → H5 Access: QR code and token](../../images/app/settings-h5.webp) + +### Turning it on + +1. Open **Settings → H5 Access**. +2. Turn on **Enable H5 access** and confirm after reading the warning. +3. Click **Generate token**. A QR code and an H5 link appear. +4. Scan it with your phone, or click **Copy launch URL** and send it to your own device. + +The scanned link carries the server address and the token. Once your phone's browser connects, it remembers the connection and later visits go straight in. + +![The mobile conversation view with a file-changes card](../../images/app/h5-session.webp) + +### The token is the credential + +Anyone holding that link can reach what your desktop exposes. So: + +- Never paste a link containing the token into a group chat, a public issue, a log screenshot, or any public page. +- Only enable it on networks you trust. For anything reachable from the internet, put HTTPS, a VPN, or access control in front of it — don't rely on one long-lived token. +- If you suspect a leak, click **Regenerate token**. The old QR code and old token stop working immediately. Turning H5 off and on again does *not* rotate the token. + +Turn it off when you're not using it. That immediately rejects remote access while keeping the token, so the same one still works next time. + +:::warning +H5 is off by default, and it isn't a public service. Confirm you're on a network you trust before enabling it. +::: + +### Access host and fixed port + +**Access host / IP** takes your computer's current LAN IP, e.g. `192.168.1.20`, on the current service port. After switching Wi-Fi or unplugging Ethernet the old IP may no longer be yours; the page notices and offers the working one. + +If you run your own reverse proxy, put the full URL here instead — `https://cc.example.com` — and add that origin to **Allowed origins**. + +A **fixed port** is worth setting when a phone bookmark has to stay valid, a firewall only opens specific ports, or Nginx or Caddy forwards to one fixed upstream. The port must be between 1024 and 65535, and the change takes effect after a restart — the page tells you which port is currently live in the meantime. + +### Locking your phone won't kill the task + +That's what **Disconnect grace** is for: when your phone locks, backgrounds the tab, or drops off the network briefly, **a running task is not stopped**. It finishes in the background and the result is waiting when you reconnect. + +Only when a task is idle *and* nothing is connected does the CLI process stop, after the grace period. The default is 30 seconds and the valid range is 5 seconds to 24 hours. Raise it — say to 600 — if you're operating remotely for a while. + +### What works on a phone + +Session list and project switching, sending messages, stopping, streaming replies, image and file attachments, permission buttons, questions from Claude, `@` file references, copy and fork — the whole conversation flow. + +The desktop workspace, embedded terminal, native "open with", Computer Use authorization, and the desktop pet are not part of H5. + +## IM Adapters + +![Settings → IM Adapters: pairing and the five platforms](../../images/app/settings-im.webp) + +**Settings → IM Adapters** supports five platforms, each connected differently: + +| Platform | How to connect | +|---|---| +| WeChat | Generate a QR code in settings and scan it with WeChat to bind the account | +| DingTalk | Scan to create and authorize a bot in one step, or fill in Client ID / Secret manually | +| WhatsApp | Generate a QR code and scan it under **Linked devices** in WhatsApp | +| Telegram | Get a bot token from @BotFather and paste it in | +| Feishu | Enter an App ID and App Secret; if you don't have a bot, the page can create one from a template | + +### Binding an account is not the same as allowing a person + +This is the step people miss: **scanning only binds the account's messaging capability. Who is allowed to talk to the bot is a separate question.** + +Under **Pairing**, click **Generate pairing code**, then send that code to the bot in a direct message from your own IM account. That completes the binding. Alternatively, list user IDs under **Allowed users**. + +When both are empty, everyone is denied — that's deliberate, not a bug. + +Paired users are listed below and can be unbound at any time; unbinding requires pairing again. + +### Other settings + +- **Default project** — the working directory for new IM sessions. Left empty, it uses your current user working directory. +- **Streaming card mode** — updates the message content live, so it reads more like watching it type. +- **Permission requests** — DingTalk can use an interactive card template ID for button-based approval. Without it, every platform falls back to the `/allow`, `/always`, and `/deny` text commands. + +For each platform's application flow, permission configuration, and troubleshooting, see [IM integrations](../im/index.md). diff --git a/docs/en/desktop/schedule.md b/docs/en/desktop/schedule.md new file mode 100644 index 00000000..1dab0e52 --- /dev/null +++ b/docs/en/desktop/schedule.md @@ -0,0 +1,56 @@ +--- +title: Scheduled tasks +nav_title: Scheduled tasks +description: Run a saved prompt on a schedule — a code review every morning, for example. +order: 5 +--- + +# Scheduled tasks + +Save a prompt and have it run on a schedule. The usual jobs: review yesterday's commits every morning, tidy the backlog weekly, check a service's logs hourly. + +## Where to start one + +Click **Scheduled** in the sidebar to open the task list, then **+ New task** in the top right. + +The list page carries a standing notice about the following. + +:::warning +Scheduled tasks only run while the desktop app is open and your computer is awake. Close the lid, quit the app, or shut down and nothing fires — and nothing is caught up afterwards. This is not a cloud scheduler. +::: + +## Every field in the form + +![The New scheduled task dialog: name, description, prompt, frequency, notifications](../../images/app/schedule-create.webp) + +- **Name** (required) — the identifier in the list. A hyphenated form like `daily-code-review` reads well. +- **Description** (required) — one line about what it does, so you recognize it later. +- **Prompt** — what actually gets sent to Claude. Give it the full context: what to look at, what to care about, what format to output. You won't be there to clarify. +- **Permissions** — fixed at **Full permissions (fixed)**; the form doesn't offer a choice. The prompt editor shows the directory scope of the run right below. So: **pick the narrowest possible working directory, and read your own prompt once before saving.** +- **Model** — set per task, independent of your session default. Routine checks are fine on a cheaper model. +- **Working directory** — where the task runs. You can also enable **Isolated worktree** so the task works in a separate Git worktree and never touches your main branch. +- **Frequency** — every N minutes, every N hours, daily, weekdays (Mon–Fri), specific days, monthly, or a custom cron expression. Time-of-day options get their own picker beside the frequency. Custom cron is "minute hour day month weekday" and invalid expressions are flagged as you type. +- **Push notification on completion** — pick channels: native desktop notifications and any IM channels you've configured. If no IM channel is set up, the form points you to **Settings → IM Adapters**; setup steps are in [Phone (H5) and IM](./remote.md). + +Desktop notifications need **System Notifications** enabled in **Settings → General** and permission granted at the OS level. + +Tasks fire with a small random delay so a dozen jobs don't all start on the same tick. + +## Once it's running + +Each task card offers: + +- **Run now** — don't wait for the next trigger. Do this once right after creating a task to confirm the prompt works. +- **Logs** — the execution log, one row per run, with status (running, completed, failed, timeout) and duration. **Summary** shows the output excerpt; **View conversation** jumps to the full session for that run. +- **Edit** — change any field, including the frequency. +- **Disable / Enable** — pause without deleting. +- **Delete** — removes the task and all of its logs permanently. + +The three numbers at the top of the list are total tasks, active, and disabled. + +## A few habits worth having + +1. **Run it manually before setting a frequency.** Prompt problems show up on the very first run. +2. **Keep the working directory narrow.** The task runs with full permissions, so the directory is its boundary. +3. **Don't schedule it too tightly.** A task that runs every minute burns tokens fast and floods the log. +4. **Ask for a conclusion, not a transcript.** Put "summarize in three lines at the end" in the prompt, or the notification will be unreadable. diff --git a/docs/en/desktop/sessions.md b/docs/en/desktop/sessions.md new file mode 100644 index 00000000..adb2efce --- /dev/null +++ b/docs/en/desktop/sessions.md @@ -0,0 +1,100 @@ +--- +title: Sessions, permissions, and review +nav_title: Sessions +description: A session from first question to reviewed diff, and which button to press when Claude asks. +order: 1 +--- + +# Sessions, permissions, and review + +A session is one complete collaboration: you describe what you want, Claude reads files, runs commands, and edits code, and every step stays in the conversation where you can go back and check it. This page explains what each part of the session screen does. + +## Starting a session + +Click **New session** in the sidebar, or press `⌘N` (`Ctrl+N` on Windows and Linux). The empty session asks you for exactly one thing: a project directory. After that you can start typing — the model and permission mode come from your defaults in Settings. + +Each session opens as a tab, and you can run many side by side. A dot on the tab means that session is still running; closing a running tab asks whether you want to **Keep running** or **Stop and close**. + +The small line under the session title is metadata: project path, branch, model. A session is bound to one directory — to work on a different project, start a new session. + +## Reading the conversation + +![A full session: a question, tool cards, a thinking block, a file edit with an inline diff](../../images/app/session-main.webp) + +Claude doesn't just reply with a paragraph. Several kinds of card appear along the way: + +- **Tool cards** — reading files, searching, running commands. Consecutive operations of the same kind collapse into one line, like "Read 6 files" or "edited 3 files"; expand it to see the details. Skim them normally, open them when something goes wrong. +- **Thinking blocks** — the reasoning before it acts, labelled **Thinking** while it runs and **Thought** once done. Collapsed by default. +- **File edits** — shown as an inline diff right in the conversation, so you don't have to look anywhere else. +- **Claude needs your input** — when it's genuinely unsure it asks, with buttons for the likely answers plus a free-text box. + +In a long conversation, `⌘F` opens find-in-page and jumps between matches in the current session. `⌘K` is global search across every session you've ever had. + +## The permission prompt: which button? + +![The "Allow Claude to Edit index.html?" prompt with a preview of the change](../../images/app/session-permission.webp) + +In the default permission mode, Claude stops and asks before editing a file or running a risky command. The dialog previews the change, then offers three buttons: + +- **Allow** — just this once. The same operation will ask again next time. +- **Allow for session** — stop asking for this kind of operation in this session. It resets when the session closes. +- **Deny** — don't run it. Claude gets the refusal and tries another approach. + +When in doubt, pick **Allow** — being asked a few extra times costs nothing. If you can't tell what it's about to do, click **Show full input** to see the raw arguments. + +### The five permission modes + +The permission button in the composer toolbar sets the overall strictness: + +| Mode | What it does | +|---|---| +| Ask permissions | Confirm file edits and higher-risk commands when CLI asks | +| Auto accept edits | Claude writes to disk without asking | +| Auto mode | Claude reviews tool calls and runs actions it considers safe | +| Plan mode | Architecture and reasoning only, no files | +| Bypass permissions | Full tool access for shell and file system | + +**Auto mode** and **Bypass permissions** each require a one-time confirmation. In Plan mode Claude produces a plan without touching files; when it's done you get a "Ready to code?" prompt where you can approve the plan or send it back for changes. + +The permission mode is locked while a turn is running and unlocks when the turn finishes. + +:::warning +**Bypass permissions** hands over your shell and your entire file system. Use it only in an isolated environment you can restore. +::: + +## Undoing a turn + +After each turn, a card appears in the conversation reading "**{n} files changed**", listing every file that turn touched. It offers two actions: + +- **Undo current turn** — roll back the latest reply and restore the files it changed. +- **Roll back to before this turn** — for older turns: rewind both the conversation and the files to that checkpoint. + +Both ask for confirmation first. Some turns have no file checkpoint; in that case only the conversation rolls back, and the message says so. + +## The Activity panel + +The first button on the right of the tab bar opens the Activity panel, which lists everything running in parallel for this session: + +- **Tasks** — the to-do list Claude maintains for itself, with "Task progress 3/7" at the top. +- **SubAgents** — the agents it delegated to. Open one to read its full transcript. +- **Background tasks** — commands and workflows running in the background; each can be stopped individually. +- **Team** — when an Agent Team is in play, one row per member, and you can message a member directly. + +Tool activity from background subagents bubbles up here too, so you don't have to wait for one to finish to see what it's doing. + +## What the composer can do + +![The slash-command panel that opens when you type `/`](../../images/app/composer-slash.webp) + +- **`/` slash commands** — type `/` for the command panel. `/status` for session state and usage, `/context` for context breakdown, `/compact` to compress, `/review` to review changes, `/commit`, `/memory` to open project memory, `/doctor` to open the diagnostics check. +- **`@` file references** — type `@` for file search; the file you pick is attached to the message as a path. +- **Attachments** — click `+`, drag files in, or paste a screenshot. Images, PDFs, and directories all work. +- **Context usage ring** — the small ring shows how much of the context window is used; hover it for used, free, and window size. When it fills up, run `/compact`. +- **Model and effort** — switch models at any time. Effort has five levels — low, medium, high, xhigh, max — and models that don't support a level ignore it. +- **Location** — shows the current project and branch. In a Git project you can switch branches here, or turn on **Isolated worktree** to keep an experiment off your main branch. See [Workspace](./workspace.md). + +Enter sends and Shift+Enter inserts a newline by default; **Settings → General** can swap that to `Ctrl/Cmd+Enter`. `⌘.` stops the current generation. + +## Forking a conversation + +Every past message has **Fork a new conversation**. It branches a new session from that point: everything before it is kept, everything after is up for grabs. Use it when you want to try a different approach without losing the thread you already have. diff --git a/docs/en/desktop/settings.md b/docs/en/desktop/settings.md new file mode 100644 index 00000000..45728337 --- /dev/null +++ b/docs/en/desktop/settings.md @@ -0,0 +1,127 @@ +--- +title: Settings reference +nav_title: Settings +description: All 16 settings tabs — what each one configures and when you'd need it. +order: 6 +--- + +# Settings reference + +Click **Settings** at the bottom of the sidebar. Sixteen tabs on the left, in a fixed order. This page walks through them in that order: what each one configures, and when you'd actually need to touch it. + +## Providers + +Model access. Sign in to Claude, ChatGPT, or Grok with an account (no API key required), or add any Anthropic- or OpenAI-compatible service with an API key. + +You'll come here once during setup and rarely again. Full steps in [Connecting a model](../start/models.md). + +## General + +The tab you'll open most often — everything about how the app feels. + +![Settings → General: color themes, language, output style, default permissions](../../images/app/settings-general.webp) + +- **Color theme** — six of them: Pure White (default), Paper, Warm Classic, Celadon, Ink Night, Ink Blue. There's also **Follow the system**, which lets you pick which theme to use in light mode and which in dark mode. +- **Language** — the interface language. +- **Response Language** — makes Claude always reply in a given language, set independently of the interface. +- **Output Style** — Default, Explanatory, or Learning. Explanatory adds reasoning about implementation choices and codebase patterns; Learning pauses and asks you to write small pieces yourself. Running sessions keep their current prompt; the change applies to new sessions. +- **Default Session Permissions** — which permission mode new sessions start in. Each session can still be changed individually. +- **Effort Level** and **Thinking Mode** — defaults for new sessions. Turning thinking off sends an explicit non-thinking parameter to providers like DeepSeek that need one. +- **Message Sending** — Enter to send (Shift+Enter for a newline), or `Ctrl/Cmd+Enter` to send. +- **System Notifications** — route permission prompts, completed replies, and scheduled task results to the OS notification center. Enabling it requests system permission. +- **Network** — three modes: Direct connection (explicitly bypass the system proxy), System proxy (follow system or PAC rules per destination), or Manual proxy (a URL like `http://user:password@127.0.0.1:7890`). Below that, **AI request timeout**, which can go up to 1800 seconds when a provider is slow to first byte. App updates use their own proxy setting, over in About. +- **WebSearch** — how web search is routed. Auto prefers Claude's native WebSearch for Claude models and falls back to Tavily or Brave otherwise; those two need API keys you supply. +- **Auto-dream** — periodically tidies and compresses memory files in the background. Off by default, because it spends tokens. +- **UI Zoom** — scale the whole interface, also bound to `⌘+` / `⌘-`, with `⌘0` back to 100%. +- **Data Storage Location** — an advanced, rarely-touched setting. Defaults to the system directory `~/.claude`, or point it at an absolute path of your own. After switching, sessions, skills, MCP, plugins, and provider config are all read from the new directory; it needs a restart, and the two directories are never merged or migrated automatically. + +![The same session in the Ink Night dark theme](../../images/app/session-dark.webp) + +## H5 Access + +Continue the same session in your phone's browser. Off by default. See [Phone (H5) and IM](./remote.md). + +## IM Adapters + +Talk to Claude from WeChat, DingTalk, WhatsApp, Telegram, or Feishu, and manage paired users. See [Phone (H5) and IM](./remote.md) and [IM integrations](../im/index.md). + +## Terminal + +A real host shell embedded in the app, for installing plugins, skills, MCP servers, and anything else that needs a command line. The desktop app bundles `claude-haha`, so anywhere the docs say `claude ` you can run `claude-haha `. + +On Windows you can also choose the startup shell (system default, PowerShell 7, Windows PowerShell, Command Prompt, or a custom executable) and set a Bash path — used when a tool calls Unix commands like `grep` or `sed`, usually pointing at Git Bash. + +## MCP + +External tools and data sources. STDIO, Streamable HTTP, and SSE transports are supported, and the scopes match the CLI: + +- **Local** — only for you, but bound to one project. +- **Project** — written to the project's `.mcp.json` and shared with the team. +- **User** — written to your global config, active in every project. + +The three numbers at the top are total servers, currently connected, and needs attention. STDIO commands run directly on your machine, so runtimes like Node, Python, and Bun must be installed and on your `PATH`. + +## Agents + +Browse installed agents and create your own. See [Subagents](./agents.md). + +## Skills + +Every skill available on this machine, grouped by source, with the prose and source files readable in place. See [Skills and the Skills Market](./skills.md). + +## Memory + +View and edit the Markdown memory files Claude keeps per project. Pick a project on the left, a file in the middle, then edit or preview the rendered output. The files live in `~/.claude/projects//memory/` and are loaded by the CLI at runtime. + +`/memory` in any session jumps straight here. For how memory is written and recalled, see [Memory system](../internals/memory.md). + +## Plugins + +A plugin bundles skills, agents, hooks, and MCP servers together. This tab shows installed plugins, their health, and what capabilities each one exposes, with enable, disable, update, and uninstall — including multi-select for bulk operations. + +After enabling or disabling anything, click **Apply changes** to push the change into the running runtime. + +## Pets + +A little robot floating on your desktop. Off by default. See [Desktop pet](./pets.md). + +## Computer Use + +Let Claude read the screen, click, and type. Unusable until you install the runtime environment and grant system permissions. See [Computer Use](./computer-use.md). + +## Token usage + +![Settings → Token usage: heatmap and stat cards](../../images/app/settings-usage.webp) + +A usage dashboard computed from the Claude Code session records on this machine. Everything is calculated locally and nothing is uploaded. + +- Across the top: total tokens, peak tokens, longest task, current and longest streak. +- In the middle: a heatmap with daily, weekly, and cumulative views. Click a day for that day's sessions, tokens, messages, and tool calls. +- Below: activity insights — active rate, most-used model, skills used, fresh versus cache-hit token split, and estimated cost. Estimated cost excludes models with no known price, and the UI says how many were skipped. + +## Trace + +Records the model request chain for each session — requests, responses, status events, timings — for debugging stalls, failures, and unexplained waits. The switch isn't on this tab: turn on **Agent trace** in **Settings → General** first, and this tab fills up. + +Once enabled, new sessions write condensed records to a local traces directory. Existing records stay readable after you turn it off; only new ones stop being written. The trace list supports search, filtering (all / LLM / tools / errors), opening a trace in its own window, and deleting a session's trace without touching its chat history. + +## Diagnostics + +Where to go when something breaks. Logs server and CLI startup, provider, and session runtime errors. + +- Across the top: log size, event count, warnings in the last 24 hours, retention policy. +- **Recent events** lists the actual errors, each with an event ID you can copy on its own. +- **Export Bundle** / **Copy error summary** / **Copy issue report** — use the latter two when filing an issue; they're already formatted. +- **Doctor** — checks user and current-project configuration state, read-only, returning a healthy / not configured / missing / invalid list. `/doctor` in a session opens it directly. +- **Reset safe UI state** — clears only regenerable interface keys like open tabs, theme, and zoom. Chat history, model config, skills, MCP, IM, and OAuth are always protected and never touched. +- **Local index** — status and size of the derived SQLite index, with a rebuild button. Rebuilding only affects the index; source conversations are never deleted. + +:::info +Issue reports and exported bundles are redacted on a best-effort basis — chat contents, file contents, full environment variables, and API keys are omitted. Still give them a read before sharing, in case an internal hostname, username, or path slipped through. +::: + +## About + +Version, changelog, GitHub repo, feedback link. + +**App Updates** checks GitHub Releases, downloads, and restarts to install. Updates use their own proxy setting, separate from **Settings → General** — if updates stall on a corporate network, configure the advanced update proxy here. diff --git a/docs/en/desktop/skills.md b/docs/en/desktop/skills.md new file mode 100644 index 00000000..e2e07083 --- /dev/null +++ b/docs/en/desktop/skills.md @@ -0,0 +1,76 @@ +--- +title: Skills and the Skills Market +nav_title: Skills +description: A skill is a ready-made procedure for Claude. What to check before installing one. +order: 4 +--- + +# Skills and the Skills Market + +A skill is a procedure someone else already worked out. Install it and Claude follows it whenever the matching situation comes up. A "PDF handling" skill, for instance, tells it which library to use, what order to split pages in, and what to do with an encrypted file — so you don't have to explain it every time. + +**How it differs from an agent**: an agent is a worker with its own context and tool scope; a skill is knowledge and procedure that an agent loads. Delegating to an agent is hiring someone; installing a skill is handing them a manual. + +## The Skills Market + +![The Skills Market: cards, security badges, source filters](../../images/app/skill-market.webp) + +Click **Skills Market** in the sidebar. It aggregates two sources, **ClawHub** and **SkillHub**, and loads more as you scroll — there is no "load more" button. + +Three filters across the top: + +- **Source** — all sources, ClawHub, or SkillHub. +- **Security** — see below. +- **Install status** — all skills, installed, or not installed. + +The search box matches names and keywords. + +### Reading the security badges + +Each card carries a security badge. It reports what the **source** scanned, not an audit by this app: + +| Badge | Meaning | +|---|---| +| Verified | Publisher is verified and the skill passed the source's security scan | +| Scanned safe | The source's security scan found no risks | +| Not audited | The source provided no security audit data | +| Flagged | The source's scan flagged potential risk | + +"Not audited" doesn't mean unsafe — it means nobody checked. "Flagged" means don't install it unless you've read the files and understand exactly what they do. + +## Before you install + +A skill can bring new tools, scripts, and external dependencies, and once installed Claude may run them. The disclaimer at the top of the market means what it says: these skills come from third-party community sources and this app does not audit their contents. + +Recommended: + +1. Open the **Files** tab on the detail page and read `SKILL.md` and any accompanying scripts. +2. If you're unsure, have Claude look first — "check this skill's files for anything suspicious". +3. Confirm you actually need it. More skills isn't more capability; every skill costs context. + +**Install** opens a confirmation with the install location, the security note, and a reminder that the skill takes effect in new sessions. **Sessions already open won't pick it up** — start a new one. + +Uninstall lives in the same place and deletes the local files under that skill's directory. + +## Installed skills + +**Settings → Skills** lists everything available on this machine, grouped by source: + +- **User** — installed by you, in `~/.claude/skills/`. +- **Project** — shipped with the repo. +- **Plugin** — bundled by a plugin. +- **Built-in** — shipped with the app. + +Each entry shows its entry file, file count, and estimated token cost. Open one to switch between **doc mode** and **code mode** and read the skill's prose and source files directly. + +### The `.agents/skills` convention + +Besides `~/.claude/skills/`, the desktop app also reads `~/.agents/skills/`. That's an open standard directory shared with Codex, Cursor, Gemini CLI, and other clients — install once, use it everywhere. Skills from that directory are tagged `.agents` in the list. + +A project's own `.agents/skills/` works the same way: it ships with the repo rather than being something you installed. + +:::warning +A shared directory means skills another client installed also apply here. Scan **Settings → Skills** occasionally and make sure nothing on the list is a stranger. +::: + +For how skills are loaded and what the file format is, see [Skills internals](../internals/skills.md). diff --git a/docs/en/desktop/workspace.md b/docs/en/desktop/workspace.md new file mode 100644 index 00000000..fdeef414 --- /dev/null +++ b/docs/en/desktop/workspace.md @@ -0,0 +1,77 @@ +--- +title: Workspace +nav_title: Workspace +description: The right-hand panel — see what changed, review the diff line by line, preview a page in-app. +order: 2 +--- + +# Workspace + +The conversation tells you what Claude said. The workspace tells you what it actually changed. It's a panel on the right that you can pull out next to the conversation, so you never have to switch windows. + +## Opening it + +Click the folder icon on the right of the tab bar. Click it again to collapse. Drag the panel's left edge to resize. + +At the top of the panel is a **Files / Browser** switch: + +- **Files** — project files, Git changes, and diff review. +- **Browser** — a built-in browser for previewing the page you just changed. + +## Changed files and All files + +![The "Changed files" list, each row with a status marker and line counts](../../images/app/workspace-changes.webp) + +In Files mode there are two views: + +- **Changed files** — only files with uncommitted changes in this Git repo, each row showing its status (modified, added, deleted, renamed, untracked) and lines added or removed. This is where you'll spend review time. +- **All files** — the full directory tree. The search box above it matches file names across the whole project, including directories you haven't expanded yet. + +Click a file name to open a preview: a diff in two columns, or the file itself. Previews accumulate as tabs, like an editor. + +When the directory isn't a Git repo, **Changed files** says so plainly — that isn't an error. + +Any file can have its path copied, or be pushed back into the composer as context with **Add to chat**. + +## Diff review: leaving a note on a line + +![Diff review with syntax highlighting, old and new side by side](../../images/app/workspace-diff.webp) + +The diff keeps old and new lines with full syntax highlighting. The genuinely useful part is line-level comments: + +1. Click a line — a comment box opens beside it. +2. To comment on a range, hold `Shift` and click the first and last line. The selection must stay on one side of the diff and inside one hunk. +3. Describe what you want changed there and click **Submit**. +4. The comment goes back into the composer along with the file path, line numbers, and the code itself, ready to send. + +This is far more precise than describing "the null check in that one function" in prose. + +If Claude changes the file while you're writing a comment, the panel tells you the diff has updated and asks you to reselect — that's there to stop a comment from landing on the wrong lines. + +:::tip +Denied tool calls never reach disk, so they never show up in Changed files. Even so, give `git diff` one last read before you ship. +::: + +## Isolated worktree: keeping experiments caged + +The **Location** control in the composer offers **Isolated worktree**. Turn it on and the session gets its own Git worktree; everything Claude does happens there, and your current branch and working directory are untouched. + +When it earns its keep: + +- You want Claude to attempt a big rewrite you may not keep. +- Your working directory has uncommitted changes, so Git would block a branch switch. +- The branch you want is already checked out in another worktree. + +The temporary worktree is cleaned up when you're done. History stays readable, but to continue you'll need a new session in the original project — the app tells you when that's the case. + +## Built-in browser + +![The built-in browser previewing a page that was just edited](../../images/app/workspace-preview.webp) + +Switch the workspace panel to **Browser** and type a local dev address or any URL. Three buttons here exist specifically so Claude can see what you see: + +- **Capture** — send the current rendering back into the conversation. +- **Pick element** — click an element on the page; its selector, position, and a screenshot go to Claude as context. This saves an enormous amount of back-and-forth on styling. +- **Zoom** — change the preview scale to check responsive layouts. + +Logins and cookies in this browser are real, same as any browser. Before you demo or screenshot anything publicly, switch to a page that doesn't require signing in. diff --git a/docs/en/features/computer-use.md b/docs/en/features/computer-use.md deleted file mode 100644 index ea5bd977..00000000 --- a/docs/en/features/computer-use.md +++ /dev/null @@ -1,188 +0,0 @@ -# Computer Use Guide - -Computer Use lets a model inspect the screen and operate the mouse, keyboard, applications, and clipboard. It acts on the current computer, so review the authorization scope and platform differences before enabling it. - -## Supported platforms - -| Platform | Status | Important difference | -|---|---|---| -| macOS Apple Silicon / Intel | Supported | Requires Accessibility and Screen Recording; supports native screenshot filtering | -| Windows | Supported | Uses the Windows Python runtime; screenshots are not filtered to authorized windows | -| Linux | Unsupported | There is currently no Linux executor | - -The packaged desktop app does not require Bun. Computer Use does require a working Python 3 installation. The app creates an isolated virtual environment under the managed user configuration directory and installs the platform dependencies there. - -## Quick start - -### Desktop app - -1. Open Settings → Computer Use. -2. Enable Computer Use. -3. Check Python status. If automatic discovery fails, select a Python executable. -4. Run Install or Repair so the app can create the virtual environment and install dependencies. -5. On macOS, grant Accessibility and Screen Recording, then check again. -6. Choose the applications that may be controlled and, if needed, enable clipboard and system key permissions. -7. Start a session and describe both the goal and the applications involved. - -Start with a small, reversible task: - -```text -Take a screenshot and tell me what you can see. -Open Notes and create an empty note titled "Test". -Find the settings entry in the authorized app, but do not change anything. -``` - -### CLI - -Source mode requires the project dependencies and Python 3: - -```bash -bun install -python3 --version -./bin/claude-haha -``` - -Disable the dynamic Computer Use MCP with: - -```bash -CLAUDE_COMPUTER_USE_ENABLED=0 ./bin/claude-haha -``` - -Or set the managed configuration in `~/.claude/cc-haha/computer-use-config.json`: - -```json -{ - "enabled": false -} -``` - -The desktop Settings page updates the same managed configuration. Prefer the UI and do not overwrite unknown fields by hand. - -## Tools and Teach capability - -The codebase defines 27 Computer Use tools: - -| Category | Tools | -|---|---| -| Authorization | `request_access`, `list_granted_applications` | -| Screenshot | `screenshot`, `zoom` | -| Mouse | `left_click`, `right_click`, `middle_click`, `double_click`, `triple_click`, `left_click_drag`, `mouse_move`, `left_mouse_down`, `left_mouse_up`, `cursor_position`, `scroll` | -| Keyboard | `type`, `key`, `hold_key` | -| Applications | `open_application`, `switch_display` | -| Clipboard | `read_clipboard`, `write_clipboard` | -| Control flow | `wait`, `computer_batch` | -| Teach | `request_teach_access`, `teach_step`, `teach_batch` | - -There are 24 base control tools. The three Teach tools are exposed only when the host enables the Teach capability; otherwise a session sees only the base tools. - -Teach is intended for requests where the user wants to learn a workflow: - -1. `request_teach_access` requests access to the applications used by the tour. -2. `teach_step` shows an anchored explanation and waits for the user to choose Next. -3. `teach_batch` groups predictable steps to reduce model round trips. - -Teach authorization is separate from regular control authorization. Actions still pass the application allowlist and input safety checks. After the user exits a tour, the model must stop calling Teach tools. - -## How it works - -Computer Use follows a screenshot → analyze → act → screenshot loop: - -```text -Model - → Computer Use MCP tool - → TypeScript dispatch and safety checks - → Python bridge - → macOS / Windows system operation - → Screenshot or action result returned to the model -``` - -- Tool definitions and authorization logic live in `src/vendor/computer-use-mcp/`. -- CLI integration and the Python bridge live in `src/utils/computerUse/`. -- Platform executors live in `runtime/mac_helper.py` and `runtime/win_helper.py`. -- Desktop setup, permissions, and preauthorization are managed by `src/server/api/computer-use.ts` and `desktop/src/pages/ComputerUseSettings.tsx`. - -## Authorization model - -### Application access - -The model first calls `request_access` with the required applications and a reason. The user can approve or deny the request. Applications preauthorized in Settings provide managed defaults; they do not grant arbitrary access to every application. Adding an application during a run still follows the relevant authorization flow. - -Applications use three permission tiers: - -| Tier | Capability | -|---|---| -| `read` | Inspect screenshots without input | -| `click` | Click, move, and scroll without typing or higher-risk input | -| `full` | Allow keyboard, drag, and other complete input after the remaining checks pass | - -### Clipboard and system keys - -Clipboard read, clipboard write, and system-level key combinations are separate grants. Authorizing an application does not automatically authorize these capabilities. - -### Concurrency - -A session lock prevents multiple sessions from competing for the mouse and keyboard. If Computer Use reports that another session owns the lock, finish or stop that session instead of deleting the lock file. - -## Platform safety boundaries - -### macOS - -- Accessibility is required for application input. -- Screen Recording is required for screenshots. -- Screenshots support native window filtering, leaving authorized applications and the desktop visible. - -### Windows - -- The current screenshot filtering capability is `none`, so screenshots may include every visible window. -- The application allowlist still rejects input aimed at an unauthorized frontmost application. -- Because screenshot content is not filtered, close or minimize windows containing sensitive information before starting. - -### Protections that are not currently available - -- **There is no global Escape abort hotkey.** Use the current task's Stop action in the desktop app; a CLI run can be interrupted from its terminal. -- **Unauthorized windows are not automatically hidden before every action.** Do not rely on auto-hide for privacy. -- **Pixel staleness validation is disabled by default.** After the UI changes, the model should take another screenshot before clicking. - -These are current implementation boundaries and must not be described as completed safeguards. - -## Python runtime - -During first install or repair, the app: - -1. Synchronizes the helper and requirements for the current platform into the managed user directory. -2. Creates a venv with the detected or selected Python. -3. Installs or upgrades pip. -4. Uses the requirements content hash to decide whether dependencies need reinstalling. -5. Calls the platform helper with a JSON payload and parses a uniform JSON response. - -macOS primarily uses `mss`, Pillow, PyAutoGUI, and PyObjC. Windows also uses pywin32, psutil, pyperclip, and screeninfo. See `runtime/requirements*.txt` for the current version constraints. - -## Troubleshooting - -### macOS still reports missing permissions - -- Make sure the authorization applies to the app that actually launches Claude Code Haha. -- Fully quit and reopen the app after changing permissions. -- Run the permission check again from Settings. - -### Python installation fails - -- Select a specific Python 3 executable in Settings. -- Confirm that Python supports `venv`. -- Run Install or Repair again. -- Review the Computer Use installation log in Diagnostics. - -### Screenshots work but clicks fail - -- Confirm that the target application is authorized. -- Confirm that it is the frontmost application. -- Check whether the permission tier allows the requested action. -- Take a new screenshot after any UI change instead of reusing old coordinates. - -### Other windows appear in Windows screenshots - -This is a known boundary of the current Windows screenshot capability. The input allowlist does not filter screenshot content. Close or minimize sensitive windows first. - -## Learn more - -- [Computer Use architecture](./computer-use-architecture.md) diff --git a/docs/en/guide/faq.md b/docs/en/guide/faq.md deleted file mode 100644 index c50e86bf..00000000 --- a/docs/en/guide/faq.md +++ /dev/null @@ -1,153 +0,0 @@ -# FAQ and Support - -## What is the fastest way to get help? - -First, confirm that you are using [GitHub Releases Latest](https://github.com/NanmiCoder/cc-haha/releases/latest), then retry the shortest sequence that reproduces the problem reliably. - -If the Desktop app still opens: - -1. Go to **Settings → Diagnostics**. -2. Select **Copy Issue Report**. -3. Search [GitHub Issues](https://github.com/NanmiCoder/cc-haha/issues) for the same problem, and create a new issue only if there is no duplicate. -4. Paste the report and add the reproduction steps, expected result, and actual result. - -If the report is not enough, you can also select **Export Bundle**. Issue reports and bundles attempt to omit chat content, file content, complete environment variables, and API keys. They can still contain private metadata such as local paths or provider hostnames, so **review everything before sharing it**. - -Include: - -- The Claude Code Haha version -- Operating system, CPU architecture, and installer type -- Provider type and API format, but never the API key -- The shortest reproduction steps and complete error text -- Whether the problem occurs in Desktop, H5, or the CLI - -If the app cannot start, provide the installer version, a screenshot of the system error, and the last visible action before the failure. Do not delete or overwrite `~/.claude` as a troubleshooting shortcut. - -## Providers and OAuth - -### Do OpenAI-compatible services always require LiteLLM? - -No. A Custom Provider in the Desktop app supports: - -- Anthropic Messages (native) -- OpenAI Chat Completions (local proxy translation) -- OpenAI Responses API (local proxy translation) - -Consider an external gateway such as LiteLLM only when the upstream service uses a protocol or custom fields that the app does not support. - -### A custom provider returns 401 or “API Key invalid” - -Edit the provider under **Settings → Providers**, then check: - -1. The base URL is the API root required by the provider, without a missing or duplicated path. -2. The API format matches the real upstream endpoint. -3. The authentication strategy is correct. Some Anthropic-compatible services use a Bearer token, while the official Anthropic API uses `x-api-key`. -4. The main model ID exists and the current credential can access it. -5. Select **Test Connection**. Resolve the first connectivity failure before the second proxy-translation result shown for OpenAI formats. - -Do not expose URL query credentials, tokens, or API keys in screenshots, issues, or diagnostic attachments. - -### Claude, ChatGPT, or Grok sign-in does not complete - -- Keep Claude Code Haha running while the sign-in flow is open. -- Allow the system browser to open the authorization page and complete authorization with the intended account. -- Confirm that the system clock is correct and that a proxy, firewall, or browser extension is not blocking the provider page or local OAuth callback. -- If the browser reports success but the app does not update, return to **Settings → Providers** and start the sign-in flow again. - -If it still fails, copy an Issue Report and state whether the browser did not open, the authorization page failed, or the callback did not return to the app. Never share an OAuth code, access token, or browser cookie. - -### The connection test succeeds, but chat still fails - -Confirm that the current task selected the new Provider and model; saving a Provider does not necessarily select it for every task. Also check that the account supports the configured main and role-model mappings. - -Create a new task and retry with a short plain-text message. If it still fails, copy an Issue Report from Diagnostics. A successful connection test proves basic endpoint, authentication, and translation behavior; it does not verify every model capability, tool call, or long-context combination. - -## Installation and Updates - -### macOS says the app cannot be verified, is damaged, or will not open - -Confirm that the installer came from the [official Latest Release](https://github.com/NanmiCoder/cc-haha/releases/latest) and that you selected the correct Intel or Apple Silicon architecture. A signed and notarized public release should normally show only the standard download-source confirmation. - -Older, draft, or temporary unsigned builds may need extra approval. See the [Desktop installation guide](/en/desktop/04-installation#macos). Do not bypass system security prompts for packages from unknown mirrors. - -### Windows shows SmartScreen - -Confirm that the installer came from the official Release. An unsigned Windows installer may trigger SmartScreen. Expand “More info,” verify the file name and source, and then decide whether to run it. - -Quit Claude Code Haha completely before an in-place update. If the installer reports that a process is still running, close the relevant window and background process before retrying. Do not delete the user configuration directory first. - -### A Linux AppImage does not start - -Make it executable: - -```bash -chmod +x Claude-Code-Haha--linux-.AppImage -``` - -Some distributions also require FUSE. See the [Desktop installation guide](/en/desktop/04-installation#linux) for distribution-specific instructions. - -## H5 Access - -### A phone or another computer cannot connect to H5 - -Check each item in order: - -1. **H5 Access** is enabled in the Desktop app. -2. You are using the Server URL currently shown in Settings or encoded in the current QR code, not an old LAN address. -3. The client has the current H5 token. -4. Both devices are on networks that can reach each other, and the system firewall allows the configured port. -5. A reverse proxy forwards both HTTP and WebSocket traffic and uses an allowed origin. - -H5 is a remote entry point to the current Desktop service, not the complete Desktop environment. Terminals, native previews, pet windows, and some system capabilities remain Desktop-only. - -See [H5 access](/en/desktop/06-h5-access) for deployment and security details. - -## Git Branches and Worktrees - -### Creating an isolated worktree fails - -Common causes include: - -- The selected directory is not a Git repository -- The branch does not exist or is already checked out by another worktree -- Uncommitted changes prevent the requested Git operation from running safely -- The target worktree path already exists or is not writable - -Read the exact error shown in the app. You can use the current working tree, select a different branch, or handle the existing Git changes safely before retrying. Do not automatically delete an existing directory or discard uncommitted changes to make a worktree. - -### Should I use the current working tree or an isolated worktree? - -- **Current working tree**: continue work that already exists in the selected directory. -- **Isolated worktree**: run a parallel task or keep a branch separate from the current checkout. - -If the current directory contains important uncommitted changes, inspect the Git status and make a backup before choosing. - -## Computer Use - -### Computer Use is unavailable or cannot control an app - -Open **Settings → Computer Use** and confirm: - -- The global feature switch is enabled -- Python and dependency checks pass -- Required macOS or Windows system permissions are granted -- The target app is in the list of apps allowed for control -- The current session's permission request was explicitly approved - -After granting a system permission, reopen Claude Code Haha or the target app. Computer Use is not currently supported on Linux. A successful Desktop installation does not mean that Computer Use is fully configured. - -If Settings still shows an error, copy an Issue Report and include the status shown on that page. Avoid uploading a full-screen screenshot that exposes content from other apps. - -## CLI - -### `bun install` or the CLI fails to start - -Confirm that the shell is in the repository root and that Bun is current enough for the project: - -```bash -bun --version -bun install -./bin/claude-haha --help -``` - -If the error mentions a missing Bun built-in such as `bun:bundle`, upgrade Bun. See [Get Started in 3 Minutes](./quick-start.md#option-2-run-the-cli-from-source) for installation and the [CLI reference](./cli-reference.md) for commands. diff --git a/docs/en/guide/global-usage.md b/docs/en/guide/global-usage.md deleted file mode 100644 index 061f4c7c..00000000 --- a/docs/en/guide/global-usage.md +++ /dev/null @@ -1,58 +0,0 @@ -# Global Usage (Run from Any Directory) - - -If you want to run `claude-haha` directly from any project directory, set up one of the following. Once configured, `claude-haha` will automatically recognize your current working directory. - -## macOS / Linux - -Add to `~/.bashrc` or `~/.zshrc`: - -```bash -# Option 1: Add to PATH (recommended) -export PATH="$HOME/path/to/claude-code-haha/bin:$PATH" - -# Option 2: Alias -alias claude-haha="$HOME/path/to/claude-code-haha/bin/claude-haha" -``` - -Then reload the config: - -```bash -source ~/.bashrc # or source ~/.zshrc -``` - -## Windows (Git Bash) - -Add to `~/.bashrc`: - -```bash -export PATH="$HOME/path/to/claude-code-haha/bin:$PATH" -``` - -### Windows + WSL Toolchains - -If `claude-haha` runs on Windows / Git Bash but tools such as Node, Python, uv, or bun are installed inside WSL, call them through WSL explicitly: - -```bash -wsl -e bash -lc 'node --version && python3 --version' -``` - -When cc-haha detects `wsl` / `wsl.exe`, it automatically sets `MSYS2_ARG_CONV_EXCL=*` so Git Bash does not rewrite WSL paths such as `/home/...` into `C:/Program Files/Git/home/...`. - -To route Bash tool commands through WSL by default, set this before startup: - -```bash -export CLAUDE_CODE_SHELL_PREFIX='wsl -e bash -lc' -``` - -Computer Use still controls Windows desktop apps. CLI tools running inside WSL do not need to be added to `computer-use-config.json`. If you only need the WSL toolchain and do not need desktop control, disable Computer Use with `--no-computer-use` or the Settings > Computer Use switch. - -## Verify - -After setup, navigate to any project directory and test: - -```bash -cd ~/your-other-project -claude-haha -# Ask "What is the current directory?" — it should show ~/your-other-project -``` diff --git a/docs/en/guide/quick-start.md b/docs/en/guide/quick-start.md deleted file mode 100644 index e092906a..00000000 --- a/docs/en/guide/quick-start.md +++ /dev/null @@ -1,109 +0,0 @@ -# Get Started in 3 Minutes - -Claude Code Haha has two ways to run. Install the **Desktop app** for everyday development. Run the **CLI from source** only when you need a terminal workflow, scripting, or local development. - -| Option | Best for | What you need | -|--------|----------|---------------| -| **Desktop (recommended)** | Managing projects, sessions, worktrees, code diffs, and permission reviews | The installer for your platform; **Bun is not required** | -| **CLI** | Terminal users, `--print` automation, and contributors | Git, Bun, and access to a model provider | - -## Option 1: Install the Desktop App - -### 1. Download the current stable release - -Open [GitHub Releases Latest](https://github.com/NanmiCoder/cc-haha/releases/latest), then select the installer for your operating system and CPU: - -- macOS on Apple Silicon (M-series): `mac-arm64.dmg` -- macOS on Intel: `mac-x64.dmg` -- Windows: `win-x64.exe` or `win-arm64.exe` -- Linux: the matching `.AppImage` or `.deb` - -See the [Desktop installation guide](/en/desktop/04-installation) for platform-specific installation prompts. - -### 2. Configure a model provider - -Launch the app and open **Settings → Providers**. - -The shortest path is to select an official option and follow its on-screen connection or sign-in flow: - -- **Claude Official** -- **ChatGPT Official**: sign in with a ChatGPT account through OAuth -- **Grok Official**: sign in with an xAI account through OAuth - -These official options do not require you to paste an API key. You can also select **Add Provider**, choose a built-in preset or Custom, and enter the API key, base URL, API format, and model mapping. - -Before saving a custom provider, select **Test Connection**. When the test succeeds, make the provider the default. The app can translate OpenAI Chat Completions and OpenAI Responses requests through its built-in local proxy, so LiteLLM is not required by default. - -See [Third-party models and custom providers](./third-party-models.md) for configuration details. - -### 3. Create your first task - -Select `+` in the sidebar: - -1. Choose a local project directory. -2. If it is a Git repository, choose the branch to use. -3. Decide whether to use the current working tree or create an isolated worktree. - -The current working tree shares any uncommitted changes already in that directory. An isolated worktree is better for parallel tasks or changes that should not affect your current checkout. - -### 4. Confirm the model and permissions - -Before sending the first message, confirm the Provider, model, and effort. For a first run, keep **Default** permission mode: the app will ask before sensitive tools or commands run. - -Use automatic approval or bypass modes only when you understand their impact. See [Desktop quick start](/en/desktop/01-quick-start#5-choose-a-permission-mode) for the permission modes. - -### 5. Send the first message - -Start with a small, verifiable request: - -```text -Inspect this project in read-only mode. Explain how to start it and what its main directories do. Do not modify files. -``` - -If you see a streaming response, tool calls, and permission requests, the Desktop app, provider, and project directory are connected. - -## Option 2: Run the CLI from Source - -### 1. Clone the repository and install Bun - -Install [Git](https://git-scm.com/downloads) and [Bun](https://bun.sh), then run: - -```bash -git clone https://github.com/NanmiCoder/cc-haha.git -cd cc-haha -bun install -``` - -### 2. Configure a model provider - -```bash -cp .env.example .env -``` - -Edit `.env` with at least one valid authentication method, base URL, and model. See [Environment variables](./env-vars.md) for the variable definitions and authentication-header differences. - -Never commit a real API key to Git or expose it in an issue, screenshot, or diagnostic attachment. - -### 3. Start and verify - -On macOS, Linux, or Git Bash: - -```bash -./bin/claude-haha -./bin/claude-haha -p "Summarize the current project structure" -``` - -On Windows PowerShell or Command Prompt: - -```powershell -bun --env-file=.env ./src/entrypoints/cli.tsx -``` - -See the [CLI reference](./cli-reference.md) for command options, headless mode, recovery mode, and global usage. - -## Next Steps - -- [Desktop quick start](/en/desktop/01-quick-start): sessions, permissions, attachments, and workspace actions -- [Third-party models and custom providers](./third-party-models.md): providers, API formats, and model mapping -- [FAQ](./faq.md): installation, OAuth, H5, worktree, and Computer Use troubleshooting -- [Global usage](./global-usage.md): start the CLI from any directory diff --git a/docs/en/guide/third-party-models.md b/docs/en/guide/third-party-models.md deleted file mode 100644 index 5bbf5251..00000000 --- a/docs/en/guide/third-party-models.md +++ /dev/null @@ -1,98 +0,0 @@ -# Third-Party Models - -Claude Code Haha can connect to Anthropic Messages, OpenAI Chat Completions, and OpenAI Responses APIs. For Desktop users, the reliable entry point is **Settings → Providers**, not a hand-written environment file. The app stores authentication and model mappings and starts a local protocol proxy when one is required. - -## Recommended setup - -1. Open **Settings → Providers** in Desktop. -2. Choose a built-in preset or create a custom provider. -3. Enter the base URL, authentication details, and protocol format. -4. Configure at least the primary model. Add Haiku, Sonnet, and Opus mappings when the service uses different model IDs. -5. Test the provider before activating it. -6. Start a new session and verify a tool call. A text-only response does not prove that the full agent workflow works. - -After activation, Desktop and the CLI processes it launches reuse the same provider configuration. Do not keep a second, stale key in `.env` or `~/.claude/settings.json`. - -## Choose the correct protocol - -| Provider format | Upstream API | Use it when | -|-----------------|--------------|-------------| -| `anthropic` | Anthropic Messages | The service natively accepts Anthropic requests and responses | -| `openai_chat` | `/v1/chat/completions` | The service implements OpenAI Chat Completions | -| `openai_responses` | `/v1/responses` | The service implements OpenAI Responses | - -The `anthropic` format calls the service directly without changing the protocol. For `openai_chat` and `openai_responses`, Claude Code Haha starts a loopback proxy that translates Claude agent traffic into the selected protocol. This proxy belongs to the local runtime and does not need to be exposed to a LAN. - -If a provider advertises both “OpenAI compatible” and “Anthropic compatible,” choose the API that it implements completely and that handles tool calls reliably. Do not infer the protocol only from a `/v1` path. - -## Authentication and model mappings - -The service determines the authentication header: - -- Anthropic API keys usually use `x-api-key`. -- Bearer tokens usually use `Authorization: Bearer`. -- OpenAI-compatible services usually use bearer tokens, but private gateways can differ. - -Model slots are logical tiers requested by the Claude agent, not additional downloads. The primary model must support tool use. Other slots can map to that same model or to separate models chosen for cost and capability. Leave a slot empty when the provider does not support it. - -Model names and provider capabilities change frequently, so this page intentionally does not maintain a soon-to-be-stale model catalog. Use the current model ID reported by the provider and confirm it with the test action in Settings. - -## Built-in runtimes - -Claude Code Haha also includes Claude, OpenAI, and Grok runtimes. Available sign-in methods depend on the current build and local account state; follow the authorization flow shown on the Providers page. - -Built-in runtimes and custom compatible endpoints are separate paths. Prefer a built-in runtime for an existing official account. Create a custom provider for a relay service, private gateway, or local model server. - -## CLI-only configuration - -CLI-only users can configure an Anthropic Messages-compatible endpoint directly: - -```bash -ANTHROPIC_AUTH_TOKEN=sk-example -ANTHROPIC_BASE_URL=https://provider.example.com/anthropic -ANTHROPIC_MODEL=provider-model -./bin/claude-haha -``` - -This path does not translate Anthropic requests into an OpenAI protocol. Configure OpenAI Chat Completions or Responses services as Desktop providers so the app can manage the protocol proxy. See [Environment Variables](./env-vars.md) for the complete variables and effective precedence. - -Azure OpenAI uses the project's dedicated Responses path. See [Azure OpenAI environment variables](./env-vars.md#azure-openai). - -## LiteLLM: an advanced compatibility layer - -Add LiteLLM only when the service has no reliable Anthropic endpoint and the built-in provider translation is not suitable. It introduces another service, another protocol conversion, and another troubleshooting boundary. - -Minimal example: - -```yaml -model_list: - - model_name: provider-model - litellm_params: - model: openai/provider-model - api_base: https://provider.example.com/v1 - api_key: os.environ/PROVIDER_API_KEY -``` - -After starting LiteLLM, connect its Anthropic-compatible URL as an `anthropic` provider. Follow the [official LiteLLM documentation](https://docs.litellm.ai/) for deployment, authentication, and model prefixes. - -## Capability boundaries - -A third-party model needs at least the following behavior for a stable agent workflow: - -- correct handling of multi-turn messages, system content, and tool calls; -- preservation of tool-call IDs and acceptance of matching tool results; -- sufficient context and output limits; -- complete, correctly ordered events in streaming mode. - -Thinking modes, effort, prompt caching, image input, and structured output depend on the protocol, provider, and exact model. They cannot be labeled universally supported or unsupported. When diagnosing a failure, disable optional capabilities, verify basic text plus tool use, and restore features one at a time. - -## Troubleshooting - -| Symptom | Check first | -|---------|-------------| -| `401` / `403` | Authentication header, token permissions, and whether the base URL belongs to that credential | -| `404` | Provider format and whether the base URL duplicates an API path | -| Model not found | Use the provider's real model ID, not an alias from another platform | -| Text works but tools never run | Confirm that both the model and gateway fully support tool calls | -| Old endpoint after switching providers | Remove duplicate configuration from `.env`, `settings.json`, or Desktop | -| Streaming stops early | Disable optional capabilities and check whether a gateway buffers or rewrites the event stream | diff --git a/docs/en/im/dingtalk.md b/docs/en/im/dingtalk.md index 8bedc903..ad23bef8 100644 --- a/docs/en/im/dingtalk.md +++ b/docs/en/im/dingtalk.md @@ -1,80 +1,85 @@ +--- +title: DingTalk Integration +nav_title: DingTalk +description: Authorize a DingTalk bot by QR code and run over DingTalk Stream, with no public callback URL required. +order: 4 +--- + # DingTalk Integration -The DingTalk adapter uses DingTalk Stream, so it does not require a public callback URL. It supports private chats only. +For organizations already running on DingTalk: one QR scan authorizes a bot, and the adapter connects over DingTalk Stream, so no public callback URL or tunnel is needed. Replies stream through an AI Card by default. Only private chats are supported, and tappable approval cards need one extra template ID — otherwise approval is text replies. -Current behavior includes text, image attachments, project selection, status, stop, AI Card streaming, and text or card-based permission approval. +## Bind the bot by QR code -## Bind a DingTalk bot +1. Open **Settings → IM Adapters** and select the **DingTalk** tab. +2. Under **Bind DingTalk bot with QR**, select **Scan to bind**. +3. Scan with the DingTalk mobile app and confirm bot creation and authorization. +4. Wait for Desktop to report the bound state. -Open **Settings → IM Integration → DingTalk**. - -The recommended flow is: - -1. Select **Scan to bind**. -2. Scan with the DingTalk mobile app. -3. Confirm bot creation and authorization. -4. Wait for Desktop to show the bound state. -5. Save the configuration. - -Desktop stores `clientId` and `clientSecret` in `~/.claude/adapters.json` and restarts the adapter sidecar. +**Client ID** and **Client Secret** are filled in and written to local configuration automatically, and the adapter reconnects with them. ## Manual credentials -If QR binding is unavailable, enter: +If QR binding is unavailable, fill in the fields below the QR area yourself: -- **Client ID** — DingTalk application `appKey` -- **Client Secret** — application `appSecret` +- **Client ID** — the DingTalk application `appKey` +- **Client Secret** — the application `appSecret` - **Stream Endpoint** — optional; defaults to `https://api.dingtalk.com` -- **Permission card template ID** — optional -- **Allowed Users** — optional explicit user IDs +- **Permission Card Template ID** — optional; when set, permission requests prefer an interactive card +- **Allowed Users** — optional; explicit DingTalk user IDs -Saving starts a DingTalk Stream connection. +Select **Save**, and the adapter opens a DingTalk Stream connection with the new credentials. ## Authorize a user -Binding the bot does not authorize every DingTalk account. Generate a six-character pairing code in Desktop and send it to the bot in a private chat. +Binding the bot does not authorize every DingTalk account. -Codes expire after 60 minutes, are one-time use, and are rate limited after repeated failures. Paired users can be removed from Desktop at any time. +1. Back at the top of the page, under **Pairing**, select **Generate Code**. +2. Send the six-character code to the bot in a private chat. +3. Once pairing is confirmed, you can start chatting. -## Projects and commands +Codes are valid for 60 minutes and work once. Paired accounts appear under **Paired Users** and can be revoked at any time; a revoked user needs a fresh code. -If no default project is configured, the adapter returns recent projects. Reply with a number, project name, or absolute path. The mapping is persisted in `~/.claude/adapter-sessions.json`. +## Projects and sessions -Supported commands: +With **Default Project** set, the first accepted message opens a session in that directory. Without it, the bot lists recent projects; reply with a number, project name, or absolute path. -- `/help` or `帮助` -- `/status` or `状态` -- `/projects` or `项目列表` -- `/new` or `新会话` -- `/new ` -- `/clear` or `清空` -- `/stop` or `停止` +Later messages in the same chat reuse that session. `/new` picks another project; `/clear` empties the context while keeping the project binding. -## Permission approval +## Commands -Without a published card template, reply: +DingTalk has no bot menu like Feishu, so the chat box is the only entry point. After pairing, the bot suggests `/help`. -- `/allow ` -- `/always ` -- `/deny ` +- `/help` or `帮助` — show available commands +- `/status` or `状态` — project, branch, model, run state, and task summary +- `/projects` or `项目列表` — list recent projects again +- `/new` or `新会话` — clear the current binding and choose a project +- `/new ` — start directly in that project +- `/clear` or `清空` — clear context, keep the project binding +- `/stop` or `停止` — stop the current generation -With a configured permission-card template, the adapter prefers an interactive card and receives its callback over DingTalk Stream. Text commands remain the fallback. +## Approval and reply behavior -## Replies and attachments +By default a permission request arrives as text. Reply with one of: -- Private messages arrive through `dingtalk-stream`. -- Normal replies use the message `sessionWebhook`. -- AI Card is preferred for streaming output. -- Image attachments are downloaded and passed as inline image input. -- Shared attachment size limits are enforced. +- `/allow ` — allow once +- `/always ` — persist the matching approval +- `/deny ` — deny + +With a published template entered in **Permission Card Template ID**, the adapter prefers an interactive card and receives the button callback over DingTalk Stream. Text commands stay available whenever a card fails to send or cannot be seen. + +Normal replies prefer AI Card streaming. Image attachments are downloaded and passed as inline image input; oversized attachments are reported back in the chat. ## Unbind -Unbinding the bot clears DingTalk credentials, allowlisted and paired users, and the permission-card template ID. Removing a single paired user revokes only that user. +Two levels: + +- **Unbind bot account** clears the DingTalk credentials, the permission card template ID, and this platform's authorization lists. Binding again means scanning or entering credentials again. +- **Unbind** next to a name under **Paired Users** revokes only that person. ## Development -Packaged Desktop starts the sidecar automatically. For source development: +Packaged Desktop starts the sidecar automatically. Run it by hand only when working from source: ```bash cd adapters @@ -91,3 +96,21 @@ export DINGTALK_STREAM_ENDPOINT="https://api.dingtalk.com" export DINGTALK_PERMISSION_CARD_TEMPLATE_ID="..." export ADAPTER_SERVER_URL="ws://127.0.0.1:3456" ``` + +Normal Desktop use needs none of these; QR binding or manual entry writes the local configuration. + +## Troubleshooting + +**Still unauthorized after a successful scan.** That is the expected flow. Scanning only stores bot credentials; the individual user still has to send a pairing code or appear in **Allowed Users**. + +**Scanning fails or waits forever.** Select **Scan to bind** again for a fresh QR code. If it still fails, enter **Client ID** and **Client Secret** manually. + +**The adapter reports missing credentials.** Neither the environment variables nor `dingtalk.clientId` / `dingtalk.clientSecret` in `~/.claude/adapters.json` took effect. Complete QR binding or manual entry in Settings first. + +**No permission card appears.** Confirm the template ID belongs to a published template, that the template supports the callback route the adapter uses, and that the bot has published a new version. Text approval works regardless. + +**No replies.** Confirm Desktop is running, the tab shows the bound state, the sender is paired or allowlisted, and `~/.claude/adapters.json` is writable. When running from source, also confirm `ADAPTER_SERVER_URL` points at the running Desktop server. + +## Source + +`index.ts`, `helpers.ts`, `ai-card.ts`, `permission-card.ts`, and `media.ts` under `adapters/dingtalk/`, plus `pairing.ts`, `session-store.ts`, `ws-bridge.ts`, and `http-client.ts` under `adapters/common/`. diff --git a/docs/en/im/feishu.md b/docs/en/im/feishu.md index 17b29da9..395837a4 100644 --- a/docs/en/im/feishu.md +++ b/docs/en/im/feishu.md @@ -1,59 +1,70 @@ +--- +title: Feishu Integration +nav_title: Feishu +description: Create a Feishu bot from the official template and drive Desktop sessions from a private chat with card-based approval. +order: 1 +--- + # Feishu Integration -The Feishu adapter connects an enterprise custom app to local Desktop sessions. It handles `p2p` private chats only; group chats are not supported. +Best for teams already on Feishu: an official template creates a bot with every required permission preconfigured, and permission requests arrive as interactive cards you can tap. Common commands can be exposed as a bot menu. It handles private (`p2p`) chats only, and changing the bot configuration means publishing a new version in the developer console. -## Create the Feishu app +## Create the bot -Feishu provides a template with the messaging, event, and card capabilities needed by this integration: +Open [Create a Feishu bot](https://open.feishu.cn/page/openclaw?form=multiAgent). This is the official OpenClaw template, with messaging, event subscription, and card callback permissions already granted — no scopes to add by hand. The **Create Feishu bot** button under **Settings → IM Adapters → Feishu** opens the same page. -[Create a Feishu bot from the template](https://open.feishu.cn/page/openclaw?form=multiAgent) +Choose a name, create the app, then keep its **App ID** and **App Secret** for the next step. -Choose a name, create the app, and save its **App ID** and **App Secret**. +## Configure the bot menu -In the [Feishu developer console](https://open.feishu.cn/app?lang=en-US), create and publish a bot version. Optional menu entries can invoke: +This step is optional. With a menu, you can switch projects and start sessions by tapping instead of typing. -- `/projects` -- `/new` -- `/clear` +In the [Feishu developer console](https://open.feishu.cn/app?lang=en-US), open your bot and go to its bot menu configuration. Add three entries, each with a label of your choice and one of these commands: -The app must be published before users can reliably receive the configured bot behavior. +- `/projects` — list recent projects and switch +- `/new` — start a new session +- `/clear` — clear the current context -## Configure Desktop +Save the menu, then publish a new application version. Menu changes take effect only after publishing. -Open **Settings → IM Integration → Feishu**: +## Enter credentials in Desktop -1. Enter the App ID and App Secret. -2. Generate a six-character pairing code. -3. Save the configuration. -4. Send a private message to the bot and provide the code. +1. Open **Settings → IM Adapters** and select the **Feishu** tab. +2. Paste the values into **App ID** and **App Secret**. +3. Leave **Encrypt Key** and **Verification Token** empty; the template bot does not need them. +4. Enable **Streaming Card Mode** if you want long replies to update one card in place. +5. Select **Save**. -Pairing codes expire after 60 minutes, work once, and are rate limited after repeated failures. App credentials do not authorize every organization user. +**Allowed Users** can stay empty. When it is, only paired accounts are accepted, which is usually what you want. + +## Pair your account + +At the top of the page, under **Pairing**, select **Generate Code**. The six-character code is written to local configuration immediately — no separate save. + +Send any message to your new bot in Feishu, then send the code when prompted. Once pairing is confirmed you can talk to Claude Code directly. + +Codes are valid for 60 minutes, work once, and are invalidated when a new one is generated. ## Commands +Alongside the menu buttons, these work in the chat box at any time: + - `/help` or `帮助` - `/status` or `状态` -- `/clear` or `清空` - `/projects` or `项目列表` - `/new` or `新会话` +- `/clear` or `清空` - `/stop` or `停止` -## Permission approval +## Approval and reply behavior -Permission requests are sent as interactive cards. Selecting allow or deny returns the result to the pending Desktop session. +Permission requests arrive as interactive cards. Selecting allow or deny returns the result to the pending Desktop session. -If card actions do not work, confirm that the latest application version is published and includes the required card-action capability. - -## Reply behavior - -- Normal text uses Feishu post messages. -- Permission approval uses cards. -- Streaming output prefers patching the same message. -- Long completed content is split to respect platform limits. +Normal replies use Feishu post messages. Streaming output prefers patching the same message, and long completed text is split to respect platform limits. ## Development -Packaged Desktop starts the sidecar automatically. For source development: +Packaged Desktop starts the sidecar automatically. Run it by hand only when working from source: ```bash cd adapters @@ -69,4 +80,16 @@ export FEISHU_APP_SECRET="xxx" export ADAPTER_SERVER_URL="ws://127.0.0.1:3456" ``` -If messages are missing, confirm that the app is published, the conversation is a private chat, and the sender is paired or explicitly allowlisted. +## Troubleshooting + +**No messages arrive.** Confirm the app is published — menu edits require a new version — and that the conversation is a private chat rather than a group. + +**Card buttons do nothing.** The card action capability usually did not ship with the published version. Publish again from the developer console. + +**Still unauthorized.** Check that the code is within its 60-minute window, that it is the current one, and that the account now appears under **Paired Users** in Desktop. + +**Session not restored after a restart.** Verify that `~/.claude/adapter-sessions.json` is writable and that the session still exists in Desktop. + +## Source + +`adapters/feishu/index.ts`, plus `pairing.ts`, `session-store.ts`, `ws-bridge.ts`, and `http-client.ts` under `adapters/common/`. diff --git a/docs/en/im/index.md b/docs/en/im/index.md index d15a4d75..a998ccdf 100644 --- a/docs/en/im/index.md +++ b/docs/en/im/index.md @@ -1,76 +1,94 @@ +--- +title: IM Integrations +nav_title: Overview +description: Bridge Feishu, Telegram, WeChat, DingTalk, or WhatsApp private chats into the Desktop app and continue the same session from your phone. +order: 0 +--- + # IM Integrations -Claude Code Haha can bridge private messages from WeChat, DingTalk, WhatsApp, Telegram, and Feishu into local Desktop sessions. +A session running in the Desktop app can be reached from a private chat on your phone. Once bound, a message in Feishu, Telegram, WeChat, DingTalk, or WhatsApp drives the Claude Code session on your own machine: start a long task before you leave, then follow the progress, approve permissions, and switch projects from the road. -These integrations do not make the local agent public. A user is accepted only when they are explicitly listed in `allowedUsers` or have completed pairing. +The chat partner is a bot or account you bound yourself. Messages reach your local Desktop app; no intermediate service holds your code. -## Current architecture +![Settings shows pairing management on top and one tab per platform](../../images/app/settings-im.webp) -The supported path is the Desktop adapter architecture: +## What you get -```mermaid -flowchart LR - A["Desktop Settings"] --> B["/api/adapters"] - B --> C["~/.claude/adapters.json"] - C --> D["Platform adapter sidecar"] - D --> E["Pairing and allowlist check"] - E --> F["HTTP session creation"] - F --> G["/ws/:sessionId"] - G --> H["Claude Code session"] -``` +- **The same session, continued.** Messages sent from your phone enter the Claude Code session on your computer, where file edits, commands, and reads really happen. +- **Project switching.** `/projects` lists recent projects and switches to the one you pick; `/new` starts a fresh session. +- **Permission approval.** When Claude wants to write a file or run a risky command, the request is pushed to the chat. Feishu and DingTalk send interactive cards, Telegram sends buttons, WeChat and WhatsApp expect a text reply. +- **Status and stop.** `/status` reports the current project, model, and run state; `/stop` interrupts the current turn. -Platform adapters are separate local processes. They read their platform configuration, enforce authorization, map a private chat to a project session, and bridge messages over the Desktop server. +The Desktop app has to stay running. The chat side is only a remote control. -This is different from the upstream Claude Code Channel/MCP research retained under [Channel System](../channel/). +## Choosing a platform -## Set up an integration +All five expose the same capabilities. They differ in setup cost and approval experience. -1. Keep the Desktop app running. -2. Open **Settings → IM Integration**. -3. Configure or bind one platform. -4. Set a default project if desired. -5. Generate a six-character pairing code. -6. Send the code in a private chat with the bot or bound account. +| Platform | How you connect | Best for | Known limits | +|---|---|---|---| +| Feishu | Create a bot from the official template, paste its App ID and App Secret | Teams that want one-tap permission approval | Private (`p2p`) chats only; menu changes require publishing a new app version | +| Telegram | Ask `@BotFather` for a Bot Token, paste it into Settings | Individuals who can reach Telegram; fastest setup | Private chats only | +| WeChat | Scan a QR code in Settings to log in a bot account | People who only want WeChat | Private chats only; permission approval is text replies | +| DingTalk | Scan a QR code in Settings; credentials are filled in for you | Organizations already on DingTalk | Private chats only; interactive approval cards need an extra template ID | +| WhatsApp | Scan from **Linked devices** on your phone | Users outside mainland China | Personal linked-device login, not the official Cloud API; personal private chats only | -The code expires after 60 minutes, can be used once, and is invalidated when a new code is generated. Repeated failures are rate limited. +If you have no preference, start with Telegram or Feishu — their approval flows are the most comfortable. -## Platforms +## Pairing flow -- [WeChat](./wechat.md) — QR-bound bot account, private chats -- [DingTalk](./dingtalk.md) — DingTalk Stream, private chats -- [WhatsApp](./whatsapp.md) — personal linked-device session through WhatsApp Web -- [Telegram](./telegram.md) — BotFather token, private chats -- [Feishu](./feishu.md) — enterprise custom app, `p2p` chats +Binding happens in two layers: first the Desktop app gets platform credentials, then your personal account is authorized with a pairing code. The second layer is identical everywhere. -## Local state +1. Open **Settings → IM Adapters**. +2. Bind one platform in its tab: Feishu and Telegram take credentials, WeChat, DingTalk, and WhatsApp use a QR code. +3. Pick a directory under **Default Project**. +4. Select **Save**. +5. Back at the top, in **Pairing**, select **Generate Code** to get a six-character code. +6. Send that code to your bot in a private chat on the matching platform. +7. Once pairing is confirmed, anything you type goes to Claude Code. -`~/.claude/adapters.json` stores platform configuration, allowlists, and pairing state. Sensitive fields returned by the settings API are masked. +A code is valid for 60 minutes, works once, and is invalidated the moment a new one is generated. The code itself is platform-neutral — it binds whichever account sends it. Five failed attempts within five minutes trigger rate limiting. -`~/.claude/adapter-sessions.json` stores chat-to-session mappings, including the session ID, working directory, and update time. This allows an adapter to reconnect to an existing session after restart. +Generating a code and QR binding are written to local configuration immediately. **Save** is only needed for typed values such as App ID, Bot Token, **Allowed Users**, and **Default Project**. -Both paths follow `CLAUDE_CONFIG_DIR` when a custom data directory is active. +Paired accounts appear under **Paired Users**, where **Unbind** revokes one of them. A revoked user needs a fresh code. -## Authorization model +## Default project decides where work happens -- `allowedUsers` and `pairedUsers` are combined. -- If both are empty, access is denied. -- Binding a bot or linked account does not authorize its contacts. -- Removing a paired user requires that user to pair again. -- Removing platform credentials stops that platform from connecting. +**Default Project** is the working directory for new IM sessions. With it set, the first message from your phone opens a session in that directory. Left empty, the bot lists recent projects and asks you to choose. + +Later messages in the same chat reuse that session, and the mapping survives a Desktop restart. `/new` changes the directory; `/clear` empties the context while keeping the project binding. ## Common commands -The exact presentation differs by platform, but adapters support the same core operations: +Entry points differ slightly per platform — Feishu can expose commands as a bot menu — but these work everywhere: -- `/help` — show available commands -- `/status` — show the current project, model, and run state -- `/projects` — list or switch recent projects -- `/new` — start a new session or choose another project -- `/clear` — clear current context while keeping the project binding +- `/help` — list available commands +- `/status` — current project, model, and run state +- `/projects` — list recent projects and switch +- `/new` — start a new session, optionally with a project number or path +- `/clear` — clear context, keep the project binding - `/stop` — stop the current generation -Permission requests are returned as platform buttons, cards, or explicit text commands. A request ID must match the pending Desktop session request. +WeChat, DingTalk, and Feishu also accept Chinese aliases such as `帮助`, `状态`, `项目列表`, `新会话`, `清空`, and `停止`. -## Development +## Security -Packaged Desktop starts configured adapter sidecars automatically. Manual `bun run ` commands are for source development and isolated troubleshooting. +::: warning This is a remote control for your computer +A paired account can make Claude read files, write files, and run commands on your machine. Send pairing codes only to yourself, never post one in a group, and never commit bot credentials. +::: + +Authorization is the union of **Allowed Users** and paired users. When both are empty, every sender is rejected. Binding a bot or a linked account does not authorize its contacts. + +Platform credentials, pairing state, and allowlists live in `~/.claude/adapters.json`; chat-to-session mappings live in `~/.claude/adapter-sessions.json`. Both stay on your machine, both contain material that can drive it, and neither should be shared. Sensitive fields are masked when the settings page reads the configuration back. Both paths follow `CLAUDE_CONFIG_DIR` when a custom data directory is active. + +For a full mobile interface rather than a chat window, see [H5 access](../desktop/remote.md). + +## Per-platform guides + +- [Feishu](./feishu.md) — template bot, card approval +- [Telegram](./telegram.md) — BotFather token, button approval +- [WeChat](./wechat.md) — QR-bound account, text approval +- [DingTalk](./dingtalk.md) — QR authorization, AI Card streaming +- [WhatsApp](./whatsapp.md) — personal linked device, text approval diff --git a/docs/en/im/telegram.md b/docs/en/im/telegram.md index 32269d54..f99e53bf 100644 --- a/docs/en/im/telegram.md +++ b/docs/en/im/telegram.md @@ -1,60 +1,59 @@ +--- +title: Telegram Integration +nav_title: Telegram +description: Get a Bot Token from BotFather, paste it into Desktop, and approve permissions with native buttons. +order: 2 +--- + # Telegram Integration -The Telegram adapter connects a BotFather bot to local Desktop sessions. It accepts private chats only; groups are not supported. +The fastest of the five to set up: ask `@BotFather` for a token, paste it into Desktop, done. Permission requests come back as native buttons. It accepts private chats only; groups are not supported. ## Create a bot -In Telegram, open the official **@BotFather** account: +In Telegram, open the official `@BotFather` account and send `/newbot`. Then: -1. Send `/newbot`. -2. Choose a display name. -3. Choose a username ending in `_bot`. -4. Copy the Bot Token returned by BotFather. +1. Choose a display name, for example `ClaudeCodeHaha Bot`. +2. Choose a username in Latin letters ending in `_bot`, for example `jiang_cc_hah_bot`. +3. Copy the **Bot Token** that BotFather returns. -Treat the token as a credential. +That token is the bot's password. Do not paste it anywhere public. -## Configure Desktop +## Enter the token in Desktop -Open **Settings → IM Integration → Telegram**: +1. Open **Settings → IM Adapters** and select the **Telegram** tab. +2. Paste the value into **Bot Token**. +3. Select **Save**. -1. Paste the Bot Token. -2. Generate a six-character pairing code. -3. Save the configuration. -4. Send a message to the new bot and provide the pairing code when prompted. +**Allowed Users** can stay empty. When it is, only paired accounts are accepted. To allowlist a known account directly, enter its numeric Telegram user ID; separate several with commas. -The code expires after 60 minutes, works once, and is rate limited after repeated failures. Bot configuration alone does not authorize all Telegram users. +## Pair your account + +At the top of the page, under **Pairing**, select **Generate Code**. The six-character code takes effect immediately — no separate save. + +Send any message to your new bot, then send the code when prompted. Once pairing is confirmed you can talk to Claude Code directly. + +Codes are valid for 60 minutes, work once, and are invalidated when a new one is generated. Repeated failures are rate limited; wait a few minutes before retrying. ## Commands -- `/start` — show help +- `/start` — show help and available commands - `/help` — show available commands -- `/projects` — list or switch recent projects -- `/status` — show project, model, run state, and task summary -- `/clear` — clear context while keeping the project -- `/new` — start a new session and choose a project +- `/projects` — list recent projects and switch +- `/status` — project, model, run state, and task summary +- `/new` — clear the current binding and choose a project again +- `/clear` — clear context, keep the project binding - `/stop` — stop the current generation -## Permission approval +## Approval and reply behavior -Telegram presents buttons for: +A permission request arrives as a message with three buttons: allow once, always allow the matching operation, and deny. The choice is converted to a `permission_response` for the pending Desktop session. -- allow once; -- always allow the matching operation; -- deny. - -The callback is converted to a `permission_response` for the pending Desktop session. - -## Reply behavior - -The adapter buffers streaming output: - -- a placeholder can be sent during thinking; -- text deltas are accumulated; -- completed text is split into platform-sized messages. +Replies pass through a streaming buffer: a placeholder can be sent while Claude is thinking, text deltas accumulate in place, and completed text is split into platform-sized messages. ## Development -Packaged Desktop starts the adapter automatically. For source development: +Packaged Desktop starts the sidecar automatically. Run it by hand only when working from source: ```bash cd adapters @@ -69,4 +68,16 @@ export TELEGRAM_BOT_TOKEN="123456:ABC-DEF..." export ADAPTER_SERVER_URL="ws://127.0.0.1:3456" ``` -If a sender is rejected, verify that the current pairing code was sent to the correct bot private chat and that the sender is now present in the paired or allowed list. +## Troubleshooting + +**The adapter reports a missing token.** Neither `TELEGRAM_BOT_TOKEN` nor `telegram.botToken` in `~/.claude/adapters.json` took effect. Re-enter the token in Settings and save. + +**Settings opens but the bot does nothing.** When running from source, the web app only writes configuration; it does not launch `bun run telegram`. Packaged Desktop starts the sidecar for you. + +**A sender is rejected.** Confirm a code was generated, that it is within its 60-minute window, and that it was sent to the correct bot in a private chat. + +**Session not restored after a restart.** Verify that `~/.claude/adapter-sessions.json` is writable and that the session still exists in Desktop. + +## Source + +`adapters/telegram/index.ts`, plus `pairing.ts`, `session-store.ts`, `ws-bridge.ts`, `message-buffer.ts`, and `format.ts` under `adapters/common/`. diff --git a/docs/en/im/wechat.md b/docs/en/im/wechat.md index d9896cd0..d623c65b 100644 --- a/docs/en/im/wechat.md +++ b/docs/en/im/wechat.md @@ -1,79 +1,75 @@ +--- +title: WeChat Integration +nav_title: WeChat +description: Scan a QR code in Settings to log in a WeChat bot account, then authorize individual users with a pairing code. +order: 3 +--- + # WeChat Integration -The WeChat adapter binds a bot account by QR code, then authorizes individual private-chat users through pairing or an allowlist. +For people who only want to use WeChat: the whole setup is one QR scan, with no developer platform to register on. The trade-off is that permission approval is text replies rather than tappable buttons, and only private chats are supported. -It supports text, transcribed voice content, image and file attachments, project selection, status, stop, and text-based permission approval. Group chats are not supported. +Besides text, this path also accepts transcribed voice content, images, and file attachments. -## Bind the WeChat bot account +## Bind the bot account -Open **Settings → IM Integration → WeChat**: +1. Open **Settings → IM Adapters** and select the **WeChat** tab. +2. Select **Scan to Bind**. +3. Scan the QR code with WeChat and confirm the login on your phone. +4. Wait for the status to read **WeChat is bound**. -1. Select **Scan to bind**. -2. Scan the QR code with WeChat. -3. Confirm the login in WeChat. -4. Wait for Desktop to show the bound state. -5. Save the configuration. +Credentials are written to local configuration and the adapter restarts on its own; no separate save is needed. The tab then offers **Rescan** and **Unbind WeChat account**. -Desktop stores the returned `accountId`, `botToken`, `baseUrl`, and `userId` in the `wechat` section of `~/.claude/adapters.json`, then restarts the adapter sidecar. - -Binding credentials does not authorize every WeChat user. +Binding the bot account is not the same as allowing every WeChat contact. Who may use it is decided in the next step. ## Authorize a user -Generate a pairing code at the top of **IM Integration**, then send the six-character code to the bound bot in a private chat. +1. Back at the top of the page, under **Pairing**, select **Generate Code**. +2. Send the six-character code to the bound bot in WeChat. +3. Once pairing is confirmed, you can send messages directly. -The code: +Codes are valid for 60 minutes, work once, and are invalidated when a new one is generated. -- expires after 60 minutes; -- can be used once; -- is replaced immediately when a new code is generated; -- is rate limited after repeated failures. - -Known WeChat user IDs can instead be entered in `Allowed Users`. +Known WeChat user IDs can instead be entered in **Allowed Users**, separated by commas. That suits a fixed allowlist but is less convenient than pairing. ## Projects and sessions -With a default project configured, the first accepted message creates or resumes a session in that directory. +With **Default Project** set, the first accepted message opens a session in that directory. Without it, the bot lists recent projects; reply with a number, project name, or absolute path. -Without a default project, the adapter returns recent projects. Reply with a number, project name, or absolute path. The resulting chat-to-session mapping is stored in `~/.claude/adapter-sessions.json`. - -Use `/new` to choose another project and start a new session. +Later messages in the same chat reuse that session, and the mapping survives a Desktop restart. `/new` picks another project; `/clear` empties the context while keeping the project binding. ## Commands -- `/help` or `帮助` -- `/status` or `状态` -- `/projects` or `项目列表` -- `/new` or `新会话` -- `/new ` -- `/clear` or `清空` -- `/stop` or `停止` +WeChat has no configurable menu, so the chat box is the only entry point. After pairing, the bot suggests `/help`. -## Permission approval +- `/help` or `帮助` — show available commands +- `/status` or `状态` — project, branch, model, run state, and task summary +- `/projects` or `项目列表` — list recent projects again +- `/new` or `新会话` — clear the current binding and choose a project +- `/new ` — start directly in that project +- `/clear` or `清空` — clear context, keep the project binding +- `/stop` or `停止` — stop the current generation -Reply to the text approval message with: +## Approval and reply behavior + +A permission request arrives as text containing a request ID. Reply with one of: - `/allow ` — allow once - `/always ` — persist the matching approval - `/deny ` — deny -The adapter sends the response back to the pending Desktop session. - -## Attachments and replies - -- WeChat messages are received through long polling. -- Long text is split into platform-sized messages. -- Images enter model input as inline images. -- Other files are downloaded to a local temporary path for the session. -- Shared attachment size limits are enforced. +Messages are received through long polling and long replies are split into platform-sized messages. A typing indicator is shown while Claude thinks or runs tools. Images enter model input inline; other files are downloaded to a local temporary path. Oversized attachments are reported back in the chat. ## Unbind -Unbinding the WeChat account clears its credentials and platform authorization lists. Removing one paired user only revokes that user. +Two levels: + +- **Unbind WeChat account** clears the bot credentials and this platform's authorization lists. Binding again means scanning again. +- **Unbind** next to a name under **Paired Users** revokes only that person. They need a fresh pairing code afterwards. ## Development -Packaged Desktop starts the sidecar automatically. For source development: +Packaged Desktop starts the sidecar automatically. Run it by hand only when working from source: ```bash cd adapters @@ -91,4 +87,20 @@ export WECHAT_USER_ID="..." export ADAPTER_SERVER_URL="ws://127.0.0.1:3456" ``` -If messages are rejected, confirm that Desktop is running, the account is bound, the sender is paired or allowlisted, and both adapter state files are writable. +Normal Desktop use needs none of these; QR binding writes the local configuration. + +## Troubleshooting + +**Still unauthorized after a successful scan.** That is the expected flow. Scanning only stores bot credentials; the individual user still has to send a pairing code or appear in **Allowed Users**. + +**The QR code expired.** Select **Scan to Bind** again. WeChat login QR codes are short-lived. + +**The adapter reports a missing WeChat account.** Neither the environment variables nor `wechat.accountId` / `wechat.botToken` in `~/.claude/adapters.json` took effect. Complete QR binding in Settings first. + +**No replies.** Confirm Desktop is running, the tab reads **WeChat is bound**, the sender is paired or allowlisted, and `~/.claude/adapters.json` is writable. When running from source, also confirm `ADAPTER_SERVER_URL` points at the running Desktop server. + +**Session not restored after a restart.** Verify that `~/.claude/adapter-sessions.json` is writable and that the session still exists in Desktop. + +## Source + +`index.ts`, `protocol.ts`, and `media.ts` under `adapters/wechat/`, plus `pairing.ts`, `session-store.ts`, `ws-bridge.ts`, and `http-client.ts` under `adapters/common/`. diff --git a/docs/en/im/whatsapp.md b/docs/en/im/whatsapp.md index 9ab31ccc..c4c573e5 100644 --- a/docs/en/im/whatsapp.md +++ b/docs/en/im/whatsapp.md @@ -1,83 +1,80 @@ +--- +title: WhatsApp Integration +nav_title: WhatsApp +description: Scan from Linked devices on your phone to attach the Desktop app as a logged-in WhatsApp Web device. +order: 5 +--- + # WhatsApp Integration -The WhatsApp adapter uses a personal WhatsApp Web linked-device session through `@whiskeysockets/baileys`. It is not the official WhatsApp Business Platform or Cloud API. +For users outside mainland China: no Meta developer account is required, and one QR scan lets your personal WhatsApp number drive the Desktop app. In exchange, this is a WhatsApp Web linked-device login rather than the official Cloud API — no official SLA, template messages, or broadcast capability. It handles personal private chats only, not groups, channels, or status. Permission approval is text replies. -It handles personal private chats only. Groups, channels, and status broadcasts are not supported. +## This is not "creating a bot" -## What linked-device access means +What the integration really does is attach the Desktop app as one more logged-in Web device on your WhatsApp account. So there is no Meta for Developers app, no WhatsApp Business Account, and no Phone Number ID, access token, webhook URL, or message template review. -You do not need a Meta developer app, WABA, Phone Number ID, Cloud API access token, webhook, or message template. +The person on the other side is talking to the WhatsApp account you bound, not to a separately created bot. -Instead: - -1. Desktop creates a WhatsApp Web login QR code. -2. You scan it from **WhatsApp → Linked devices**. -3. Local Baileys auth state is saved. -4. The adapter observes private messages received by that account. -5. Only allowlisted or paired senders can reach a Claude Code session. - -The chat recipient is the WhatsApp account you bound, not a separately created bot. +For customer service, template messages, an official SLA, or broadcasting, you would need the official Cloud API instead, which is a separate implementation. See the [Linked Devices help page](https://faq.whatsapp.com/1317564962315842/) and the [WhatsApp Cloud API overview](https://developers.facebook.com/docs/whatsapp/cloud-api/overview). ## Bind the account -Open **Settings → IM Integration → WhatsApp**: +1. Open **Settings → IM Adapters** and select the **WhatsApp** tab. +2. Select **Scan to Bind**. +3. On your phone, open **WhatsApp → Settings → Linked devices**. +4. Scan the QR code shown in Desktop. +5. Wait for the status to read **WhatsApp is bound**. -1. Select **Scan to bind**. -2. Open **Linked devices** on the phone. -3. Scan the QR code. -4. Wait for Desktop to confirm the binding. - -The default local auth directory is: +The login state is saved locally and the adapter restarts on its own; no separate save is needed. The default directory is: ```text ~/.claude/whatsapp-auth/default ``` -Do not publish or share this directory. +That directory is equivalent to a credential that can send and receive messages as you. Do not share it and do not commit it. ## Authorize a sender -Linked-device login does not authorize all contacts. +Linked-device login attaches the account; it does not authorize your contacts. -Generate a six-character pairing code in Desktop, then send it in a private WhatsApp chat to the bound account. The sender’s JID is added to `whatsapp.pairedUsers`. +1. Back at the top of the page, under **Pairing**, select **Generate Code**. +2. From the account you want to authorize, send the six-character code to the bound account in a private chat. +3. Once pairing is confirmed, that JID is recorded in the local authorization list. -Known JIDs can be entered in `Allowed Users`, for example: +Known JIDs can instead be entered in **Allowed Users** as a country code plus phone number: ```text -@s.whatsapp.net +15551234567@s.whatsapp.net ``` +Codes are valid for 60 minutes, work once, and are invalidated when a new one is generated. + ## Commands -- `/start` or `/help` -- `/projects` -- `/status` -- `/clear` -- `/new [project]` -- `/stop` +- `/start` or `/help` — show help and available commands +- `/projects` — list recent projects and switch +- `/status` — current project, model, and run state +- `/new [project]` — start a new session or switch projects +- `/clear` — clear context, keep the project binding +- `/stop` — stop the current generation -## Permission approval +## Approval and reply behavior -WhatsApp uses explicit text replies: +WhatsApp has no tappable buttons, so a permission request is answered by replying as the message instructs: - `1` or `/allow ` — allow once - `2` or `/always ` — persist the matching approval - `3` or `/deny ` — deny -## Reply behavior - -- Thinking can produce a short status message. -- Completed text is split into platform-sized messages. -- Recognized Markdown image output can be sent as an image message. -- The adapter does not depend on editing one WhatsApp message for token-level streaming. +The adapter does not rely on repeatedly editing one message for token-level streaming: a short status message is sent while Claude thinks, completed text is split into platform-sized messages, and Markdown image output is recognized and sent as an image message. ## Unbind -Use the WhatsApp settings page to unbind, remove local auth state, and scan again. Removing only a paired user revokes that sender without unlinking the account. +**Unbind WhatsApp account** removes the locally stored login state, after which you have to scan again. To revoke a single person instead, select **Unbind** next to their name under **Paired Users**; the account link itself is unaffected. ## Development -Packaged Desktop starts the sidecar automatically. For source development: +Packaged Desktop starts the sidecar automatically. Run it by hand only when working from source: ```bash cd adapters @@ -89,8 +86,18 @@ Optional overrides: ```bash export WHATSAPP_AUTH_DIR="$HOME/.claude/whatsapp-auth/default" -export WHATSAPP_ACCOUNT_JID="@s.whatsapp.net" +export WHATSAPP_ACCOUNT_JID="15551234567@s.whatsapp.net" export ADAPTER_SERVER_URL="ws://127.0.0.1:3456" ``` -If the adapter reports that no account is bound, complete QR binding in Desktop first; the manual adapter command does not provide a separate login UI. +## Troubleshooting + +**The adapter reports no bound account.** Complete QR binding in Desktop first; `bun run whatsapp` has no login UI of its own. + +**Still unauthorized after binding.** Scanning binds the account, not the sender. Generate a pairing code and send it from the chat you want to authorize. + +**WhatsApp reports a logout.** Unbind in Settings and scan again. Removing the device under **Linked devices** on your phone also logs it out. + +## Source + +`index.ts`, `protocol.ts`, `session.ts`, and `media.ts` under `adapters/whatsapp/`, plus `pairing.ts`, `session-store.ts`, and `ws-bridge.ts` under `adapters/common/`. diff --git a/docs/en/agent/03-agent-framework.md b/docs/en/internals/agent-framework.md similarity index 89% rename from docs/en/agent/03-agent-framework.md rename to docs/en/internals/agent-framework.md index fdbe53c1..428160e5 100644 --- a/docs/en/agent/03-agent-framework.md +++ b/docs/en/internals/agent-framework.md @@ -1,31 +1,30 @@ -# Claude Code Agent Framework Deep Dive +--- +title: Agent Framework Deep Dive +nav_title: Agent Framework +description: The core agent loop, prompt engineering, tool system, and context compression, read from source. +order: 6 +--- -> Deconstructing the architecture behind the world's most popular AI code editor — from source code to design philosophy. +# Agent Framework Deep Dive -

-Core Loop · Prompt Engineering · Tool System · Context Management · Skills & Plugins · Permissions · Recovery · vs LangChain · Why It Works -

+Deconstructing the architecture behind the world's most popular AI code editor — from source code to design philosophy. ![Agent Framework Architecture Overview](./images/11-agent-framework-overview.png) ---- +## What This Framework Solves -## Preface: A Fundamental Question - -If you observe Claude Code closely, you'll notice some remarkable behaviors: +Watch Claude Code closely and a few behaviors need explaining: - It can modify dozens of files in a single conversation with extremely few errors - It automatically recovers from edge cases (token overflow, API timeouts, tool failures) - It can simultaneously manage multiple subagents collaborating on complex tasks - Long conversations don't degrade — they actually become more precise over time -Behind these capabilities lies a carefully engineered Agent framework. This document deconstructs that framework from the source code level, revealing its core design philosophy. +None of that comes from the model alone; it is designed into the framework. The rest of this page walks the source in order. ---- +## The Core Agent Loop -## 1. The Core Agent Loop - -### 1.1 Not ReAct — An Async Generator State Machine +### Not ReAct — An Async Generator State Machine Most agent frameworks (including LangChain) adopt the classic **ReAct** pattern: @@ -42,7 +41,7 @@ export async function* query(params: QueryParams): AsyncGenerator<...> This function is the heart of the entire agent. It's not a simple "think-act-observe" loop but a **streaming state machine** that yields messages in real-time and drives iteration through state assignment (not recursive calls). -### 1.2 The State Structure +### The State Structure ```typescript // src/query.ts:204-217 @@ -60,7 +59,7 @@ type State = { } ``` -### 1.3 Five Phases of the Core Loop +### Five Phases of the Core Loop The entire `while (true)` loop (`src/query.ts:307-1728`) consists of five phases: @@ -140,11 +139,9 @@ No recursion, no callback hell — just simple `state = next` followed by `conti - **State traceability**: Every transition reason is recorded - **Controllable recovery**: Errors at any phase can be recovered by modifying state ---- +## System Prompt Engineering -## 2. System Prompt Engineering - -### 2.1 Layered Construction Architecture +### Layered Construction Architecture The system prompt isn't a static string — it's dynamically assembled through a **layered pipeline** (`src/constants/prompts.ts:444-577`): @@ -171,7 +168,7 @@ The **cache boundary (`SYSTEM_PROMPT_DYNAMIC_BOUNDARY`)** is a critical design e This means Claude Code's system prompt **doesn't need to be reprocessed every time** — the static portion is shared globally, dramatically reducing latency and cost. -### 2.2 Two Section Types +### Two Section Types ```typescript // src/constants/systemPromptSections.ts @@ -187,7 +184,7 @@ DANGEROUS_uncachedSystemPromptSection('mcp_instructions', async () => { }, 'MCP servers can connect/disconnect mid-session') ``` -### 2.3 CLAUDE.md Loading Mechanism +### CLAUDE.md Loading Mechanism CLAUDE.md is the custom instruction system, loaded by **priority from low to high** (`src/utils/claudemd.ts`): @@ -205,7 +202,7 @@ project-root/CLAUDE.local.md ← Local private instructions (highest prio Supports `@path` syntax for recursive file inclusion, with automatic circular reference prevention. -### 2.4 System Prompt Priority Resolution +### System Prompt Priority Resolution The final system prompt is determined through `buildEffectiveSystemPrompt()` (`src/utils/systemPrompt.ts:41-123`): @@ -216,11 +213,9 @@ The final system prompt is determined through `buildEffectiveSystemPrompt()` (`s 5. **Default prompt** — Standard system prompt 6. **Append prompt** — Always appended at the end ---- +## Tool System Design -## 3. Tool System Design - -### 3.1 Tools: More Than Function Calls +### Tools: More Than Function Calls Claude Code's tools aren't simple "name + params + execute". Each tool is a **complete lifecycle management unit** (`src/Tool.ts:362-695`): @@ -258,7 +253,7 @@ type Tool = { This design makes every tool **self-describing, self-validating, and self-rendering** — the framework doesn't need to understand tool internals, just call standard interfaces. -### 3.2 Tool Registration: Three-Stage Pipeline +### Tool Registration: Three-Stage Pipeline Tool discovery and registration happens in three stages (`src/tools.ts`): @@ -278,7 +273,7 @@ Stage 3: MCP Merge (assembleToolPool) Sorting (cache stability) ``` -### 3.3 Tool Execution Pipeline +### Tool Execution Pipeline Each tool invocation passes through a **7-step pipeline** (`src/services/tools/toolExecution.ts`): @@ -290,7 +285,7 @@ Each tool invocation passes through a **7-step pipeline** (`src/services/tools/t Each step can **interrupt, modify, or enhance** the execution flow. This isn't a simple `try { tool.call(input) } catch` — it's a full middleware pipeline. -### 3.4 Deferred Tool Loading +### Deferred Tool Loading Claude Code has 48+ built-in tools. Sending all tool definitions to the model on every API call would waste massive tokens. The solution: @@ -305,11 +300,9 @@ Claude Code has 48+ built-in tools. Sending all tool definitions to the model on The model dynamically retrieves full definitions via the `ToolSearch` tool when needed. This dramatically reduces system prompt size. ---- +## Context Management & Compression -## 4. Context Management & Compression - -### 4.1 The Secret Behind Unlimited Conversations +### The Secret Behind Unlimited Conversations Claude Code claims "conversations have no context limit." Behind this is a **four-level compression system**: @@ -331,7 +324,7 @@ Staged summarization of historical messages. Not all-at-once summarization, but When all local optimizations are insufficient, Claude itself generates a complete conversation summary that replaces all historical messages. -### 4.2 System Context Injection +### System Context Injection Before every API call, two types of context are automatically injected (`src/context.ts`): @@ -350,7 +343,7 @@ getUserContext() → { } ``` -### 4.3 System Reminders +### System Reminders System reminders are special **attachment messages** injected into tool results or user messages (`src/utils/attachments.ts`): @@ -366,11 +359,9 @@ Use cases include: - Accompanying information for side questions - Availability notices for deferred tools ---- +## Skills & Plugin Ecosystem -## 5. Skills & Plugin Ecosystem - -### 5.1 Skills System +### Skills System Skills are one of Claude Code's most powerful extension mechanisms. They're not simple "command aliases" but **complete AI behavior definitions**. @@ -411,7 +402,7 @@ Project skills (.claude/skills/) ← Project-level Policy skills (policy) ← Organization-managed ``` -### 5.2 Plugin System +### Plugin System Plugins are higher-level extension units that can contain **skills, hooks, MCP servers, and LSP servers**: @@ -430,7 +421,7 @@ type BuiltinPluginDefinition = { The key plugin design: **users can toggle enable/disable**, unlike directly registered skills. -### 5.3 Hooks System +### Hooks System Hooks are **programmable interception points** across the entire lifecycle: @@ -449,7 +440,7 @@ Hooks execute as shell commands, with exit codes controlling behavior: - **2**: stderr content shown to model or user - **Other**: Shown to user only -### 5.4 MCP: Model Context Protocol +### MCP: Model Context Protocol MCP is the standard protocol for Claude Code's interaction with the external world. Tool naming convention: @@ -462,11 +453,9 @@ Supported transports: `stdio`, `sse`, `http`, `websocket`, `sdk` MCP tools are discovered at runtime and **seamlessly merged** into the unified tool pool alongside built-in tools. ---- +## Permission & Security Model -## 6. Permission & Security Model - -### 6.1 Layered Permission Model +### Layered Permission Model ``` ┌─────────────────────────────────────┐ @@ -486,7 +475,7 @@ MCP tools are discovered at runtime and **seamlessly merged** into the unified t └─────────────────────────────────────┘ ``` -### 6.2 Permission Decision Flow +### Permission Decision Flow Permission check for every tool invocation: @@ -504,7 +493,7 @@ Decision reason traceability: - `type: 'hook'` — Hook interception - `type: 'classifier'` — ML classifier decision -### 6.3 Permission Rule Pattern Matching +### Permission Rule Pattern Matching ```javascript // Exact match @@ -518,9 +507,7 @@ Decision reason traceability: { tool: 'File*', behavior: 'allow' } // Allow all File* tools ``` ---- - -## 7. Fault Recovery Mechanisms +## Fault Recovery Mechanisms This is one of Claude Code's most sophisticated designs. The core loop in `src/query.ts` has **6 built-in recovery strategies**: @@ -545,25 +532,23 @@ if (error.type === 'prompt_too_long') { } ``` -### 7.1 Model Fallback +### Model Fallback When the primary model's stream fails, the system: 1. Cleans up orphaned incomplete messages 2. Switches to a fallback model 3. Retries with the new model -### 7.2 Media Size Recovery +### Media Size Recovery When images or other media cause token overflow: - Triggers reactive compaction - Automatically strips image content - Retains text information and retries ---- +## How It Differs from LangChain/ReAct -## 8. How It Differs from LangChain/ReAct - -### 8.1 Architecture Paradigm Comparison +### Architecture Paradigm Comparison | Dimension | LangChain | Claude Code | |-----------|-----------|-------------| @@ -577,7 +562,7 @@ When images or other media cause token overflow: | **Extension Mechanisms** | Python class inheritance | Skills + Plugins + Hooks + MCP | | **Caching Strategy** | None | Global / session / per-turn three-level cache | -### 8.2 Why Not ReAct? +### Why Not ReAct? The ReAct pattern has several inherent limitations: @@ -593,7 +578,7 @@ Claude Code's Async Generator pattern solves all these problems: - **Cache optimization**: Static prompts cached globally, dynamic parts minimized - **Parallel capability**: Read-only tools auto-parallelize, write tools serialize for ordering -### 8.3 Specific Differences from LangChain Agents +### Specific Differences from LangChain Agents ``` LangChain Agent: @@ -616,7 +601,7 @@ Key differences: - LangChain requires an OutputParser to parse tool calls from model output - Claude Code directly uses Anthropic API's native `tool_use` capability — no parsing needed -### 8.4 Comparison with LangGraph +### Comparison with LangGraph LangGraph is LangChain's evolution, introducing graph structures: @@ -630,13 +615,11 @@ LangGraph is LangChain's evolution, introducing graph structures: Claude Code's advantage is **simplicity** — no need to define graph structures; a single while loop handles everything. ---- - -## 9. Why Claude Code Is So Good +## Why Claude Code Is So Good From source code analysis, we can distill these core design principles: -### 9.1 Streaming First +### Streaming First The entire architecture is designed around `AsyncGenerator` — everything is streamed: - Model responses are streamed @@ -646,7 +629,7 @@ The entire architecture is designed around `AsyncGenerator` — everything is st Users **never have to wait** — they see the model thinking, tools executing, and results emerging. -### 9.2 Intelligent Caching +### Intelligent Caching Three-level prompt caching system (`src/services/api/claude.ts:3213-3237`): @@ -660,7 +643,7 @@ Section Cache (per-turn) ← systemPromptSection memoization This dramatically reduces latency and cost for every API call. -### 9.3 Graceful Degradation +### Graceful Degradation Six recovery strategies ensure Claude Code **almost never interrupts the user's workflow due to technical issues**: - Token overflow? Auto-compress @@ -668,7 +651,7 @@ Six recovery strategies ensure Claude Code **almost never interrupts the user's - Model failure? Fall back to alternate model - Tool failure? Log error, continue conversation -### 9.4 Minimal Abstraction Principle +### Minimal Abstraction Principle Unlike LangChain's "abstract everything" philosophy, Claude Code's core has only: - **One loop** (`while (true)` in `query()`) @@ -677,7 +660,7 @@ Unlike LangChain's "abstract everything" philosophy, Claude Code's core has only No Agent → AgentExecutor → Chain → Memory → Callback nesting layers. This makes the code **easy to understand, debug, and extend**. -### 9.5 Native API Integration +### Native API Integration Claude Code directly leverages Anthropic API's native capabilities: - **Native tool calling**: No OutputParser needed, directly uses `tool_use` blocks @@ -687,7 +670,7 @@ Claude Code directly leverages Anthropic API's native capabilities: This avoids the "framework tax" — the abstraction layer that frameworks like LangChain add between the LLM and the developer. -### 9.6 Tool-Driven Agent +### Tool-Driven Agent Claude Code's philosophy: **an agent's capability equals the capability of its tools**. @@ -698,7 +681,7 @@ Claude Code's philosophy: **an agent's capability equals the capability of its t **All capabilities are exposed through the unified tool interface**, and the model uses natural language reasoning to decide which tool to use. No explicit orchestration logic needed — the model itself is the orchestrator. -### 9.7 Deep Developer Experience Integration +### Deep Developer Experience Integration Claude Code isn't "generic agent + code plugin" — it's **deeply optimized for coding scenarios from the ground up**: @@ -708,11 +691,7 @@ Claude Code isn't "generic agent + code plugin" — it's **deeply optimized for - **LSP integration**: Language Server Protocol provides type information and diagnostics - **MCP ecosystem**: Connects to various external tools via standard protocol ---- - -## 10. Architecture Summary - -### Core Component Relationships +## Core Component Relationships ``` User Input @@ -742,23 +721,17 @@ query() async generator loop (src/query.ts) └─ Teammate (mailbox communication) ``` -### One-Line Summary - -> **Claude Code's agent framework is a streaming state machine powered by AsyncGenerator, exposing all capabilities through a unified tool interface, combined with four-level context compression, three-level prompt caching, and six fault recovery strategies — an AI system that autonomously completes complex programming tasks without explicit orchestration.** - ---- - -## 11. Key Source File Index +## Key Source File Index | Component | File Path | Description | |-----------|-----------|-------------| | Core Loop | `src/query.ts` | Main agent loop (~1730 lines) | -| Query Engine | `src/QueryEngine.ts` | High-level wrapper (~687 lines) | +| Query Engine | `src/QueryEngine.ts` | High-level wrapper | | Tool Definition | `src/Tool.ts` | Tool type system (~792 lines) | | Tool Registry | `src/tools.ts` | Tool discovery and registration (~389 lines) | -| Tool Execution | `src/services/tools/toolExecution.ts` | Execution pipeline (~1500 lines) | +| Tool Execution | `src/services/tools/toolExecution.ts` | Execution pipeline | | Tool Orchestration | `src/services/tools/toolOrchestration.ts` | Parallel/serial strategy | -| System Prompt | `src/constants/prompts.ts` | Prompt assembly (~577 lines) | +| System Prompt | `src/constants/prompts.ts` | Prompt assembly | | Prompt Sections | `src/constants/systemPromptSections.ts` | Section caching | | Context Management | `src/context.ts` | System/user context | | CLAUDE.md | `src/utils/claudemd.ts` | User instruction loading | @@ -780,11 +753,9 @@ query() async generator loop (src/query.ts) | Remote Sessions | `src/remote/RemoteSessionManager.ts` | CCR connection management | | Bridge | `src/bridge/bridgeMain.ts` | Remote bridge | ---- +## Further Reading -## 12. Further Reading - -- [Usage Guide](./01-usage-guide.md) — User-facing multi-agent manual -- [Implementation Details](./02-implementation.md) — Technical deep dive into multi-agent orchestration +- [Usage Guide](./agent.md) — User-facing multi-agent manual +- [Implementation Details](./agent-internals.md) — Technical deep dive into multi-agent orchestration - [Anthropic API Docs](https://docs.anthropic.com/) — Native API capabilities - [MCP Protocol Spec](https://modelcontextprotocol.io/) — Model Context Protocol diff --git a/docs/en/agent/02-implementation.md b/docs/en/internals/agent-internals.md similarity index 95% rename from docs/en/agent/02-implementation.md rename to docs/en/internals/agent-internals.md index b1b94ec5..0570e682 100644 --- a/docs/en/agent/02-implementation.md +++ b/docs/en/internals/agent-internals.md @@ -1,20 +1,21 @@ -# Claude Code Multi-Agent System — Implementation Details +--- +title: Multi-Agent Internals +nav_title: Multi-Agent Internals +description: Spawn paths, tool pool filtering, context passing, and the background task engine. +order: 5 +--- -> A deep dive into the architecture, spawn flow, context passing, and collaboration mechanisms of multi-agent orchestration. +# Multi-Agent Internals -

-Architecture · Spawn Flow · Tool Pool · Context Passing · Teams Internals · Task Engine · DreamTask · Worktree Isolation · Permission Sync · Lifecycle Data Flow · Source Index · Feature Flags -

+A deep dive into the architecture, spawn flow, context passing, and collaboration mechanisms of multi-agent orchestration. ![Implementation Architecture Overview](./images/05-architecture.png) ---- - -## 1. Architecture Overview +## Architecture Overview Claude Code's multi-agent system consists of the following core modules: -### 5 Core Modules +### Core Modules | Module | Responsibility | Key Files | |--------|---------------|-----------| @@ -24,7 +25,7 @@ Claude Code's multi-agent system consists of the following core modules: | **Task System** | State tracking, progress updates, notification queue | `src/tasks/LocalAgentTask/` | | **Swarm Infrastructure** | Team management, mailbox communication, permission sync | `src/utils/swarm/` | -### 5 Agent Categories +### Agent Categories ``` ┌─────────────────────────────────────────────────┐ @@ -50,9 +51,7 @@ Claude Code's multi-agent system consists of the following core modules: └───────────┘ ``` ---- - -## 2. Agent Spawn Flow — Four Paths +## Agent Spawn Flow — Four Paths ### Entry Point: `AgentTool.call()` @@ -200,9 +199,7 @@ runAgent(promptMessages, toolUseContext, options) └─ totalTokens ``` ---- - -## 3. Tool Pool System — Three-Layer Filtering +## Tool Pool System — Three-Layer Filtering ![Tool Pool System](./images/07-tool-pool.png) @@ -271,9 +268,7 @@ All available tools └─ Final tool pool ``` ---- - -## 4. Context Passing Mechanism +## Context Passing Mechanism ![Context Passing](./images/06-context-passing.png) @@ -393,9 +388,7 @@ agentDefinition.model ← Agent definition 'inherit' ← Inherit parent model ``` ---- - -## 5. Agent Teams Internals +## Agent Teams Internals ### TeamFile Structure @@ -516,9 +509,7 @@ SendMessage({ to, message }) └─ sendToUdsSocket(socketPath, message) ``` ---- - -## 6. Background Task Engine +## Background Task Engine ![Background Task Engine](./images/08-background-task.png) @@ -618,9 +609,7 @@ function enqueuePendingNotification(taskId, result) { | Write method | Asynchronous queue writes to prevent memory buildup | | Safety measure | O_NOFOLLOW to prevent symlink attacks | ---- - -## 7. DreamTask — Automatic Memory Consolidation +## DreamTask — Automatic Memory Consolidation DreamTask is a special background agent used for cross-session memory consolidation. @@ -649,9 +638,7 @@ type DreamTaskState = { } ``` ---- - -## 8. Worktree Isolation Implementation +## Worktree Isolation Implementation ### Creation Flow @@ -684,9 +671,7 @@ async function createAgentWorktree(slug) { - If no changes: automatically deletes the worktree (`removeAgentWorktree()`) - On abnormal exit: cleanup is ensured via `registerTeamForSessionCleanup()` ---- - -## 9. Permission Synchronization +## Permission Synchronization ### Team-Level Permissions @@ -728,9 +713,7 @@ Teammate needs permission └─ Handled by Leader's useSwarmPermissionPoller ``` ---- - -## 10. Agent Lifecycle End-to-End Data Flow +## Agent Lifecycle End-to-End Data Flow ``` 1. User triggers Agent Tool @@ -778,9 +761,7 @@ Teammate needs permission └─ Asynchronous: processes after receiving ``` ---- - -## 11. Key Source File Index +## Key Source File Index ### Agent Tool Core @@ -840,9 +821,7 @@ Teammate needs permission |------|---------------| | `src/coordinator/coordinatorMode.ts` | Coordinator mode configuration | ---- - -## 12. Feature Flags +## Feature Flags | Flag | Controls | |------|----------| diff --git a/docs/en/agent/01-usage-guide.md b/docs/en/internals/agent.md similarity index 91% rename from docs/en/agent/01-usage-guide.md rename to docs/en/internals/agent.md index f8c77d52..b5ef34d4 100644 --- a/docs/en/agent/01-usage-guide.md +++ b/docs/en/internals/agent.md @@ -1,16 +1,17 @@ -# Claude Code Multi-Agent System — Usage Guide +--- +title: Multi-Agent Usage Guide +nav_title: Multi-Agent Usage +description: Built-in agents, spawn options, background tasks, Agent Teams, and custom agent definitions. +order: 4 +--- -> Let Claude Code orchestrate multiple specialized agents to handle complex tasks in parallel. +# Multi-Agent Usage Guide -

-Multi-Agent System · Six Built-in Agents · Spawning Agents · Background Tasks · Agent Teams · Custom Agents · Permission Modes · Quick Reference -

+Let Claude Code orchestrate multiple specialized agents to handle complex tasks in parallel. ![Multi-Agent System Overview](./images/01-agent-overview.png) ---- - -## 1. What Is the Multi-Agent System? +## What Is the Multi-Agent System? Claude Code's multi-agent system is an **intelligent task orchestration framework** that enables the primary agent to spawn multiple specialized subagents, each executing different tasks independently, then aggregating results for the user. @@ -23,15 +24,13 @@ Core philosophy: **Break large tasks into specialized subtasks, execute them in | Code review | Single-threaded, file by file | Multiple reviewers in parallel | | Debug a complex bug | Try one hypothesis at a time | Multiple debuggers verify in parallel | ---- - -## 2. Six Built-in Agents +## Six Built-in Agents ![Six Built-in Agents](./images/02-agent-types.png) Claude Code ships with 6 specialized agent types, each with a specific tool pool and intended use case: -### 2.1 general-purpose (General Agent) +### general-purpose (General Agent) **Use case**: Complex multi-step research, code search, tasks requiring full tool access. @@ -47,7 +46,7 @@ Agent({ - **Model**: Inherited from parent - **Characteristics**: The all-rounder — choose this when you are unsure which agent type to use -### 2.2 Explore (Exploration Agent) +### Explore (Exploration Agent) **Use case**: Quickly search files, find code patterns, answer questions about codebase structure. @@ -59,11 +58,11 @@ Agent({ }) ``` -- **Tool pool**: Read-only tools (Glob, Grep, Read, Bash) +- **Tool pool**: Every tool except Agent, ExitPlanMode, Edit, Write, and NotebookEdit. **Bash is in that set** — it cannot change files, but it can run any command - **Model**: Haiku (fast, low cost) - **Characteristics**: Cannot modify files; fast; ideal for research -### 2.3 Plan (Planning Agent) +### Plan (Planning Agent) **Use case**: Design implementation plans, analyze architectural trade-offs, generate step-by-step plans. @@ -75,11 +74,11 @@ Agent({ }) ``` -- **Tool pool**: Read-only tools (same as Explore) +- **Tool pool**: Same denylist as Explore - **Model**: Inherited from parent (requires strong reasoning) - **Characteristics**: Outputs structured plans including key files and dependency analysis -### 2.4 verification (Verification Agent) +### verification (Verification Agent) **Use case**: Independently verify that an implementation is correct, run tests, perform boundary checks. @@ -91,11 +90,11 @@ Agent({ }) ``` -- **Tool pool**: Read-only tools +- **Tool pool**: Same as Explore; its system prompt additionally allows writing throwaway test scripts under tmp - **Model**: Inherited from parent - **Characteristics**: Always runs in the background; outputs PASS/FAIL/PARTIAL verdicts; displayed with a red badge -### 2.5 claude-code-guide (Guide Agent) +### claude-code-guide (Guide Agent) **Use case**: Answer questions about Claude Code, Agent SDK, or the Claude API. @@ -107,11 +106,11 @@ Agent({ }) ``` -- **Tool pool**: Bash, Read, WebFetch, WebSearch +- **Tool pool**: Glob, Grep, Read, WebFetch, WebSearch (internal builds swap in Bash for Glob/Grep) - **Model**: Haiku - **Characteristics**: Focused on documentation queries; uses the dontAsk permission mode -### 2.6 statusline-setup (Status Bar Configuration Agent) +### statusline-setup (Status Bar Configuration Agent) **Use case**: Configure the Claude Code status bar display. @@ -124,15 +123,13 @@ Agent({ | Agent | Access | Tool Pool | Model | Purpose | |-------|--------|-----------|-------|---------| | general-purpose | Read/Write | All | Inherited | General tasks | -| Explore | Read-only | Search + Read | Haiku | Quick exploration | -| Plan | Read-only | Search + Read | Inherited | Architecture planning | -| verification | Read-only | Search + Read | Inherited | Independent verification | +| Explore | No file edits | All tools minus the edit set | Haiku | Quick exploration | +| Plan | No file edits | Same as Explore | Inherited | Architecture planning | +| verification | No project-file edits | Same as Explore | Inherited | Independent verification | | claude-code-guide | Read-only | Search + Web | Haiku | Documentation guide | | statusline-setup | Read/Write | Read + Edit | Sonnet | Status bar config | ---- - -## 3. How to Spawn Agents +## How to Spawn Agents ### Parameters @@ -208,9 +205,7 @@ Agent({ - If changes were made, returns the worktree path and branch name on completion - If no changes were made, cleans up automatically ---- - -## 4. Background Task Management +## Background Task Management ![Agent Spawn Flow](./images/03-spawn-flow.png) @@ -251,9 +246,7 @@ When a background agent finishes, the primary agent receives an XML-formatted no When the `tengu_auto_background_agents` feature flag is enabled, foreground agents that run for more than **120 seconds** are automatically moved to background execution, freeing the primary agent to continue working. ---- - -## 5. Agent Teams — Multi-Agent Collaboration +## Agent Teams — Multi-Agent Collaboration ![Agent Teams Collaboration](./images/04-agent-teams.png) @@ -344,9 +337,7 @@ Agent Teams supports two execution backends: | **tmux** | Runs in a separate tmux pane | When an independent terminal view is needed | | **iTerm2** | Runs in a separate iTerm2 window | For macOS iTerm2 users | ---- - -## 6. Custom Agents +## Custom Agents In addition to built-in agents, you can create your own specialized agents. @@ -447,9 +438,7 @@ When several sources define an Agent with the same name, cc-haha selects the act The desktop app still shows overridden definitions and their sources, but spawning uses the highest-priority active definition. ---- - -## 7. Permission Modes +## Permission Modes Each agent can be configured with a different permission mode: @@ -463,9 +452,7 @@ Each agent can be configured with a different permission mode: | `auto` | AI-driven permission classification (Anthropic internal only) | | `bubble` | Permission prompts bubble up to the parent agent's terminal | ---- - -## 8. Quick Reference +## Quick Reference | Action | Method | |--------|--------| diff --git a/docs/en/memory/03-autodream.md b/docs/en/internals/autodream.md similarity index 86% rename from docs/en/memory/03-autodream.md rename to docs/en/internals/autodream.md index fb983eb4..b4b8c594 100644 --- a/docs/en/memory/03-autodream.md +++ b/docs/en/internals/autodream.md @@ -1,16 +1,17 @@ -# Claude Code Memory System — AutoDream Memory Consolidation +--- +title: AutoDream Memory Consolidation +nav_title: AutoDream +description: The four-phase background pass that reviews recent sessions and prunes memories. +order: 11 +--- -> Claude "dreams" -- silently reviewing recent sessions in the background to consolidate, update, and prune memories, much like the human brain organizes memories during sleep. +# AutoDream Memory Consolidation -

-AutoDream · Trigger Conditions · Consolidation Process · Security · UI · Configuration · Comparison · Source Code -

+Claude "dreams" -- silently reviewing recent sessions in the background to consolidate, update, and prune memories, much like the human brain organizes memories during sleep. ![AutoDream Overview](./images/11-autodream-overview.png) ---- - -## 1. What Is AutoDream? +## What Is AutoDream? AutoDream is Claude Code's **background memory consolidation mechanism**, internally codenamed **"Dream: Memory Consolidation"**. @@ -21,7 +22,7 @@ Core metaphor: | Jotting down notes throughout the day | `extractMemories` -- extracts new memories after each conversation | | Organizing the notebook while sleeping | `autoDream` -- periodically reviews multiple sessions to consolidate all memories | -When you're inactive (default interval: 24 hours with 5 accumulated sessions), Claude silently launches a **"dreaming" sub-agent** (forked subagent) in the background. It reviews all recent session transcripts and consolidates scattered memories into structured, deduplicated, up-to-date persistent knowledge. +When you're inactive (default interval: 24 hours with 5 accumulated sessions), Claude silently launches a **"dreaming" subagent** (forked subagent) in the background. It reviews all recent session transcripts and consolidates scattered memories into structured, deduplicated, up-to-date persistent knowledge. **Key source**: `src/services/autoDream/autoDream.ts` @@ -30,9 +31,7 @@ When you're inactive (default interval: 24 hours with 5 accumulated sessions), C // subagent when time-gate passes AND enough sessions have accumulated. ``` ---- - -## 2. Trigger Conditions +## Trigger Conditions ![AutoDream Trigger Flow](./images/12-autodream-trigger.png) @@ -85,15 +84,13 @@ AutoDream will not trigger in the following situations: - **Remote mode**: `getIsRemoteMode() === true` - **autoMemory not enabled** - **`--bare` / SIMPLE mode** -- **Inside a sub-agent**: Only the main agent triggers it +- **Inside a subagent**: Only the main agent triggers it ---- - -## 3. Four-Phase Consolidation Process +## Four-Phase Consolidation Process ![AutoDream Four Phases](./images/13-autodream-phases.png) -Once all gates pass, AutoDream launches a **forked sub-agent** that operates according to the 4-phase prompt defined in `consolidationPrompt.ts`: +Once all gates pass, AutoDream launches a **forked subagent** that operates according to the 4-phase prompt defined in `consolidationPrompt.ts`: ### Phase 1 -- Orient @@ -132,11 +129,9 @@ Don't exhaustively read transcript files. Only look for content you already susp **Key source**: `src/services/autoDream/consolidationPrompt.ts:10-64` ---- +## Security Restrictions -## 4. Security Restrictions - -The AutoDream sub-agent operates under strict tool permission constraints: +The AutoDream subagent operates under strict tool permission constraints: ### Bash: Read-Only Only @@ -172,9 +167,7 @@ Uses a `.consolidate-lock` file for process-level mutual exclusion: **Key source**: `src/services/autoDream/consolidationLock.ts` ---- - -## 5. UI Presentation +## UI Presentation ### Bottom Status Bar @@ -213,9 +206,7 @@ appendSystemMessage({ - `src/tasks/DreamTask/DreamTask.ts` -- Task state management - `src/components/tasks/DreamDetailDialog.tsx` -- UI component ---- - -## 6. Configuration and Toggles +## Configuration and Toggles ### settings.json @@ -255,9 +246,7 @@ The `tengu_onyx_plover` feature flag can configure: In addition to automatic triggering, users can manually trigger memory consolidation via the `/dream` command. Manual triggers call `recordConsolidation()` to update the lock file timestamp. ---- - -## 7. Relationship with extractMemories +## Relationship with extractMemories | Dimension | extractMemories | autoDream | |-----------|----------------|-----------| @@ -286,9 +275,7 @@ Merge duplicates / Fix stale data / Delete conflicts / Compress index MEMORY.md and topic files refreshed ``` ---- - -## 8. Source Code Navigation +## Source Code Navigation | File | Responsibility | |------|----------------| @@ -303,9 +290,7 @@ MEMORY.md and topic files refreshed | `src/utils/backgroundHousekeeping.ts` | Initialization entry point: `initAutoDream()` | | `src/services/extractMemories/extractMemories.ts` | Shared `createAutoMemCanUseTool` | ---- - -## 9. Analytics Events +## Analytics Events AutoDream records its operational state through the following events: diff --git a/docs/en/channel/01-channel-system.md b/docs/en/internals/channel.md similarity index 89% rename from docs/en/channel/01-channel-system.md rename to docs/en/internals/channel.md index 096fb53b..03676434 100644 --- a/docs/en/channel/01-channel-system.md +++ b/docs/en/internals/channel.md @@ -1,25 +1,17 @@ -# Channel System Architecture +--- +title: Channel System +nav_title: Channel System +description: The message protocol, six-layer access control, and permission relay behind IM remote control. +order: 13 +--- -> A deep dive into how Claude Code enables remote Agent control via IM platforms +# Channel System -

-Concepts · -Architecture · -Protocol · -Access Control · -Permission Relay · -UI · -Plugins · -Security · -CLI · -Feature Flags -

+A deep dive into how Claude Code enables remote Agent control via IM platforms ![Channel System Overview](./images/01-channel-overview.png) ---- - -## 1. What is a Channel +## What is a Channel A Channel is Claude Code's **IM integration system** that allows users to remotely control a running Claude Code Agent through instant messaging platforms such as Telegram, Feishu (Lark), Discord, and Slack. @@ -45,9 +37,7 @@ type ChannelEntry = **Plugin kind**: Verified plugins from a marketplace (e.g., `plugin:telegram@anthropic`) **Server kind**: Directly specified MCP server names (always requires dev bypass) ---- - -## 2. Architecture Overview +## Architecture Overview ![Message Flow](./images/02-message-flow.png) @@ -107,11 +97,9 @@ The Channel system follows a clear bidirectional message path: | **Plugin Integration** | `mcpPluginIntegration.ts` | Plugin scoped naming | | **State** | `bootstrap/state.ts` | Global channel allowlist state | ---- +## Message Protocol -## 3. Message Protocol - -### 3.1 Inbound Notification Schema +### Inbound Notification Schema The notification format Channel Servers push to Claude Code: @@ -128,7 +116,7 @@ const ChannelMessageNotificationSchema = z.object({ }) ``` -### 3.2 XML Wrapping +### XML Wrapping After receiving the notification, the system wraps it in a `` XML tag: @@ -158,7 +146,7 @@ Can you check what's wrong with main.ts? When the model sees this tag, it knows the message came from Telegram user "alice" and will use Telegram's `reply` tool to respond. -### 3.3 Safe Metadata Filtering +### Safe Metadata Filtering Meta keys become XML attribute names. A crafted key like `x="" injected="y` could break out of the attribute structure. The system uses strict regex filtering: @@ -169,7 +157,7 @@ const SAFE_META_KEY = /^[a-zA-Z_][a-zA-Z0-9_]*$/ In practice, channel servers only send safe keys like `chat_id`, `user`, `thread_ts`, `message_id`. -### 3.4 Message Enqueuing +### Message Enqueuing Wrapped messages are pushed into the message queue: @@ -186,9 +174,7 @@ enqueue({ SleepTool polls `hasCommandsInQueue()` every ~1 second, waking the Agent when new messages arrive. ---- - -## 4. Six-Layer Access Control +## Six-Layer Access Control ![Access Control](./images/03-access-control.png) @@ -205,7 +191,7 @@ function gateChannelServer( ): ChannelGateResult // { action: 'register' } | { action: 'skip', kind, reason } ``` -### 4.1 Layer 1: Capability Declaration +### Layer 1: Capability Declaration ```typescript if (!capabilities?.experimental?.['claude/channel']) { @@ -216,7 +202,7 @@ if (!capabilities?.experimental?.['claude/channel']) { The MCP Server must declare `experimental['claude/channel']: {}` during handshake. This is MCP's "presence signal" idiom (similar to `tools: {}`), separating Channel Servers from ordinary MCP Servers. -### 4.2 Layer 2: Runtime Gate +### Layer 2: Runtime Gate ```typescript if (!isChannelsEnabled()) { @@ -227,7 +213,7 @@ if (!isChannelsEnabled()) { `isChannelsEnabled()` checks GrowthBook feature flag `tengu_harbor` (default false, 5-minute refresh). This is the global "emergency brake" — flipping this switch immediately disables all Channels without a release. -### 4.3 Layer 3: OAuth Authentication +### Layer 3: OAuth Authentication ```typescript if (!getClaudeAIOAuthTokens()?.accessToken) { @@ -238,7 +224,7 @@ if (!getClaudeAIOAuthTokens()?.accessToken) { Channels are restricted to OAuth-authenticated users. API key users are blocked because Console doesn't have a `channelsEnabled` admin surface yet. -### 4.4 Layer 4: Organization Policy +### Layer 4: Organization Policy ```typescript const sub = getSubscriptionType() @@ -252,7 +238,7 @@ if (managed && policy?.channelsEnabled !== true) { Teams/Enterprise organizations must explicitly enable `channelsEnabled: true` in managed settings. Default is OFF — even a team org with zero configured policy keys is still considered managed and does not fall through to the unmanaged path. -### 4.5 Layer 5: Session Allowlist +### Layer 5: Session Allowlist ```typescript const entry = findChannelEntry(serverName, getAllowedChannels()) @@ -264,7 +250,7 @@ if (!entry) { The MCP Server must be in the current session's `--channels` parameter list. Even if a trusted server dynamically adds the `claude/channel` capability, it cannot bypass this — the user must explicitly list it at startup. -### 4.6 Layer 6: Marketplace Verification + Allowlist +### Layer 6: Marketplace Verification + Allowlist For plugin-kind channels: @@ -303,19 +289,17 @@ type ChannelGateResult = // kind enum: capability | disabled | auth | policy | session | marketplace | allowlist ``` ---- - -## 5. Permission Relay System +## Permission Relay System ![Permission Relay](./images/04-permission-relay.png) -### 5.1 Why Permission Relay Exists +### Why Permission Relay Exists When Claude Code needs to execute sensitive operations (like running a Bash command), it shows a permission confirmation dialog. But if the user is controlling the Agent remotely via Telegram, they can't see the local terminal dialog. The permission relay system solves this: **forward permission prompts to the IM platform so users can approve or deny operations from their phone**. -### 5.2 Outbound: CC → Channel (Permission Request) +### Outbound: CC → Channel (Permission Request) When the Agent triggers a permission dialog and a Channel has declared `experimental['claude/channel/permission']` capability: @@ -334,7 +318,7 @@ type ChannelPermissionRequestParams = { The Channel Server formats this for its platform (Telegram markdown, Discord embed, etc.) and sends it to the user. -### 5.3 Short Request ID Generation +### Short Request ID Generation The 5-letter identifier design is thoughtfully crafted: @@ -359,7 +343,7 @@ function shortRequestId(toolUseID: string): string { - **Letters only**: Phone users don't need to switch keyboard modes (hex alternates between letters and digits) - **Case insensitive**: Accommodates phone autocorrect -### 5.4 Inbound: Channel → CC (Permission Response) +### Inbound: Channel → CC (Permission Response) Users reply in IM with format: `yes tbxkq` or `no tbxkq` @@ -379,7 +363,7 @@ const ChannelPermissionNotificationSchema = z.object({ **Key design**: The Channel Server is responsible for parsing the user's reply and emitting a structured event — CC never does text regex matching. This means casual conversation text can never accidentally trigger permission approval. -### 5.5 Multi-Source Racing +### Multi-Source Racing Permission responses come from four sources, first to resolve wins: @@ -401,7 +385,7 @@ Permission responses come from four sources, first to resolve wins: `createChannelPermissionCallbacks()` uses a closure to maintain a pending Map (not at module level, not in AppState — function references in state cause serialization issues), constructed once and stored in AppState. -### 5.6 Filtering Permission Relay Clients +### Filtering Permission Relay Clients ```typescript // channelPermissions.ts:177-194 @@ -417,11 +401,9 @@ function filterPermissionRelayClients(clients, isInAllowlist) { **All three conditions required**: connected + in allowlist + declares BOTH capabilities (`claude/channel` AND `claude/channel/permission`). The second capability is explicit opt-in — a relay-only Channel never accidentally becomes a permission approval surface. ---- +## UI Components -## 6. UI Components - -### 6.1 Terminal Message Rendering (UserChannelMessage) +### Terminal Message Rendering (UserChannelMessage) ```typescript // UserChannelMessage.tsx @@ -447,7 +429,7 @@ const TRUNCATE_AT = 60 // Message body truncation length Where `◁` is the Channel arrow symbol (`CHANNEL_ARROW`), showing the server leaf name and optional username. -### 6.2 Status Notice (ChannelsNotice) +### Status Notice (ChannelsNotice) Shows the status of `--channels` entries at startup, reporting blockers: @@ -458,7 +440,7 @@ Shows the status of `--channels` entries at startup, reporting blockers: | `policyBlocked` | Org policy hasn't enabled channels | | `unmatched` | Not matched in `--channels` list | -### 6.3 Developer Confirmation Dialog (DevChannelsDialog) +### Developer Confirmation Dialog (DevChannelsDialog) Warning dialog shown when using `--dangerously-load-development-channels`: @@ -480,11 +462,9 @@ Warning dialog shown when using `--dangerously-load-development-channels`: Selecting "Exit" calls `gracefulShutdownSync(1)` for immediate exit. ---- +## Plugin Channel Architecture -## 7. Plugin Channel Architecture - -### 7.1 Channel Declaration in Plugin Manifest +### Channel Declaration in Plugin Manifest Plugins declare channels via the `channels` array in `plugin.json`: @@ -539,7 +519,7 @@ const PluginManifestChannelsSchema = z.object({ } ``` -### 7.2 Configuration Flow (PluginOptionsFlow) +### Configuration Flow (PluginOptionsFlow) After enabling a plugin, if a Channel has unconfigured `userConfig` fields: @@ -571,7 +551,7 @@ function getUnconfiguredChannels(plugin: LoadedPlugin): UnconfiguredChannel[] { } ``` -### 7.3 Scoped Naming +### Scoped Naming Plugin-provided MCP Servers get a scope prefix to avoid naming conflicts: @@ -601,7 +581,7 @@ function addPluginScopeToServers( `pluginSource` is preserved on the config for later marketplace verification in the Channel Gate. -### 7.4 Effective Allowlist Source +### Effective Allowlist Source ```typescript // channelNotification.ts:127-138 @@ -617,18 +597,16 @@ function getEffectiveChannelAllowlist(sub, orgList) { Organization admins can fully control which Channel plugins are trusted via `allowedChannelPlugins`, independent of the global ledger. ---- +## Security Design -## 8. Security Design - -### 8.1 XML Injection Prevention +### XML Injection Prevention Channel message metadata becomes XML attributes. Two lines of defense: 1. **Key name filtering**: `SAFE_META_KEY = /^[a-zA-Z_][a-zA-Z0-9_]*$/` — only plain identifiers 2. **Value escaping**: `escapeXmlAttr()` applies XML escaping to attribute values -### 8.2 Marketplace Verification +### Marketplace Verification `--channels plugin:slack@anthropic` is merely the user's "intent declaration." The runtime name `plugin:slack:X` could come from `slack@anthropic` or `slack@evil`. The gate verifies they must match: @@ -641,25 +619,23 @@ if (actual !== entry.marketplace) { } ``` -### 8.3 Trust Boundary of Permission Relay +### Trust Boundary of Permission Relay From Kenneth's analysis in code comments (PR discussion #2956440848): > "Would this let Claude self-approve?" Answer: the approving party is the human via the channel, not Claude. But the trust boundary isn't the terminal — it's the allowlist (tengu_harbor_ledger). A compromised channel server CAN fabricate "yes \" without the human seeing the prompt. Accepted risk: a compromised channel already has unlimited conversation-injection turns (social-engineer over time, wait for acceptEdits, etc.); inject-then-self-approve is faster, not more capable. The dialog slows a compromised channel; it doesn't stop one. -### 8.4 skipSlashCommands +### skipSlashCommands Channel messages are enqueued with `skipSlashCommands: true`, ensuring text like `/help` sent by IM users is not interpreted as Claude Code slash commands. -### 8.5 Dev Bypass Granularity +### Dev Bypass Granularity The `dev` flag from `--dangerously-load-development-channels` is **per-entry**, not global. After accepting the dev dialog, only explicitly dev-flagged entries bypass the allowlist — normal `--channels` entries still go through full allowlist verification. ---- +## Command-Line Interface -## 9. Command-Line Interface - -### 9.1 Startup Parameters +### Startup Parameters ```bash # Use approved Channel plugins @@ -673,7 +649,7 @@ claude --channels plugin:telegram@anthropic \ --dangerously-load-development-channels plugin:dev-channel@local ``` -### 9.2 Argument Parsing +### Argument Parsing ```typescript // main.tsx @@ -687,7 +663,7 @@ const parseChannelEntries = (raw: string[], flag: string): ChannelEntry[] => { setAllowedChannels(channelEntries) ``` -### 9.3 Feature Gating +### Feature Gating These CLI options are only available when feature flags are enabled: @@ -701,11 +677,9 @@ if (feature('KAIROS') || feature('KAIROS_CHANNELS')) { `hideHelp()` means these options don't appear in `--help` output — the Channel feature is currently in hidden feature stage. ---- +## Feature Flags and Analytics -## 10. Feature Flags and Analytics - -### 10.1 Feature Flags +### Feature Flags | Flag | Source | Purpose | |------|--------|---------| @@ -714,7 +688,7 @@ if (feature('KAIROS') || feature('KAIROS_CHANNELS')) { | `tengu_harbor_ledger` | GrowthBook runtime | Approved plugin allowlist | | `tengu_harbor_permissions` | GrowthBook runtime | Permission relay feature switch | -### 10.2 Analytics Events +### Analytics Events ```typescript // Channel Gate result @@ -742,33 +716,7 @@ tengu_mcp_channel_flags: { } ``` ---- - -## 11. Summary - -The Channel system is Claude Code's **IM integration framework**, and its design embodies several core principles: - -### 1. Security First - -Six access control layers ensure only approved, user-explicitly-chosen Channels can push messages. From build-time feature flags to runtime allowlists, each layer can independently interrupt the flow. - -### 2. Protocol-Driven - -Channels aren't special code — they're ordinary MCP Servers with an additional notification protocol. This means any language that can implement MCP can write Channel plugins. - -### 3. Loose Coupling - -Channel failures don't block local workflows. Permission relay is a multi-source racing mechanism where any source's response is valid. - -### 4. Progressive Trust - -From global switch → authentication → org policy → session allowlist → marketplace verification → allowlist, trust levels increase progressively, with each step serving a clear security purpose. - -### 5. Plugin-Friendly - -Through declarative Channel configuration in `plugin.json`, automatic user config prompting, and scoped naming, third-party developers can easily build their own Channel plugins. - -### Source File Index +## Source File Index | File | Lines | Responsibility | |------|-------|---------------| diff --git a/docs/en/features/computer-use-architecture.md b/docs/en/internals/computer-use.md similarity index 95% rename from docs/en/features/computer-use-architecture.md rename to docs/en/internals/computer-use.md index 21982eaf..32344906 100644 --- a/docs/en/features/computer-use-architecture.md +++ b/docs/en/internals/computer-use.md @@ -1,6 +1,13 @@ +--- +title: Computer Use Architecture +nav_title: Computer Use +description: Tool registration, coordinate mapping, authorization gates, the Python bridge, and safety limits. +order: 12 +--- + # Computer Use Architecture -> This page describes the Computer Use tools, authorization model, platform executors, and current safety boundaries. For setup, start with the [Computer Use guide](./computer-use.md). +Between the model asking for a click and the mouse actually moving sit four gates: tool definitions, authorization checks, the Python bridge, and a platform helper. This page takes each one apart. For setup and everyday usage, start with the [Computer Use guide](../desktop/computer-use.md). ## Layers diff --git a/docs/en/guide/contributing.md b/docs/en/internals/contributing.md similarity index 81% rename from docs/en/guide/contributing.md rename to docs/en/internals/contributing.md index bf937ef8..829926da 100644 --- a/docs/en/guide/contributing.md +++ b/docs/en/internals/contributing.md @@ -1,4 +1,11 @@ -# Contributing and Local Quality Gates +--- +title: Contributing and Quality Gates +nav_title: Contributing +description: Local setup, impact checks, quality gates, testing requirements, and the PR workflow. +order: 14 +--- + +# Contributing and Quality Gates This guide explains how to install, develop, test, and run the local quality gates before opening a PR. The goal is to help maintainers and contributors answer one question before review: did this change break the core Coding Agent workflow? @@ -236,6 +243,67 @@ Release mode composes PR checks, baseline catalog validation, live baseline case In release mode, live lanes are not allowed to be silently skipped. Missing providers, model quota, or external account access will fail the gate and must be recorded as a release blocker. +## Releases and Auto-Update + +`desktop/package.json` is the single source of the desktop version number. A real release requires the version, the Git tag, and `release-notes/vX.Y.Z.md` to match exactly. + +In-app updates are driven by `electron-updater`, with artifacts hosted on GitHub Releases: + +| Platform | Install / update target | Metadata | +|---|---|---| +| macOS arm64 / x64 | `dmg` for first install, `zip` for Squirrel.Mac updates | `latest-mac.yml` | +| Windows x64 / ARM64 | NSIS `.exe` | `latest.yml` | +| Linux x64 | `.AppImage` for updates, `.deb` for manual install | `latest-linux.yml` | +| Linux arm64 | `.AppImage` for updates, `.deb` for manual install | `latest-linux-arm64.yml` | + +The release workflow generates `latest*.yml` inside each platform matrix job, renames colliding metadata to `latest-.yml`, and finally lets `scripts/release-update-metadata.ts` merge them back into the standard filenames electron-updater expects. Do not change this so each matrix job publishes the GitHub Release directly — the metadata files would overwrite each other. + +### Signing secrets + +macOS signing and notarization depend on these GitHub Actions repository secrets: + +```text +MACOS_CERTIFICATE +MACOS_CERTIFICATE_PASSWORD +APPLE_ID +APPLE_APP_SPECIFIC_PASSWORD +APPLE_TEAM_ID +``` + +`MACOS_CERTIFICATE` is the base64 content of a Developer ID Application `.p12`. The project does not ship a `.pkg`, so no Developer ID Installer certificate is needed. + +Windows signing is optional: + +```text +WINDOWS_CERTIFICATE +WINDOWS_CERTIFICATE_PASSWORD +``` + +Auto-update still works without Windows signing; users may just see a SmartScreen prompt. + +### Pre-release checks + +```bash +bun run scripts/release.ts --dry +bun test scripts/pr/release-workflow.test.ts scripts/release-update-metadata.test.ts scripts/quality-gate/package-smoke/index.test.ts +bun run check:policy +``` + +Confirm `release-notes/v.md` exists before running `bun run scripts/release.ts ` for real. + +### Verify one real update path + +Every release should be verified by upgrading from the previous stable build at least once: + +1. Install the previous stable release from GitHub Releases. +2. Push the tag and let the `Release Desktop` workflow finish green. +3. Open the old build and wait for the startup check, or check for updates manually in settings. +4. Confirm the new version is offered, then install and restart. +5. After restart, confirm the version in About, and that providers, sessions, skills, agents, memories, custom pets, and a custom data directory all still work. +6. Confirm historical attachment context, subagent details, and task state restore correctly; open a pet window and check the overlay and current-session navigation. + +Platforms differ in what matters: on macOS confirm the release job used the signed artifacts and the launch-policy check passed; on Windows confirm `latest.yml`, `.exe`, and `.exe.blockmap` are all in the release assets, and remember that a SmartScreen prompt on an unsigned build does not mean the updater failed; on Linux verify auto-update through the AppImage, since `.deb` ships as a manual installer only. + ## PR Workflow 1. Create a product branch such as `fix/session-reconnect` or `feat/provider-quality-gate`. diff --git a/docs/en/internals/desktop.md b/docs/en/internals/desktop.md new file mode 100644 index 00000000..844f9a2c --- /dev/null +++ b/docs/en/internals/desktop.md @@ -0,0 +1,205 @@ +--- +title: Desktop Architecture +nav_title: Desktop +description: Process boundaries between the Electron main process, server sidecar, CLI subprocesses, and IM adapters. +order: 1 +--- + +# Desktop Architecture + +The desktop app does not embed the CLI inside React. It is four kinds of processes cooperating, and most maintenance mistakes come from crossing a boundary that was meant to stay closed. + +## Process map + +```text +Electron main +├── Chromium renderer (React UI) +├── claude-sidecar server +│ ├── Bun.serve HTTP API +│ ├── Bun WebSocket session gateway +│ └── CLI subprocess per session +└── claude-sidecar adapters (launched per platform) + ├── Telegram + ├── Feishu + ├── WeChat + ├── DingTalk + └── WhatsApp +``` + +- **Electron main** owns windows, native dialogs, updates, terminals, native preview, pet windows, and the sidecar lifecycle. +- **Renderer** owns the interface only. It reaches native capabilities through `window.desktopHost`, exposed by preload. +- **Server sidecar** is the local service shared by the desktop app and the H5 client: REST, WebSocket, provider proxy, and session management. +- **CLI subprocess** executes model requests, tool calls, and agent orchestration. +- **Adapter sidecar** bridges IM platform messages into the same server and CLI sessions. + +`desktop/src-tauri/` now only holds packaging assets and historical code. It is not the desktop runtime; Electron is the current host. + +## Current stack + +| Layer | Main technologies | Purpose | +|---|---|---| +| Renderer | React 18, Vite 8, TypeScript, Zustand 5 | Desktop UI and state | +| Styling and content | Tailwind CSS 4, Marked, DOMPurify, Shiki 4, Mermaid, KaTeX | Layout, Markdown, code, diagrams | +| Diff | `react-diff-viewer-continued` | Diffs in chat and workspace | +| Electron host | Electron, electron-builder, electron-updater | Native windows, packaging, updates | +| Terminal | node-pty, xterm.js | Native PTY and terminal rendering | +| Local service | Bun, `Bun.serve` | HTTP API and WebSocket | + +Versions follow `desktop/package.json`. This page lists only the majors that change how the architecture reads, not the full dependency list. + +## Electron host + +| Path | Responsibility | +|---|---| +| `desktop/electron/main.ts` | Electron main entry, windows, IPC registration | +| `desktop/electron/preload.ts` | Typed host API exposed to the main renderer | +| `desktop/electron/preview-preload.ts` | Isolated bridge for native web preview | +| `desktop/electron/pet-preload.ts` | Minimal bridge for pet windows | +| `desktop/electron/ipc/` | IPC channel registration and payload validation | +| `desktop/electron/services/` | Sidecar, update, terminal, preview, window, and proxy services | + +The renderer must not import Electron directly, and must not assemble arbitrary IPC channel names. A new native capability means updating the host contract, the main-side validation, and the related tests together. + +### Startup sequence + +1. Electron resolves the default or portable storage mode and prepares the app config directory. +2. The host picks a free port and starts the unified `claude-sidecar server` binary. +3. The sidecar enters `startServer()` from `src/server/index.ts` and serves HTTP and WebSocket from one `Bun.serve`. +4. Once the health check passes, the renderer takes the loopback server URL and loads sessions and settings. +5. The host starts adapter sidecars for the platforms that are configured. +6. The server starts a CLI subprocess on demand when a session begins. + +The server can bind a LAN-reachable address for H5, but the desktop renderer always talks to the loopback control address. H5, remote access, and pet windows each pass their own token and capability limits. A running local server does not make every caller trusted. + +### Sidecar entry point + +`desktop/sidecars/claude-sidecar.ts` is the single entry point: + +```text +claude-sidecar server --app-root --host --port +claude-sidecar cli --app-root [CLI arguments] +claude-sidecar adapters --app-root --telegram|--feishu|--wechat|--dingtalk|--whatsapp +``` + +The sidecar sets `CLAUDE_APP_ROOT`, `CALLER_DIR`, and the launch arguments before importing any business module, because the top-level modules of the server, CLI, and adapters read those values at import time. + +## Server sidecar + +The real server entry is `src/server/index.ts`, not a separate `server.ts` wrapper. + +```text +src/server/ +├── index.ts # Bun.serve, auth, CORS, upgrades, static H5 +├── router.ts # REST resource routes +├── api/ # API boundary +├── services/ # Sessions, providers, indexing, diagnostics +├── ws/ # WebSocket protocol and session lifecycle +├── proxy/ # Provider protocol and streaming translation +├── middleware/ # Auth, CORS, error boundary +└── config/ # Provider presets +``` + +A single `Bun.serve` `fetch` boundary handles: + +- `/api/*` REST requests +- `/ws/:sessionId` for desktop, H5, and pet clients +- `/sdk/:sessionId` for internal CLI connections +- OAuth callbacks +- Restricted preview and local file access +- Bundled H5 static assets + +Auth rules differ by client capability. Before adding a route, decide whether it belongs to the local desktop, H5, pets, the internal SDK, or public static assets, then place it inside the matching auth and CORS boundary. + +## WebSocket semantics + +The renderer keeps one connection per session: + +```text +ws:///ws/ +``` + +If the server has auth enabled, the client passes the token as a connection query parameter. After the connection opens, the server sends the current session identity, a snapshot of pending permission requests, and the running state. + +### Heartbeat and reconnect + +Current behavior in `desktop/src/api/websocket.ts`: + +- Send `ping` every 30 seconds after connecting. +- If no `pong` arrives within 10 seconds, the client closes the connection and reconnects. +- Reconnect delay grows exponentially from 1 second, capped at 30 seconds. +- Automatic reconnect has no fixed retry ceiling; only closing the session stops it. +- Messages sent while offline are queued in memory. +- After reconnecting, queued messages are flushed first, then `sync_state` fetches the server's authoritative run state. + +If you have read "at most 10 retries" somewhere, that wording is stale — the list above is current. + +### A disconnected client does not stop the work + +After the last client disconnects: + +- Foreground turns and background tasks keep running. +- The idle grace period starts only after the work finishes. +- A client reconnecting inside the grace period cancels the cleanup. +- The server stops the CLI only once the grace period passes with no clients. +- Sessions waiting on a permission request have their own bounded cleanup policy so they cannot hold a process forever. + +That is why locking a phone, refreshing the renderer, or a brief network switch does not interrupt a running task. + +## CLI and provider proxy + +The server starts a CLI per session and forwards output, permission requests, tool results, and background task state over an internal protocol. Provider, model, effort, and permission mode are maintained jointly by the server and the CLI; the renderer is not the single source of truth. + +`src/server/proxy/` handles the supported provider protocols: + +- Anthropic Messages +- OpenAI Chat Completions +- OpenAI Responses + +Model mapping, authentication style, and context settings follow the actual provider configuration. Do not hardcode a vendor list in architecture docs. + +## IM adapters + +Each platform runs its own adapter sidecar, so one platform's bad credentials or failed startup cannot take the others down. + +```text +IM platform + → adapters/ + → adapters/common WebSocket bridge + → server sidecar + → CLI session +``` + +The shared layer in `adapters/common/` handles configuration, pairing, session mapping, message buffering, deduplication, attachments, and the server WebSocket bridge. Current platform directories: + +- `adapters/telegram/` +- `adapters/feishu/` +- `adapters/wechat/` +- `adapters/dingtalk/` +- `adapters/whatsapp/` + +## Persistence boundaries + +Different data does not share one store: + +| Data | Primary boundary | +|---|---| +| Renderer UI preferences and open tabs | Browser storage and its migrations | +| Sessions and messages | Local session data managed by the server and CLI | +| Provider, H5, Computer Use, and other settings | Config files managed by the server | +| Electron window and native state | Electron user data / app mode directory | +| IM configuration and pairing state | Adapter config and per-platform state directories | + +Any change to a JSON shape, a `localStorage` key, or the app config layout needs a forward migration plus a regression test against old data. Never overwrite the user's shared Claude configuration, and never read the real user directory from tests. + +## Build and verification + +Desktop builds are orchestrated by scripts in `desktop/package.json`: + +```bash +cd desktop +bun run build:sidecars +bun run build +bun run build:electron +``` + +`electron:package` runs electron-builder on top of that. Verifying a packaged artifact only proves the asset and installer structure is correct. User flows that touch windows, permissions, terminals, preview, updates, or Computer Use still need a real desktop smoke test. diff --git a/docs/agent/images/01-agent-overview.png b/docs/en/internals/images/01-agent-overview.png similarity index 100% rename from docs/agent/images/01-agent-overview.png rename to docs/en/internals/images/01-agent-overview.png diff --git a/docs/en/channel/images/01-channel-overview.png b/docs/en/internals/images/01-channel-overview.png similarity index 100% rename from docs/en/channel/images/01-channel-overview.png rename to docs/en/internals/images/01-channel-overview.png diff --git a/docs/en/features/images/01-computer-use-architecture.jpg b/docs/en/internals/images/01-computer-use-architecture.jpg similarity index 100% rename from docs/en/features/images/01-computer-use-architecture.jpg rename to docs/en/internals/images/01-computer-use-architecture.jpg diff --git a/docs/en/memory/images/01-memory-overview.png b/docs/en/internals/images/01-memory-overview.png similarity index 100% rename from docs/en/memory/images/01-memory-overview.png rename to docs/en/internals/images/01-memory-overview.png diff --git a/docs/en/skills/images/01-skills-overview.png b/docs/en/internals/images/01-skills-overview.png similarity index 100% rename from docs/en/skills/images/01-skills-overview.png rename to docs/en/internals/images/01-skills-overview.png diff --git a/docs/agent/images/02-agent-types.png b/docs/en/internals/images/02-agent-types.png similarity index 100% rename from docs/agent/images/02-agent-types.png rename to docs/en/internals/images/02-agent-types.png diff --git a/docs/en/features/images/02-computer-use-security-gates.jpg b/docs/en/internals/images/02-computer-use-security-gates.jpg similarity index 100% rename from docs/en/features/images/02-computer-use-security-gates.jpg rename to docs/en/internals/images/02-computer-use-security-gates.jpg diff --git a/docs/en/memory/images/02-memory-types.png b/docs/en/internals/images/02-memory-types.png similarity index 100% rename from docs/en/memory/images/02-memory-types.png rename to docs/en/internals/images/02-memory-types.png diff --git a/docs/en/channel/images/02-message-flow.png b/docs/en/internals/images/02-message-flow.png similarity index 100% rename from docs/en/channel/images/02-message-flow.png rename to docs/en/internals/images/02-message-flow.png diff --git a/docs/en/skills/images/02-skill-sources.png b/docs/en/internals/images/02-skill-sources.png similarity index 100% rename from docs/en/skills/images/02-skill-sources.png rename to docs/en/internals/images/02-skill-sources.png diff --git a/docs/en/channel/images/03-access-control.png b/docs/en/internals/images/03-access-control.png similarity index 100% rename from docs/en/channel/images/03-access-control.png rename to docs/en/internals/images/03-access-control.png diff --git a/docs/en/features/images/03-computer-use-python-bridge.jpg b/docs/en/internals/images/03-computer-use-python-bridge.jpg similarity index 100% rename from docs/en/features/images/03-computer-use-python-bridge.jpg rename to docs/en/internals/images/03-computer-use-python-bridge.jpg diff --git a/docs/en/memory/images/03-memory-trigger.png b/docs/en/internals/images/03-memory-trigger.png similarity index 100% rename from docs/en/memory/images/03-memory-trigger.png rename to docs/en/internals/images/03-memory-trigger.png diff --git a/docs/en/skills/images/03-skill-invocation.png b/docs/en/internals/images/03-skill-invocation.png similarity index 100% rename from docs/en/skills/images/03-skill-invocation.png rename to docs/en/internals/images/03-skill-invocation.png diff --git a/docs/agent/images/03-spawn-flow.png b/docs/en/internals/images/03-spawn-flow.png similarity index 100% rename from docs/agent/images/03-spawn-flow.png rename to docs/en/internals/images/03-spawn-flow.png diff --git a/docs/agent/images/04-agent-teams.png b/docs/en/internals/images/04-agent-teams.png similarity index 100% rename from docs/agent/images/04-agent-teams.png rename to docs/en/internals/images/04-agent-teams.png diff --git a/docs/en/features/images/04-computer-use-patch.jpg b/docs/en/internals/images/04-computer-use-patch.jpg similarity index 100% rename from docs/en/features/images/04-computer-use-patch.jpg rename to docs/en/internals/images/04-computer-use-patch.jpg diff --git a/docs/en/memory/images/04-memory-lifecycle.png b/docs/en/internals/images/04-memory-lifecycle.png similarity index 100% rename from docs/en/memory/images/04-memory-lifecycle.png rename to docs/en/internals/images/04-memory-lifecycle.png diff --git a/docs/en/channel/images/04-permission-relay.png b/docs/en/internals/images/04-permission-relay.png similarity index 100% rename from docs/en/channel/images/04-permission-relay.png rename to docs/en/internals/images/04-permission-relay.png diff --git a/docs/en/skills/images/04-skills-architecture.png b/docs/en/internals/images/04-skills-architecture.png similarity index 100% rename from docs/en/skills/images/04-skills-architecture.png rename to docs/en/internals/images/04-skills-architecture.png diff --git a/docs/en/memory/images/05-architecture-overview.png b/docs/en/internals/images/05-architecture-overview.png similarity index 100% rename from docs/en/memory/images/05-architecture-overview.png rename to docs/en/internals/images/05-architecture-overview.png diff --git a/docs/agent/images/05-architecture.png b/docs/en/internals/images/05-architecture.png similarity index 100% rename from docs/agent/images/05-architecture.png rename to docs/en/internals/images/05-architecture.png diff --git a/docs/en/skills/images/05-skill-loading.png b/docs/en/internals/images/05-skill-loading.png similarity index 100% rename from docs/en/skills/images/05-skill-loading.png rename to docs/en/internals/images/05-skill-loading.png diff --git a/docs/agent/images/06-context-passing.png b/docs/en/internals/images/06-context-passing.png similarity index 100% rename from docs/agent/images/06-context-passing.png rename to docs/en/internals/images/06-context-passing.png diff --git a/docs/en/memory/images/06-path-resolution.png b/docs/en/internals/images/06-path-resolution.png similarity index 100% rename from docs/en/memory/images/06-path-resolution.png rename to docs/en/internals/images/06-path-resolution.png diff --git a/docs/en/skills/images/06-skill-injection.png b/docs/en/internals/images/06-skill-injection.png similarity index 100% rename from docs/en/skills/images/06-skill-injection.png rename to docs/en/internals/images/06-skill-injection.png diff --git a/docs/en/memory/images/07-prompt-injection.png b/docs/en/internals/images/07-prompt-injection.png similarity index 100% rename from docs/en/memory/images/07-prompt-injection.png rename to docs/en/internals/images/07-prompt-injection.png diff --git a/docs/en/skills/images/07-skill-execution.png b/docs/en/internals/images/07-skill-execution.png similarity index 100% rename from docs/en/skills/images/07-skill-execution.png rename to docs/en/internals/images/07-skill-execution.png diff --git a/docs/agent/images/07-tool-pool.png b/docs/en/internals/images/07-tool-pool.png similarity index 100% rename from docs/agent/images/07-tool-pool.png rename to docs/en/internals/images/07-tool-pool.png diff --git a/docs/en/memory/images/08-auto-extraction.png b/docs/en/internals/images/08-auto-extraction.png similarity index 100% rename from docs/en/memory/images/08-auto-extraction.png rename to docs/en/internals/images/08-auto-extraction.png diff --git a/docs/agent/images/08-background-task.png b/docs/en/internals/images/08-background-task.png similarity index 100% rename from docs/agent/images/08-background-task.png rename to docs/en/internals/images/08-background-task.png diff --git a/docs/en/skills/images/08-conditional-activation.png b/docs/en/internals/images/08-conditional-activation.png similarity index 100% rename from docs/en/skills/images/08-conditional-activation.png rename to docs/en/internals/images/08-conditional-activation.png diff --git a/docs/en/memory/images/09-memory-retrieval.png b/docs/en/internals/images/09-memory-retrieval.png similarity index 100% rename from docs/en/memory/images/09-memory-retrieval.png rename to docs/en/internals/images/09-memory-retrieval.png diff --git a/docs/en/skills/images/09-skill-lifecycle.png b/docs/en/internals/images/09-skill-lifecycle.png similarity index 100% rename from docs/en/skills/images/09-skill-lifecycle.png rename to docs/en/internals/images/09-skill-lifecycle.png diff --git a/docs/agent/images/09-teams-mailbox.png b/docs/en/internals/images/09-teams-mailbox.png similarity index 100% rename from docs/agent/images/09-teams-mailbox.png rename to docs/en/internals/images/09-teams-mailbox.png diff --git a/docs/en/memory/images/10-agent-memory.png b/docs/en/internals/images/10-agent-memory.png similarity index 100% rename from docs/en/memory/images/10-agent-memory.png rename to docs/en/internals/images/10-agent-memory.png diff --git a/docs/agent/images/10-fork-cache.png b/docs/en/internals/images/10-fork-cache.png similarity index 100% rename from docs/agent/images/10-fork-cache.png rename to docs/en/internals/images/10-fork-cache.png diff --git a/docs/en/agent/images/11-agent-framework-overview.png b/docs/en/internals/images/11-agent-framework-overview.png similarity index 100% rename from docs/en/agent/images/11-agent-framework-overview.png rename to docs/en/internals/images/11-agent-framework-overview.png diff --git a/docs/en/memory/images/11-autodream-overview.png b/docs/en/internals/images/11-autodream-overview.png similarity index 100% rename from docs/en/memory/images/11-autodream-overview.png rename to docs/en/internals/images/11-autodream-overview.png diff --git a/docs/en/agent/images/12-agent-core-loop.png b/docs/en/internals/images/12-agent-core-loop.png similarity index 100% rename from docs/en/agent/images/12-agent-core-loop.png rename to docs/en/internals/images/12-agent-core-loop.png diff --git a/docs/en/memory/images/12-autodream-trigger.png b/docs/en/internals/images/12-autodream-trigger.png similarity index 100% rename from docs/en/memory/images/12-autodream-trigger.png rename to docs/en/internals/images/12-autodream-trigger.png diff --git a/docs/en/memory/images/13-autodream-phases.png b/docs/en/internals/images/13-autodream-phases.png similarity index 100% rename from docs/en/memory/images/13-autodream-phases.png rename to docs/en/internals/images/13-autodream-phases.png diff --git a/docs/en/agent/images/13-system-prompt-pipeline.png b/docs/en/internals/images/13-system-prompt-pipeline.png similarity index 100% rename from docs/en/agent/images/13-system-prompt-pipeline.png rename to docs/en/internals/images/13-system-prompt-pipeline.png diff --git a/docs/en/agent/images/14-context-compression.png b/docs/en/internals/images/14-context-compression.png similarity index 100% rename from docs/en/agent/images/14-context-compression.png rename to docs/en/internals/images/14-context-compression.png diff --git a/docs/en/internals/index.md b/docs/en/internals/index.md new file mode 100644 index 00000000..1c619e2f --- /dev/null +++ b/docs/en/internals/index.md @@ -0,0 +1,64 @@ +--- +title: Architecture Overview +nav_title: Architecture +description: How the five layers divide the work, how one message flows through them, and which page to read for the change you want to make. +order: 0 +--- + +# Architecture Overview + +Claude Code Haha looks like one desktop app, but it is five pieces of code that each run on their own. Before reading source or opening a PR, get the boundaries straight — every other page in this section lives inside one of them. + +```text +desktop/src/ Frontend — React + Zustand. Draws the UI, touches no native capability. +desktop/electron/ Desktop shell — Electron main process: windows, updates, terminals, native preview. +src/server/ Local server — REST + WebSocket on Bun.serve, shared by desktop and phone. +src/ CLI core — agent loop, tool system, permissions, memory, skills. +adapters/ IM bridges — one sidecar per platform, all wired back to the same sessions. +``` + +Three facts are worth holding onto: + +- **The CLI core is the only thing that executes.** Every desktop session makes the server spawn a CLI subprocess; every button in the frontend eventually becomes a message sent to it. +- **The local server is the only entry point.** Desktop, mobile H5, and IM adapters share one REST and WebSocket surface — they differ only in how much they are trusted. +- **Electron is the current desktop path.** `desktop/src-tauri/` keeps packaging assets and historical code as a rollback option; it is not the runtime. + +## How the CLI core is layered + +![The layered structure of the CLI core](../../images/01-overall-architecture.png) + +After bootstrap, the entry layer splits in two. On the left is the path that carries one request to completion: terminal UI, query engine, tool system, subagents. On the right are the cross-cutting capabilities that path calls into: state management, skills and plugins, and the service layer holding MCP, OAuth, and memory. The desktop app replaces the terminal UI box and reuses everything else as-is. + +## What happens to one message + +![The lifecycle of a single request](../../images/02-request-lifecycle.png) + +Input is parsed, context is assembled, and the request goes to the model. The moment a tool call appears in the stream, it passes a permission check before it runs; the result is folded back into the context and the next turn starts, until the model stops asking for tools. The tool cards and permission prompts you see in the desktop app are two nodes of this path, rendered. + +## How tools and permissions relate + +![The tool system and its permission gate](../../images/03-tool-system.png) + +Every tool registers in one registry, grouped by capability: files, shell, system, subagents, external integrations, and communication. The fixed pipeline underneath is what actually defines the safety boundary — any call passes argument validation and the permission gate before it reaches the sandbox. Adding a tool to the registry is easy; deciding which side of that gate it belongs on is the real work. + +## I want to change X — where do I start + +| What you want to touch | Start here | +|---|---| +| Windows, tray, auto-update, embedded terminal, native preview | [Desktop architecture](./desktop.md) | +| REST endpoints, WebSocket events, auth, provider proxy | [Local server and API](./server.md) | +| You cannot find which directory a feature lives in | [Project structure](./structure.md) | +| Subagent behavior, built-in agents, Agent Teams | [Multi-agent usage guide](./agent.md) | +| Spawn paths, tool pool filtering, the background task engine | [Multi-agent internals](./agent-internals.md) | +| The agent loop, system prompts, context compression | [Agent framework deep dive](./agent-framework.md) | +| Writing skills, source precedence, activation | [Skills usage guide](./skills.md) | +| Skill discovery, injection, fork execution | [Skills internals](./skills-internals.md) | +| Where memories live and when they are written | [Memory system usage guide](./memory.md) | +| Memory injection, extraction, retrieval, team sync | [Memory system internals](./memory-internals.md) | +| Background memory consolidation | [AutoDream memory consolidation](./autodream.md) | +| Screen control tools, authorization, coordinate mapping | [Computer Use architecture](./computer-use.md) | +| IM message protocol, access control, permission relay | [Channel system](./channel.md) | +| What to run before a PR, and the release process | [Contributing and quality gates](./contributing.md) | +| Running the CLI in a terminal or from a script | [CLI install and run](../cli/index.md) | + +Product features are covered elsewhere — start from [Get started](../start/index.md) and [Desktop features](../desktop/index.md). diff --git a/docs/en/memory/02-implementation.md b/docs/en/internals/memory-internals.md similarity index 92% rename from docs/en/memory/02-implementation.md rename to docs/en/internals/memory-internals.md index ddf2da7c..b40df201 100644 --- a/docs/en/memory/02-implementation.md +++ b/docs/en/internals/memory-internals.md @@ -1,16 +1,17 @@ -# Claude Code Memory System — Implementation Details +--- +title: Memory System Internals +nav_title: Memory Internals +description: Path resolution, prompt injection, auto-extraction, retrieval, and team memory sync. +order: 10 +--- -> From system prompt injection to background auto-extraction, dissecting every technical detail of the memory system. +# Memory System Internals -

-Architecture · Path Resolution · Prompt Injection · Auto-Extraction · Intelligent Retrieval · Memory Scanning · Agent Memory · Team Sync · Constants · Data Flow -

+From system prompt injection to background auto-extraction, dissecting every technical detail of the memory system. ![Implementation Architecture Overview](./images/05-architecture-overview.png) ---- - -## 1. Overall Architecture +## Overall Architecture The memory system is powered by 5 core modules working in concert: @@ -26,16 +27,14 @@ Auxiliary modules: | Module | Source Location | Responsibility | |--------|----------------|----------------| -| **AutoDream** | `src/services/autoDream/` | Background memory consolidation ("dreaming"); see [03-autodream.md](./03-autodream.md) | +| **AutoDream** | `src/services/autoDream/` | Background memory consolidation ("dreaming"); see [AutoDream](./autodream.md) | | **Type Definitions** | `src/memdir/memoryTypes.ts` | Taxonomy and prompt templates for the four memory types | | **Freshness** | `src/memdir/memoryAge.ts` | Calculates memory age, generates stale warnings | | **File Detection** | `src/utils/memoryFileDetection.ts` | Determines whether a path belongs to the memory system | -| **Agent Memory** | `src/tools/AgentTool/agentMemory.ts` | Three-level memory directories exclusive to sub-agents | +| **Agent Memory** | `src/tools/AgentTool/agentMemory.ts` | Three-level memory directories exclusive to subagents | | **Team Sync** | `src/services/teamMemorySync/` | Remote upload/download of memories | ---- - -## 2. Path Resolution System +## Path Resolution System ![Path Resolution Flow](./images/06-path-resolution.png) @@ -89,9 +88,7 @@ settings.json autoMemoryEnabled -> Follows setting Default -> Enabled ``` ---- - -## 3. System Prompt Injection +## System Prompt Injection ![Prompt Injection Flow](./images/07-prompt-injection.png) @@ -148,9 +145,7 @@ if (bytes > 25,000) -> Truncate at last newline character The prompt explicitly tells the model the directory already exists, avoiding wasted turns on `ls` or `mkdir`. ---- - -## 4. Automatic Memory Extraction +## Automatic Memory Extraction ![Auto-Extraction Flow](./images/08-auto-extraction.png) @@ -236,9 +231,7 @@ If a previous extraction is still running: 2. After the old extraction completes, a **tail extraction** is immediately launched 3. The tail extraction only processes messages added between the two calls ---- - -## 5. Intelligent Memory Retrieval +## Intelligent Memory Retrieval ![Memory Retrieval Flow](./images/09-memory-retrieval.png) @@ -293,9 +286,7 @@ function memoryFreshnessText(mtimeMs: number): string { } ``` ---- - -## 6. Memory Scanning in Detail +## Memory Scanning in Detail ### `scanMemoryFiles()` @@ -330,13 +321,11 @@ Generates a manifest consumed by Sonnet or the extraction agent: - [project] freeze.md (2026-03-10T15:00:00.000Z): Merge freeze starting 3/5 ``` ---- - -## 7. Agent Memory +## Agent Memory ![Agent Memory Three-Level Scoping](./images/10-agent-memory.png) -Sub-agents (launched via the Agent tool) have an independent three-level memory system: +Subagents (launched via the Agent tool) have an independent three-level memory system: | Scope | Path | Description | |-------|------|-------------| @@ -349,9 +338,7 @@ Differences from main memory: - Files can be written directly without the two-step process - Each agent type is isolated (explorer, planner, etc. each have their own directory) ---- - -## 8. Team Memory Sync +## Team Memory Sync When the `TEAMMEM` feature flag is enabled: @@ -391,9 +378,7 @@ In `memoryTypes.ts`, each type has a `` directive: | project | Leans toward team | | reference | Usually team | ---- - -## 9. Key Constants Quick Reference +## Key Constants Quick Reference ```typescript // Index file @@ -415,9 +400,7 @@ maxTurns = 5 // Forked agent max 5 turns Max 5 relevant memories returned ``` ---- - -## 10. Data Flow Overview +## Data Flow Overview ``` ┌─────────────────────────────────────────────────────┐ @@ -475,6 +458,6 @@ Max 5 relevant memories returned │ -> Merge duplicates / fix stale / compress index │ │ -> Notify user: "Improved N memories" │ │ │ -│ See 03-autodream.md for details │ +│ See autodream.md for details │ └─────────────────────────────────────────────────────┘ ``` diff --git a/docs/en/memory/01-usage-guide.md b/docs/en/internals/memory.md similarity index 88% rename from docs/en/memory/01-usage-guide.md rename to docs/en/internals/memory.md index 46ab5ac8..bef568c4 100644 --- a/docs/en/memory/01-usage-guide.md +++ b/docs/en/internals/memory.md @@ -1,16 +1,17 @@ -# Claude Code Memory System — Usage Guide +--- +title: Memory System Usage Guide +nav_title: Memory Usage +description: The four memory types, how saving is triggered, where memories live, and how to manage them. +order: 9 +--- -> Let Claude Code remember who you are, what you prefer, and what's happening in your project across sessions. +# Memory System Usage Guide -

-Memory System · Four Memory Types · Trigger Saving · Storage Location · Manage Memories · Lifecycle · Quick Reference -

+Let Claude Code remember who you are, what you prefer, and what's happening in your project across sessions. ![Memory System Overview](./images/01-memory-overview.png) ---- - -## 1. What Is the Memory System? +## What Is the Memory System? Claude Code's memory system is a **file-based persistent knowledge store** that allows Claude to continuously build understanding of you and your project across multiple conversations. @@ -23,15 +24,13 @@ Core principle: **Only remember things that cannot be inferred from the code its | Non-critical merges frozen after Thursday | Existing CLAUDE.md content | | Bug tracking is in Linear's INGEST project | Debugging solutions (fixes are already in the code) | ---- - -## 2. Four Memory Types +## Four Memory Types ![Four Memory Types](./images/02-memory-types.png) Claude Code strictly categorizes memories into four types: -### 2.1 User (User Profile) +### User (User Profile) Records your role, goals, skill level, and preferences to help Claude tailor its collaboration approach. @@ -40,7 +39,7 @@ User says: I've written Go for ten years, but this is my first time touching the Claude saves: Deep Go experience, React newcomer — explain frontend concepts using backend analogies ``` -### 2.2 Feedback (Behavioral Feedback) +### Feedback (Behavioral Feedback) Your corrections or affirmations about how Claude works. These memories prevent Claude from repeating the same mistakes. @@ -51,7 +50,7 @@ Claude saves: User prefers concise replies, no trailing summaries **Important**: Not only corrections are recorded -- affirmations are too. If Claude makes a non-obvious choice and you approve, that gets remembered as well. -### 2.3 Project (Project Context) +### Project (Project Context) Project context that cannot be derived from the code or Git history: who's doing what, why, and deadlines. @@ -62,7 +61,7 @@ Claude saves: Merge freeze starting 2026-03-05, flag non-critical PR work after **Note**: Claude converts relative dates ("Thursday") to absolute dates ("2026-03-05") to ensure memories don't become ambiguous over time. -### 2.4 Reference (External References) +### Reference (External References) Pointers to information in external systems: dashboards, issue trackers, Slack channels. @@ -71,9 +70,7 @@ User says: On-call monitors the grafana.internal/d/api-latency dashboard Claude saves: grafana.internal/d/api-latency is the on-call latency dashboard — check when editing request path code ``` ---- - -## 3. How to Trigger Memory Saving +## How to Trigger Memory Saving ![Memory Trigger Flow](./images/03-memory-trigger.png) @@ -84,8 +81,8 @@ This is the primary method. **You don't need to do anything** -- Claude automati Workflow: 1. You have a normal conversation with Claude 2. Claude finishes its response (no tool calls pending) -3. A **memory extraction sub-agent** starts in the background -4. The sub-agent analyzes the recent conversation content +3. A **memory extraction subagent** starts in the background +4. The subagent analyzes the recent conversation content 5. It identifies memories worth saving 6. Writes memory files + updates the MEMORY.md index @@ -121,9 +118,7 @@ Type `/remember` to trigger the memory review skill, which will: - Detect duplicate, outdated, and conflicting memories - **Does not modify anything directly** -- all changes require your approval ---- - -## 4. Where Are Memories Stored? +## Where Are Memories Stored? ### Directory Structure @@ -173,9 +168,7 @@ MEMORY.md is an index, not content. It is **always loaded into context**, with o **Limit**: Maximum 200 lines or 25KB; content beyond this is truncated. ---- - -## 5. How to Manage Memories +## How to Manage Memories ### Ask Claude to Forget @@ -215,9 +208,7 @@ Set in `~/.claude/settings.json`: Supports `~/` expansion. For security reasons, the project-level `.claude/settings.json` is **not allowed** to set this option. ---- - -## 6. Memory Lifecycle +## Memory Lifecycle ![Memory Lifecycle](./images/04-memory-lifecycle.png) @@ -245,14 +236,14 @@ New information learned during conversation ### AutoDream -- "Dreaming" to Organize Memories -Claude Code has a hidden **AutoDream** feature, analogous to how the human brain organizes memories during sleep. When the following conditions are met, Claude silently launches a "dreaming" sub-agent in the background: +Claude Code has a hidden **AutoDream** feature, analogous to how the human brain organizes memories during sleep. When the following conditions are met, Claude silently launches a "dreaming" subagent in the background: - At least **>= 24 hours** since the last consolidation - At least **>= 5 sessions** accumulated in the interim The dreaming process has four phases: Orient -> Gather -> Consolidate -> Prune. The bottom status bar shows **"dreaming"**, and you can press `Shift+Down` to view progress or `x` to terminate. -For a detailed technical analysis, see [AutoDream Memory Consolidation](./03-autodream.md). +For a detailed technical analysis, see [AutoDream Memory Consolidation](./autodream.md). ### Freshness Management @@ -260,9 +251,7 @@ For a detailed technical analysis, see [AutoDream Memory Consolidation](./03-aut - **Memories older than 1 day**: Accompanied by a stale warning, reminding Claude to verify before citing - **Memories referencing file paths/function names**: Confirmed via grep before use to ensure they still exist ---- - -## 7. Quick Reference +## Quick Reference | Action | Method | |--------|--------| diff --git a/docs/en/reference/local-server.md b/docs/en/internals/server.md similarity index 97% rename from docs/en/reference/local-server.md rename to docs/en/internals/server.md index 5ce694ae..d8ee5ad4 100644 --- a/docs/en/reference/local-server.md +++ b/docs/en/internals/server.md @@ -1,4 +1,11 @@ -# Local Server +--- +title: Local Server and API +nav_title: Local Server +description: Startup flags, access control, REST surface, and chat WebSocket of the local server. +order: 2 +--- + +# Local Server and API The local server is the runtime boundary between Desktop, the H5 browser client, and the Claude CLI. It provides REST APIs, the chat WebSocket, provider protocol translation, and Desktop web assets. The packaged Desktop app manages it automatically. Start it manually only for source development, headless deployment, or a custom client. diff --git a/docs/en/skills/02-implementation.md b/docs/en/internals/skills-internals.md similarity index 96% rename from docs/en/skills/02-implementation.md rename to docs/en/internals/skills-internals.md index 78ecd02d..483938c0 100644 --- a/docs/en/skills/02-implementation.md +++ b/docs/en/internals/skills-internals.md @@ -1,16 +1,17 @@ -# Claude Code Skills System -- Implementation Details +--- +title: Skills Internals +nav_title: Skills Internals +description: How skills are discovered, parsed, injected into conversations, and run in fork subagents. +order: 8 +--- -> A deep dive into how Skills are discovered, loaded, injected, executed, and managed. +# Skills Internals -

-Architecture · Discovery & Loading · Frontmatter Parsing · Injection · Execution Engine · Fork Execution · Conditional Activation · Hook Integration · Permission System · Complete Lifecycle · Source Index -

+A deep dive into how Skills are discovered, loaded, injected, executed, and managed. ![Skills Architecture Overview](./images/04-skills-architecture.png) ---- - -## 1. Overall Architecture +## Overall Architecture The Skills system consists of 5 core modules working together: @@ -40,9 +41,7 @@ The Skills system consists of 5 core modules working together: | Activation | `loadSkillsDir.ts` (second half) | Conditional activation and dynamic discovery | | Context | `forkedAgent.ts` | Context preparation and modification | ---- - -## 2. Skill Discovery and Loading +## Skill Discovery and Loading ### Loading Entry Point @@ -225,9 +224,7 @@ async function fetchCommandsForClient(client) { **Feature gate:** `feature('MCP_SKILLS')` controls whether MCP Skills are available. ---- - -## 3. Frontmatter Parsing +## Frontmatter Parsing ### Parsing Flow @@ -309,9 +306,7 @@ async getPromptForCommand(args, toolUseContext) { } ``` ---- - -## 4. Skill Injection into Conversations +## Skill Injection into Conversations ### Injection Flow @@ -394,9 +389,7 @@ Important: }) ``` ---- - -## 5. SkillTool Execution Engine +## SkillTool Execution Engine ### Tool Definition @@ -498,9 +491,7 @@ async function getAllCommands(context: ToolUseContext): Promise { } ``` ---- - -## 6. Fork Sub-Agent Execution +## Fork Sub-Agent Execution ### Execution Flow @@ -603,9 +594,7 @@ export async function prepareForkedCommandContext( } ``` ---- - -## 7. Conditional Activation and Dynamic Discovery +## Conditional Activation and Dynamic Discovery ### Conditional Skills @@ -679,9 +668,7 @@ clearCommandMemoizationCaches() Next conversation turn loads the updated Skill list ``` ---- - -## 8. Hook Integration +## Hook Integration ### Hook Registration @@ -732,9 +719,7 @@ During session └─ Hooks with once: true are automatically removed after first execution ``` ---- - -## 9. Permission System +## Permission System ### Check Flow @@ -775,9 +760,7 @@ SAFE_SKILL_PROPERTIES = { **Core principle:** Newly added frontmatter fields require permission by default, unless explicitly added to the whitelist. ---- - -## 10. Complete Lifecycle +## Complete Lifecycle ### Data Flow Overview @@ -841,9 +824,7 @@ On compression → buildPostCompactMessages() Restored scoped by agentId (prevents cross-agent leakage) ``` ---- - -## 11. Source Code Index +## Source Code Index ### Core Files diff --git a/docs/en/skills/01-usage-guide.md b/docs/en/internals/skills.md similarity index 93% rename from docs/en/skills/01-usage-guide.md rename to docs/en/internals/skills.md index d604cb2d..c4a56ffb 100644 --- a/docs/en/skills/01-usage-guide.md +++ b/docs/en/internals/skills.md @@ -1,16 +1,17 @@ -# Claude Code Skills System -- Usage Guide +--- +title: Skills Usage Guide +nav_title: Skills Usage +description: Skill sources, definition format, invocation, execution context, and permission control. +order: 7 +--- -> Skills are the extensible capability engine of Claude Code, allowing you to define custom automated workflows using Markdown files. +# Skills Usage Guide -

-What Are Skills · Six Sources · Definition Format · Invocation · Execution Context · Conditional Activation · Permissions · Quick Reference -

+Skills are the extensible capability engine of Claude Code, allowing you to define custom automated workflows using Markdown files. ![Skills System Overview](./images/01-skills-overview.png) ---- - -## 1. What Are Skills? +## What Are Skills? Skills are Claude Code's **extensible capability plugin system**. Each Skill is a Markdown file (with YAML frontmatter) that defines a specialized prompt and behavioral configuration, enabling Claude to execute professional workflows in specific scenarios. @@ -21,13 +22,11 @@ Core capabilities: | Specialized Workflows | Define standard processes for code review, TDD, debugging, etc. | | Tool Permission Control | Restrict a Skill to only use specified tools | | Model Switching | Assign different models to different Skills | -| Execution Isolation | Fork mode runs in an isolated sub-agent | +| Execution Isolation | Fork mode runs in an isolated subagent | | Conditional Activation | Activate only when specific files are being operated on | | Hook Injection | Automatically register lifecycle hooks when a Skill is invoked | ---- - -## 2. Six Skill Sources +## Six Skill Sources ![Skill Source Types](./images/02-skill-sources.png) @@ -128,9 +127,7 @@ Provided by connected MCP servers, naming format: `mcp__server-name__prompt-name **Security restriction**: MCP Skills are from remote untrusted sources and are **prohibited from executing** `!`...`` inline shell commands. ---- - -## 3. Skill Definition Format +## Skill Definition Format ### Directory Structure @@ -206,9 +203,7 @@ Supported special syntax: | `argument-hint` | string | -- | Argument hint text | | `version` | string | -- | Version number | ---- - -## 4. Invocation Methods +## Invocation Methods ![Skill Invocation Flow](./images/03-skill-invocation.png) @@ -259,9 +254,7 @@ When Skills with the same name exist in multiple sources, they are resolved in t 7. Built-in Commands ← Lowest priority ``` ---- - -## 5. Execution Context +## Execution Context ### Inline Mode (Default) @@ -279,7 +272,7 @@ context: inline # Default value, can be omitted ### Fork Mode (Sub-Agent) -The Skill runs in an **isolated sub-agent** with its own independent token budget and context. +The Skill runs in an **isolated subagent** with its own independent token budget and context. ```yaml context: fork @@ -303,9 +296,7 @@ agent: general-purpose # Optional, specifies agent type | Use Cases | Brief guidance, extended context | Long tasks, independent computation | | Tool Restrictions | contextModifier modification | modifiedGetAppState | ---- - -## 6. Conditional Activation +## Conditional Activation Skills can use the `paths` frontmatter to implement **on-demand activation**, becoming visible to the model only when matching files are operated on. @@ -342,9 +333,7 @@ In addition to conditional activation, Skills also support **runtime discovery** 5. New directory found → addSkillDirectories() → load and register ``` ---- - -## 7. Permission Control +## Permission Control ### Auto-Allow @@ -370,9 +359,7 @@ Allow? (y)es / (n)o / (a)lways allow / (d)eny **Processing order:** Deny rules → Allow rules → Safe property check → Ask user ---- - -## 8. Quick Reference +## Quick Reference ### Creating a Skill diff --git a/docs/en/reference/project-structure.md b/docs/en/internals/structure.md similarity index 96% rename from docs/en/reference/project-structure.md rename to docs/en/internals/structure.md index 4014dbe4..ee531dec 100644 --- a/docs/en/reference/project-structure.md +++ b/docs/en/internals/structure.md @@ -1,3 +1,10 @@ +--- +title: Project Structure +nav_title: Structure +description: Responsibility boundaries across the CLI, server, desktop app, adapters, and docs site. +order: 3 +--- + # Project Structure The repository contains the CLI/TUI, local Server, Electron desktop app, IM adapters, and documentation site. This map lists stable responsibility boundaries rather than every file. diff --git a/docs/en/memory/index.md b/docs/en/memory/index.md deleted file mode 100644 index b1f5a292..00000000 --- a/docs/en/memory/index.md +++ /dev/null @@ -1,121 +0,0 @@ -# Claude Code Memory System Documentation - -> Complete usage guide and technical implementation documentation for the memory system - ---- - -## Documentation Index - -### [01-usage-guide.md](./01-usage-guide.md) — Usage Guide - -A comprehensive user-facing manual covering: - -- **Four memory types**: User (user profile), Feedback (behavioral feedback), Project (project context), Reference (external references) -- **Four trigger methods**: Automatic extraction, explicit requests, `/memory` command, `/remember` command -- **Storage format**: YAML frontmatter + Markdown content -- **Management operations**: Forgetting, ignoring, manual editing, disabling, custom directories -- **Lifecycle**: From learning to injection, freshness management - -**Target audience**: All Claude Code users - ---- - -### [02-implementation.md](./02-implementation.md) — Implementation Details - -A deep technical analysis for developers, covering: - -- **5 core modules**: Path resolution, prompt construction, memory scanning, intelligent retrieval, automatic extraction -- **Path resolution system**: Priority chain, security validation, enable conditions -- **System prompt injection**: `loadMemoryPrompt()` -> `buildMemoryLines()`, MEMORY.md truncation strategy -- **Automatic memory extraction**: Forked agent, mutual exclusion mechanism, tool permissions, merge mechanism -- **Intelligent retrieval**: `scanMemoryFiles()` -> Sonnet selection -> freshness warnings -- **Agent memory**: Three-level scoping (user/project/local) -- **Team memory sync**: Pull/Push API, merge semantics -- **Complete data flow**: From session startup to context injection - -**Target audience**: Contributors, architects, developers who want a deep understanding of the implementation - ---- - -### [03-autodream.md](./03-autodream.md) — AutoDream Memory Consolidation - -Claude's "dreaming" mechanism -- a deep dive into background silent memory consolidation, covering: - -- **Core concept**: Like how the human brain organizes memories during sleep, periodically reviewing multiple sessions to consolidate knowledge -- **Five-gate check**: Feature toggle -> Time gate (24h) -> Scan throttle (10min) -> Session gate (5 sessions) -> Lock gate -- **Four-phase process**: Orient -> Gather -> Consolidate -> Prune -- **Security restrictions**: Read-only Bash, write operations limited to memory directory, PID lock file mutual exclusion -- **UI presentation**: Bottom "dreaming" label, Shift+Down detail dialog, completion notification -- **Configuration control**: settings.json local toggle + GrowthBook remote feature flag -- **Comparison with extractMemories**: Taking notes during the day vs. organizing the notebook while sleeping - -**Target audience**: Contributors, architects, developers interested in Claude's automated memory management - ---- - -## Illustrations - -All illustrations use a dark background (#1a1a2e) + Claude Code Haha orange-blue accent (#FF7A00) style, consistent with the official Claude Code documentation. - -| Image | Description | Size | -|-------|-------------|------| -| `01-memory-overview.png` | Memory system overview -- four-layer architecture (trigger/type/storage/retrieval) | 632 KB | -| `02-memory-types.png` | Four memory types -- 2x2 grid showing User/Feedback/Project/Reference | 507 KB | -| `03-memory-trigger.png` | Memory trigger flow -- four paths from conversation to storage | 474 KB | -| `04-memory-lifecycle.png` | Memory lifecycle -- complete cycle flow + freshness checks | 1.0 MB | -| `05-architecture-overview.png` | Implementation architecture overview -- 5 core modules + auxiliary modules | 3.5 MB | -| `06-path-resolution.png` | Path resolution flow -- three-level priority + security validation | 1.0 MB | -| `07-prompt-injection.png` | Prompt injection flow -- loadMemoryPrompt dispatch logic | 1.1 MB | -| `08-auto-extraction.png` | Auto-extraction flow -- forked agent complete process | 1.2 MB | -| `09-memory-retrieval.png` | Intelligent retrieval flow -- Sonnet selection + freshness management | 816 KB | -| `10-agent-memory.png` | Agent memory scoping -- three-level nested structure | 523 KB | -| `11-autodream-overview.png` | AutoDream overview -- dreaming mechanism core architecture and human sleep analogy | 777 KB | -| `12-autodream-trigger.png` | AutoDream trigger flow -- five-gate check chain | 493 KB | -| `13-autodream-phases.png` | AutoDream four phases -- Orient/Gather/Consolidate/Prune | 602 KB | - ---- - -## Quick Start - -### For Users - -1. Read the [Usage Guide](./01-usage-guide.md) -2. Learn about the four memory types and trigger methods -3. Try the `/memory` and `/remember` commands - -### For Developers - -1. Read the [Implementation Details](./02-implementation.md) -2. Explore the source code: - - `src/memdir/paths.ts` -- Path resolution - - `src/memdir/memdir.ts` -- Prompt construction - - `src/memdir/memoryScan.ts` -- Memory scanning - - `src/memdir/findRelevantMemories.ts` -- Intelligent retrieval - - `src/services/extractMemories/` -- Automatic extraction -3. Understand the data flow and module interactions - ---- - -## Core Concepts Quick Reference - -| Concept | Description | -|---------|-------------| -| **MEMORY.md** | Index file, always loaded into context (max 200 lines / 25KB) | -| **Topic files** | `*.md` files containing frontmatter + content | -| **Auto-extraction** | Runs in the background after each response; a forked agent analyzes the conversation | -| **AutoDream** | Triggers after 24h + 5 sessions; consolidates/deduplicates/prunes all memories in the background | -| **Intelligent retrieval** | Sonnet model selects up to 5 relevant memories from the entire collection | -| **Freshness** | <=1 day: no warning; >1 day: stale warning attached | -| **Forked agent** | Shares prompt cache, restricted tool permissions, max 5 turns | -| **Three-level scoping** | Agent memory: user (global) > project (project-level) > local (local-level) | - ---- - -## Related Resources - -- [Claude Code Haha Home](/en/) -- [Memory system source code](https://github.com/NanmiCoder/cc-haha/tree/main/src/memdir/) -- [Auto-extraction service](https://github.com/NanmiCoder/cc-haha/tree/main/src/services/extractMemories/) -- [AutoDream service](https://github.com/NanmiCoder/cc-haha/tree/main/src/services/autoDream/) -- [DreamTask](https://github.com/NanmiCoder/cc-haha/tree/main/src/tasks/DreamTask/) -- [GitHub Issues](https://github.com/NanmiCoder/cc-haha/issues) diff --git a/docs/en/reference/fixes.md b/docs/en/reference/fixes.md deleted file mode 100644 index 092f66c3..00000000 --- a/docs/en/reference/fixes.md +++ /dev/null @@ -1,13 +0,0 @@ -# Fixes Compared with the Original Leaked Source - - -The leaked source could not run directly. This repository mainly fixes the following issues: - -| Issue | Root cause | Fix | -|------|------|------| -| TUI does not start | The entry script routed no-argument startup to the recovery CLI | Restored the full `cli.tsx` entry | -| Startup hangs | The `verify` skill imports a missing `.md` file, causing Bun's text loader to hang indefinitely | Added stub `.md` files | -| `--print` hangs | `filePersistence/types.ts` was missing | Added type stub files | -| `--print` hangs | `ultraplan/prompt.txt` was missing | Added resource stub files | -| **Enter key does nothing** | The `modifiers-napi` native package was missing, `isModifierPressed()` threw, `handleEnter` was interrupted, and `onSubmit` never ran | Added try/catch fault tolerance | -| Setup was skipped | `preload.ts` automatically set `LOCAL_RECOVERY=1`, skipping all initialization | Removed the default setting | diff --git a/docs/en/start/first-session.md b/docs/en/start/first-session.md new file mode 100644 index 00000000..0472923e --- /dev/null +++ b/docs/en/start/first-session.md @@ -0,0 +1,103 @@ +--- +title: Run your first session +nav_title: First session +description: Pick a folder, set permissions, state a goal, watch it edit files, review the diff. +order: 3 +--- + +# Run your first session + +Your model is connected. Now let it actually do something. Find a project you don't mind touching — ideally one under Git, so anything it breaks is one command away from being undone. + +## 1. New session, pick the project folder + +Click "New session" in the sidebar, or press `Cmd/Ctrl + N`. + +![Empty session, with permission mode, launch location, and model controls in the composer](../../images/app/session-new.webp) + +Look at the row along the bottom of the composer: `+` for attachments, then **permission mode**, **launch location**, **model and effort**, and finally "Run". + +Start with the **launch location** pill in the middle (it reads `task-board / main` in the screenshot) and pick a project folder. This sets the agent's boundary: reading files, searching, running commands, checking Git status — all of it happens inside this directory and nowhere else. + +If the folder is a Git repository, the same pill also lets you choose a branch and whether to use an isolated worktree. Skip the worktree for now — working directly in the folder makes the changes easiest to follow. + +## 2. Permissions: start with "Ask permissions" + +Click the permission mode button and all five levels open up. + +![The five permission modes](../../images/app/permission-modes.webp) + +| Mode | What the app says it does | +|---|---| +| **Ask permissions** | Confirm file edits and higher-risk commands when CLI asks | +| **Auto accept edits** | Claude writes to disk without asking | +| **Auto mode** (enable once) | Claude reviews tool calls and runs actions it considers safe | +| **Plan mode** | Architecture & reasoning only, no files | +| **Bypass permissions** (high risk) | Full tool access for shell and file system | + +**Leave it on "Ask permissions" for your first run.** Every file write and every risky command stops and asks, so you can see exactly what it intends to do. Loosen it later, once you know how it behaves. + +What the other four are for: + +- **Auto accept edits** opens up file writes only; commands still prompt. Good once you're confident about the scope of the change. +- **Auto mode** has to be enabled once by hand. After that Claude reviews each tool call itself, running what it judges safe and blocking what it judges risky. **It reduces prompts; it does not guarantee safety.** Use it in isolated environments only. +- **Plan mode** never touches a file — it only produces a plan. Use it for read-only investigation, or to make it explain its approach before it starts. +- **Bypass permissions** removes every check. Shell and filesystem are wide open, it can delete anything, and it won't ask. **Never use it in a directory holding production credentials, personal data, or anything not under version control.** + +:::danger +"Bypass permissions" is not just a slightly looser "Auto mode". Auto mode at least keeps Claude's own review in the loop; bypass removes even that. +::: + +Permission modes are locked while a turn is running — what the UI shows and what's actually enforced have to match. Wait for the turn to finish, or press `Cmd/Ctrl + .` to stop it. + +## 3. Say what you want + +Plain language is fine; you don't need to write a spec. What matters is that the goal is **verifiable** — that you'll know how to check whether it got there. + +For example: + +```text +Read this project, then add a "sort by due date" toggle to the task list. +Put it to the right of the heading; clicking it switches between manual +order and due date, with undated items last. Tell me which files you changed. +``` + +If you'd rather ease in, ask for something read-only first: "Read this project and tell me what it does and how to start it. Don't change anything." + +Press `Enter` to send (`Shift + Enter` for a newline). + +## 4. Watch it work + +![A full turn: collapsed tool calls, thinking, a permission prompt, an inline diff](../../images/app/session-main.webp) + +It orients itself before it starts editing, and you'll see several kinds of card go by: + +- **Tool call cards** ("Searched files, ran one command, read 4 files") — collapsed by default; expand to see exactly what it read and ran. +- **Thinking blocks** — its reasoning about the next step. +- **Permission prompts** ("Allow Claude to Edit index.html?") — these stop and wait for you. The card includes a preview of the change: green for added lines, red for removed. **Read the diff before you decide.** + +Three buttons: + +- **Allow** — this one time only. +- **Allow for session** — stop asking about this kind of operation for the rest of the session. Once the scope is clear, this saves a long string of repeated clicks. +- **Deny** — send it back. It will try a different approach, or ask you for more information. + +To stop mid-run, use the stop button at the bottom right of the composer or press `Cmd/Ctrl + .`. + +## 5. Review file by file + +After each edit lands, the session shows an **inline diff**: the file path, an `+8 / -3` line count, and old and new lines side by side with syntax highlighting. That's the right view for following a single change as it happens. + +Once a turn finishes and the changes pile up, open the **workspace** panel on the right ("Show Workspace" at the top right of the tab bar). It collects everything changed this turn into a list you can open for a full review, comment on specific lines, and send those comments — with their code location attached — straight back into the composer for another round. + +The full workspace walkthrough is in [Workspace](../desktop/workspace.md). + +:::warning +Denied edits never reach the disk, but the disk and `git diff` are the only source of truth. Run `git status` and `git diff` yourself before you ship anything — don't take the UI's word for it. +::: + +## Next + +- See what else it can do — [desktop feature map](../desktop/index.md) +- Keep going from your phone — [Phone and IM handoff](../desktop/remote.md) +- Something got stuck — [Won't install, won't open, won't connect](./troubleshooting.md) diff --git a/docs/en/start/index.md b/docs/en/start/index.md new file mode 100644 index 00000000..3ba8adf2 --- /dev/null +++ b/docs/en/start/index.md @@ -0,0 +1,56 @@ +--- +title: What Claude Code Haha is +nav_title: What it is +description: An AI coding workbench that runs on your own machine. You pick the model; you approve every change. +order: 0 +--- + +# What Claude Code Haha is + +It's an app on your computer. You hand it a project folder, describe what you want in plain language, and it goes off to read the code, edit files, and run commands — with every change laid out in front of you, waiting for your approval. + +![A full session: prompt, tool calls, file edits, inline diff](../../images/app/session-main.webp) + +That's a real session. Projects and history on the left, the conversation in the middle, and when Claude edits a file the diff appears right underneath, line by line. + +## How this relates to the Claude Code CLI + +Claude Code is Anthropic's command-line coding agent. The engine inside Claude Code Haha is a CLI built from repaired Claude Code sources (it's called `claude-haha` in this repo), and the desktop app is the graphical shell wrapped around it. + +Two practical consequences: + +- **You don't install Claude Code first.** The CLI engine ships inside the installer. No Node.js, no npm, no global commands. +- **Nothing is missing.** Permission prompts, subagents, Skills, MCP, memory — it's the same machinery, just shown as an interface instead of scrolling terminal output. + +If you'd rather stay in the terminal, the CLI is still there: see [Command line](../cli/index.md). + +## What it does for you + +**Writes code.** Describe a goal — add a feature, fix a bug, restyle a page — and it finds the files, reads the surrounding context, makes the edits, and tells you which files it touched. + +**Shows its work.** Every edit comes with an inline diff, and the workspace panel on the right collects everything changed this turn into a list you can open file by file. Don't like it? Send it back. + +**Delegates.** Big tasks can be split across subagents running in parallel, with their progress visible in the activity panel. You can also give each agent its own model, tools, and system prompt. + +**Runs on a schedule.** Tidy up logs every morning, audit dependencies every week — set a job on a schedule and it clocks in on its own, leaving a record of each run. + +**Follows you to your phone.** Turn on H5 access, scan a QR code, and pick the conversation back up on your phone. Or connect Telegram, Feishu, or WeChat and drive it from a chat window. + +## Three commitments + +**Local first.** Sessions, settings, memory, and skills live on your machine (under `~/.claude` by default). No accounts, no cloud sync, no uploading your code. The only outbound traffic goes to the model service you configured yourself. + +**Your choice of model.** Nothing is locked to one vendor. Sign in with a Claude, ChatGPT, or Grok account; use a built-in preset for DeepSeek, Kimi, or Zhipu GLM; or point it at a local model running in LM Studio or Ollama and pay nothing at all. + +**You approve the changes.** The default is "Ask permissions" — it stops and asks before writing a file or running a risky command. Loosen it to auto-accept edits, or tighten it so it can only plan and never touch a file. Five levels, switchable any time. + +## Start here + +Work through these in order; about twenty minutes gets you to a working first session. + +1. [Download and install](./install.md) — installers for all three platforms, and what to do when the OS blocks them. +2. [Connect a model](./models.md) — official accounts, third-party APIs, or local models. Pick one. +3. [Run your first session](./first-session.md) — pick a folder, set permissions, state a goal, watch it work, review the diff. +4. [Desktop feature map](../desktop/index.md) — once it's running, see what else is in the box. + +Stuck along the way? [Won't install, won't open, won't connect](./troubleshooting.md) is organized by symptom. diff --git a/docs/en/start/install.md b/docs/en/start/install.md new file mode 100644 index 00000000..bc4cb7d7 --- /dev/null +++ b/docs/en/start/install.md @@ -0,0 +1,121 @@ +--- +title: Download and install +nav_title: Install +description: Installers for macOS, Windows, and Linux, plus what to do when the OS blocks them. +order: 1 +--- + +# Download and install + +Install and go. You don't need Node.js, Python, or Claude Code — the CLI engine and the ripgrep binary used for file search are both bundled inside the installer. + +## Pick the right package + +Everything lives on [GitHub Releases](https://github.com/NanmiCoder/cc-haha/releases/latest). Choose by operating system and CPU architecture: + +| Your system | Download | +|---|---| +| macOS, Apple Silicon | `Claude-Code-Haha--mac-arm64.dmg` | +| macOS, Intel | `Claude-Code-Haha--mac-x64.dmg` | +| Windows x64 | `Claude-Code-Haha--win-x64.exe` | +| Windows ARM64 | `Claude-Code-Haha--win-arm64.exe` | +| Linux x64 | `Claude-Code-Haha--linux-x86_64.AppImage` or `-linux-amd64.deb` | +| Linux ARM64 | `Claude-Code-Haha--linux-arm64.AppImage` or `-linux-arm64.deb` | + +Not sure which architecture you have? On macOS check the chip listed in "About This Mac"; on Windows check the system type under Settings → System → About. Don't guess from the brand of the machine. + +The `.blockmap` and `latest*.yml` files are used by the app's own updater. You don't need to download them. + +## macOS + +1. Open the DMG. +2. Drag Claude Code Haha into Applications. +3. Launch it from Applications. + +### If macOS says the app is damaged + +Nothing is actually damaged. macOS quarantines anything downloaded from the web and refuses to launch it without an Apple signature — but it words the error as "damaged", which is thoroughly misleading. Signed and notarized releases skip this entirely; if you have an unsigned build, use one of the two routes below. + +**Option 1: the official script (recommended)** + +Download `install-macos-unsigned.sh` from the same Release into **the same folder as the DMG** (Downloads, for example), then run: + +```bash +cd ~/Downloads +bash install-macos-unsigned.sh +``` + +The script picks the DMG matching your architecture, mounts it, installs the app into `/Applications`, strips the quarantine attribute, and launches it. Any existing install is moved to the Trash first rather than overwritten in place. + +**Option 2: clear the quarantine flag yourself** + +If the app is already in Applications: + +```bash +xattr -dr com.apple.quarantine "/Applications/Claude Code Haha.app" +``` + +Only do this for packages you have confirmed came from this repository's Releases. Never bypass Gatekeeper for software of unknown origin. + +## Windows + +1. Fully quit any running copy of the old version, including the system tray icon. +2. Double-click the `.exe`. +3. **Don't** right-click and choose "Run as administrator" — the installer is per-user, and running it elevated puts your data directory in the wrong place. + +Unsigned packages trigger a SmartScreen warning. Once you've confirmed the file came from this repository's Releases, click "More info" → "Run anyway". + +When upgrading in place, the installer inspects user data in the old install directory. If it reports that the program is still running, quit the main window and the tray icon, give the background sidecar, terminal, and IM adapter processes a few seconds to exit, then run the installer again. Don't delete the old install directory by hand first. + +## Linux + +**AppImage** (no installation, just run it): + +```bash +chmod +x Claude-Code-Haha--linux-x86_64.AppImage +./Claude-Code-Haha--linux-x86_64.AppImage +``` + +If it fails with a FUSE-related error, install the runtime: `sudo apt install libfuse2` on Ubuntu 22.04 and earlier, `libfuse2t64` on 24.04 and later. + +**deb** (installs into your application menu): + +```bash +sudo apt install ./Claude-Code-Haha--linux-amd64.deb +``` + +On ARM64 machines, use the corresponding `linux-arm64` file. + +## Running from source + +If you want to modify the code, debug the engine, or just use the CLI in a terminal: + +```bash +git clone https://github.com/NanmiCoder/cc-haha.git +cd cc-haha +bun install +cp .env.example .env +./bin/claude-haha +``` + +Requires [Bun](https://bun.sh) and Git. This runs the CLI only; for building the desktop app and configuring the local server, see [Command line](../cli/index.md). + +## Updating + +**In-app updates (recommended).** Open Settings → About → App Updates and click "Check now". It compares your installed version against the latest GitHub Release, downloads the new build, and offers "Install and restart". + +Before updating, stop any running sessions and save uncommitted work. + +If the download stalls, you probably can't reach GitHub. The same panel has an "Advanced update proxy" setting where you can switch to the system proxy or enter a local HTTP proxy address (`http://127.0.0.1:7890`, for instance). This proxy only affects the app's own update downloads — it has no effect on model requests. + +**Manual replacement.** Download the new installer from Releases and repeat the steps for your platform. Sessions, provider configuration, skills, agents, and memory live under `~/.claude`, not in the application directory, so installing over the top doesn't touch them. + +:::warning +The installer's data protection is not a backup. Keep your own copy of anything you can't afford to lose. +::: + +## Next + +Go to [Connect a model](./models.md). Until a model is connected, the app opens but can't send a single message. + +If it won't install or won't open, see [Won't install, won't open, won't connect](./troubleshooting.md). diff --git a/docs/en/start/models.md b/docs/en/start/models.md new file mode 100644 index 00000000..3cfc9368 --- /dev/null +++ b/docs/en/start/models.md @@ -0,0 +1,124 @@ +--- +title: Connect a model +nav_title: Connect a model +description: Official accounts, third-party APIs, or a local model — pick one and be running in ten minutes. +order: 2 +--- + +# Connect a model + +Claude Code Haha ships without a model. It's the shell that does the work; you have to give it a brain first. + +Click "Settings" at the bottom of the sidebar, then pick the first tab, "Providers". From there you have three routes: + +- **You have an official account** — Claude, ChatGPT, and Grok each have a built-in card. One click opens a browser sign-in. No API key to type. +- **You have a third-party API key** — DeepSeek, Kimi, Zhipu GLM and others come as presets. Paste the key and you're done. +- **You want it free** — run LM Studio or Ollama on your own machine. The model runs on your GPU, costs nothing, and works offline. + +You can configure all three and switch between them in the provider list. + +## Sign in with an official account + +Three cards sit at the top of Settings → Providers: + +| Card | What you need | +|---|---| +| **Claude Official** | A Claude.ai account (Pro / Max subscription, or an API account with credit) | +| **ChatGPT Official** | A ChatGPT account, via OpenAI OAuth | +| **Grok Official** | An xAI account, via official Grok OAuth | + +Click the sign-in button on the card ("Sign in to Claude" / "Sign in with ChatGPT" / "Sign in with Grok"). Your system browser opens the provider's authorization page; complete it with the same account and the browser hands you back to the app, where the card now reads as signed in. + +Three things to watch for: + +- Leave Claude Code Haha running for the whole flow — the callback has to land in the running app. +- If the browser doesn't open by itself, click "Copy authorization link" and paste it in manually. +- Proxies and blocking browser extensions can intercept either the authorization page or the local callback. Turn them off before retrying. + +Which models you get afterwards depends on your account tier and entitlements, not on the app. A model name appearing in documentation doesn't mean your account can call it. + +## Third-party API providers + +With an API key in hand, this is the fastest route. Click "Add Provider", pick something under "Preset", and the base URL and default models are filled in for you — all you supply is the key. + +The built-in presets, as they appear in the dialog: + +- **DeepSeek** · **Zhipu GLM** · **Kimi** · **MiniMax** — major Chinese model vendors; the base URLs point at each one's Anthropic-compatible endpoint. +- **接口AI (JiekouAI)** · **胜算云 (Shengsuanyun)** · **TeamoRouter** — routing services that give you access to official Claude models through their own gateway. +- **LM Studio** · **Ollama** — local models; see the next section. +- **Custom** — anything not listed above. + +When a preset has a signup page for API keys, a "Get API Key" button appears under the key field. + +## Local models + +To spend nothing and stay offline, run a model server on your machine and point the app at it. + +**LM Studio**: load a model, start the local server, then choose the `LM Studio` preset in "Add Provider" with base URL `http://localhost:1234`. + +**Ollama**: run `ollama serve`, choose the `Ollama` preset, and use base URL `http://localhost:11434`. + +Two hard requirements: + +1. **Do not append `/v1` to the base URL.** Both of these expose an Anthropic-compatible protocol and the app uses that path. Adding `/v1` gives you a straight 404. +2. **Raise the context window — at least 200K.** Claude Code's system prompt, tool definitions, and Skills consume a substantial amount of context before your first message. A default 4K or 8K window can't even hold the opening. Change this in LM Studio or Ollama's own model settings, not in this app. + +Whether a local model can actually sustain an agent workflow comes down to its tool-calling ability. Small models often talk endlessly without ever calling a tool — that's the model, not your configuration. + +## The Add Provider dialog, field by field + +![Add Provider dialog: preset, base URL, auth variable, API key, model mapping](../../images/app/settings-provider-add.webp) + +**Name** (required) — how this provider appears in the list. A preset fills it in; rename it to something you'll recognize, like "DeepSeek — work account". + +**Notes** — a line for yourself, e.g. "expires end of month". Doesn't affect any request. + +**Base URL** (required) — the provider's API root, not its marketing site. Presets get this right. When entering it yourself, watch for duplicated path segments: if the URL already ends in `/anthropic`, don't also append `/v1/messages`. + +**Auth Variable** — decides how your key is transmitted. Five options: + +| Option | When to use it | +|---|---| +| API Key (`ANTHROPIC_API_KEY`) | Direct Anthropic API access, sends an `x-api-key` header | +| Bearer Token (`ANTHROPIC_AUTH_TOKEN`) | Nearly all third-party Anthropic-compatible services, sends `Authorization: Bearer` | +| Bearer + Empty API_KEY | OpenRouter, Ollama and similar, which must not fall back to an Anthropic key | +| Write token to both variables | Services like Hugging Face Router that check both | +| Write dummy to both variables | Local vLLM-style services that only need placeholder auth | + +Presets pick the right one. If you're guessing, start with Bearer Token and switch to API Key if you get a 401. + +**Enable Tool Search** (on by default) — loads MCP tools and part of the tool definitions on demand, saving a large chunk of schema tokens on the first turn. It depends on the model supporting `tool_reference`. **Turn it off for weaker models, or for providers that reject this request shape.** + +**Disable experimental beta headers** — sets `CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1` for this provider. Some third-party gateways error out on beta-shaped API requests; checking this stops the headers being sent. **Try this first when you see a 400 or an "unsupported parameter" error.** + +**API Key** (required except for local models) — copied from the provider's console. Watch for leading or trailing whitespace when pasting. The key stays on your machine. + +**Model Mapping** — four slots, each taking a **model ID**, not a product name: + +- **Main Model** (required) — the model used for conversation. It must support tool calling. +- **Haiku Model** — leave blank to follow the main model. This slot handles lightweight work like title generation and file summaries; mapping it to a cheap small model saves real money. +- **Sonnet Model / Opus Model** — leave blank to follow the main model. Fill these in only when you actually want tiering. + +Each slot has a `1M` checkbox. Tick it only if that model genuinely supports a one-million-token context window. + +**API Format** — this field only appears when you pick the `Custom` preset or edit an existing provider. Three options: Anthropic Messages (native), OpenAI Chat Completions (proxied), and OpenAI Responses API (proxied). The proxied formats are translated by a loopback proxy the app starts locally — you don't deploy anything. Every built-in preset uses Anthropic native, which is why the field is hidden for them. + +Click "Add" when you're done. + +## After saving + +Back in the provider list, on the entry you just created: + +1. Click "Test". Anthropic-native providers run one step, "① Connectivity"; OpenAI formats add "② Proxy pipeline". Both must pass. +2. Click "Set default" so new sessions use it. +3. Multiple providers can be dragged to reorder. Order only affects how the list is displayed. + +Then start a new session and **pick the specific model from the model selector at the bottom right of the composer** — that list reflects what the active provider actually offers. The control next to it sets reasoning effort; leave it at the default if you're unsure. + +:::tip +A passing test isn't a guarantee. It proves the endpoint is reachable and the credentials work — not that the model can sustain tool calls and long context. The real check is asking for a task that edits a file, and seeing whether it actually does. +::: + +Getting a 401, a connection failure, or an empty model list? See the model section of [Won't install, won't open, won't connect](./troubleshooting.md). + +With a model connected, go [run your first session](./first-session.md). diff --git a/docs/en/start/troubleshooting.md b/docs/en/start/troubleshooting.md new file mode 100644 index 00000000..8510eabf --- /dev/null +++ b/docs/en/start/troubleshooting.md @@ -0,0 +1,211 @@ +--- +title: Won't install, won't open, won't connect +nav_title: Troubleshooting +description: Organized by symptom — install failures, blank window, model 401s, stuck sessions, port conflicts, phone access. +order: 4 +--- + +# Won't install, won't open, won't connect + +Find your symptom below. Each entry is "what you see → why → what to do". + +First, one check: make sure you're on the latest stable build from [GitHub Releases](https://github.com/NanmiCoder/cc-haha/releases/latest). A lot of problems on older versions are already fixed. + +## Won't install + +### macOS says the app is damaged and can't be opened + +**Why** — The file isn't damaged. macOS quarantines downloads and refuses to launch anything without an Apple signature, but words the error as "damaged", which sends everyone down the wrong path. + +**What to do** — Download `install-macos-unsigned.sh` from the same Release, put it in the same folder as the DMG, and run `bash install-macos-unsigned.sh`. Or, if the app is already in Applications, run `xattr -dr com.apple.quarantine "/Applications/Claude Code Haha.app"`. Full details in [Download and install](./install.md). + +### Windows shows a SmartScreen warning + +**Why** — Unsigned installers get flagged by SmartScreen. + +**What to do** — Confirm the file came from this repository's Releases, then click "More info" → "Run anyway". If the filename or origin doesn't match, don't bypass it. + +### The Windows installer says the program is still running + +**Why** — The old version's main process, sidecar, embedded terminal, or IM adapter hasn't fully exited, so the installer won't overwrite it. + +**What to do** + +1. Quit the main window, and quit the tray icon too. +2. Give background processes a few seconds to exit. +3. Still stuck? End any remaining Claude Code Haha processes in Task Manager. +4. Run the installer again. **Don't** use "Run as administrator", and **don't** manually delete data from the old install directory. + +### The Linux AppImage does nothing when I run it + +**Why** — Usually a missing execute bit, or missing FUSE. + +**What to do** — Run `chmod +x .AppImage` first. If you still get a FUSE error, install `libfuse2` on Ubuntu 22.04 and earlier, or `libfuse2t64` on 24.04 and later. + +## Won't open + +### Nothing happens when I launch it + +**Why** — Most often the wrong CPU architecture: `mac-x64` on Apple Silicon, or `win-x64` on ARM64 Windows. + +**What to do** — Re-check your architecture against [Download and install](./install.md) and reinstall the right package. An old version still running in the background can also block startup, so quit everything first. + +### The window opens but stays blank + +**Why** — The interface assets didn't load, usually after an interrupted upgrade or because a graphics driver issue prevented the renderer process from starting. + +**What to do** + +1. Fully quit the app (not just close the window) and reopen it. +2. Still blank? Reinstall the same version over the top. Sessions and configuration live under `~/.claude`, not in the application directory, so nothing is lost. +3. Still blank after that, it's failing during startup. File an issue at [GitHub Issues](https://github.com/NanmiCoder/cc-haha/issues) with your OS version, CPU architecture, and installer filename. + +:::warning +Never delete `~/.claude` while troubleshooting. Your sessions, provider configuration, skills, agents, and memory are all in there, and they don't come back. +::: + +### My session list is empty after updating + +**Why** — Almost certainly not data loss. The sidebar list is served by a local SQLite index, which is derived data that can be rebuilt; the original sessions are still stored as JSON / JSONL files. While the index rebuilds, the list looks empty. + +**What to do** — Open Settings → Diagnostics → Local index and check whether it's still building; let it finish. If there are degraded sources or an error code, click "Rebuild local index" — that only rebuilds the index and leaves conversations and settings untouched. + +## Model 401s and connection failures + +### 401 / 403 / "API Key invalid" + +**Why** — The auth method doesn't match the provider, or the key itself is wrong. + +**What to do** — Open Settings → Providers, edit the entry, and check in order: + +1. Is **Base URL** the API root rather than the marketing site? +2. Is **Auth Variable** correct? Third-party Anthropic-compatible services almost always want `Bearer Token (ANTHROPIC_AUTH_TOKEN)`; only direct Anthropic access uses `API Key (ANTHROPIC_API_KEY)`. If unsure, try both. +3. Did the **API Key** pick up stray whitespace, or has it expired or run out of credit? +4. Save, click "Test", and verify with a fresh session once it passes. + +Don't hand-edit `settings.json` for any of this — changes made in the app sync automatically. + +### 404, or "model not found" + +**Why** — A duplicated path segment in the URL, or a model ID borrowed from another platform. + +**What to do** — Check the base URL for repetition (if it already ends in `/anthropic`, don't append `/v1/messages`). Use the exact model ID string from the provider's console, not the product name. + +For local models (LM Studio / Ollama), **do not append `/v1` to the base URL** — that's the single most common source of 404s. + +### 400, or "unsupported parameter" + +**Why** — A third-party gateway is rejecting beta-shaped API requests, or the model doesn't support `tool_reference`. + +**What to do** — Edit the provider and check "Disable experimental beta headers". If that isn't enough, turn off "Enable Tool Search". Both switches exist for exactly these gateways. + +### Signed in, but the model I want isn't in the selector + +**Why** — Which models you see is determined by your account entitlements, region, and plan — not by the app. + +**What to do** — Refresh the provider status and reopen the model selector. If it's still missing, trust the provider's own console over anything else. + +### It talks endlessly but never edits a file + +**Why** — The model's tool-calling isn't strong enough to sustain an agent workflow. Small local models do this a lot. + +**What to do** — Try a model with solid function-calling support. Also confirm you aren't in Plan mode, which forbids touching files by design. + +### OAuth sign-in hangs + +**Why** — The authorization callback isn't reaching the running app. + +**What to do** — Keep the app running through the whole flow; complete the authorization in your system browser with the same account; disable proxies and blocking extensions; and check that your system clock is correct, since a skewed clock breaks the handshake. If the browser never opens, click "Copy authorization link" and paste it manually. + +## Sessions that hang + +### It spins forever without producing anything + +**Why** — Could be a network hiccup, or an upstream that never properly closed the response. + +**What to do** — In this order; don't jump straight to restarting: + +1. Wait ten or fifteen seconds in case it's transient. +2. Click stop, or press `Cmd/Ctrl + .`. +3. Open the activity panel and check the real state of background tasks and subagents — a quiet main conversation doesn't mean the background is idle. +4. Switch to another session and back to rule out a stale view. +5. If none of that helps, fully quit and reopen the app. +6. Copy the error summary from Settings → Diagnostics. + +### I can't change permission mode mid-session + +**Why** — This is deliberate. Changing permissions mid-turn would leave the UI showing something different from what's enforced. + +**What to do** — Wait for the turn to end, or stop it first, then switch. + +### I denied an edit and I'm not sure whether the file changed + +**Why** — Denied writes never reach the disk, but what the UI displays and what's on disk are separate things. + +**What to do** — Run `git status` and `git diff` before shipping. Disk and Git are the source of truth. + +## Port conflicts + +**What you see** — H5 won't load, or the local service doesn't come up after launch. + +**Why** — The local server defaults to port `3456`. If something else already holds that port, the service moves elsewhere or fails to start. + +**What to do** — Open Settings → H5 Access and read "Current port" — it may have already moved, leaving your old QR code pointing at the wrong place. If you need a stable address (for a bookmark or a reverse proxy), set "Fixed port" on the same page, anywhere in 1024–65535. Port changes apply after restarting the app. + +## Phone won't connect + +**What you see** — Scanning the QR code opens nothing, or you get an unauthorized error. + +**Why** — Address, port, token, and network all have to line up. Any one of them being wrong breaks the connection. + +**What to do** — Go through Settings → H5 Access: + +1. Is H5 access actually switched on? +2. Does "Access host / IP" still match your computer's current network interface? It changes when you switch Wi-Fi. +3. Does the port in the QR code match "Current port"? +4. Are the phone and computer on the same local network? +5. Does your firewall allow that port? +6. Has the token been regenerated? **The moment you regenerate it, every old QR code is dead.** + +If you changed the fixed port, restart the app. Full deployment guidance and security boundaries are in [Phone and IM handoff](../desktop/remote.md). + +### Does locking my phone kill a running task? + +No. A brief disconnect doesn't stop work in progress — it finishes in the background and you'll see the result when you reconnect. Only when a task is already idle *and* no client is connected does the disconnect grace timer stop the corresponding CLI (30 seconds by default). + +That's not a promise it will never drop, though. System sleep, process exit, proxy failures, and service restarts all still end the connection. + +### I scanned the IM QR code but my contacts still can't talk to it + +Scanning only binds the platform account; it doesn't authorize everyone who can message you. Each person still has to send the one-time pairing code generated in the desktop app, or be added to the allowlist. With both empty, access is denied by default. Per-platform differences are in [IM integrations](../im/index.md). + +## Computer Use does nothing + +**What you see** — You ask it to click something or control another app, and it says it can't, or simply doesn't move. + +**Why** — Computer Use has a chain of prerequisites and won't run unless every link passes. It supports macOS and Windows only — **there is no Linux support**. + +**What to do** — Open Settings → Computer Use and find the first item that isn't green: + +1. Is the toggle at the top on? (With it off, new sessions never get these tools at all.) +2. Did the Python 3 check pass? If not, install it, or point "Python Interpreter Path" at one you already have — conda and pyenv both work. +3. Are the virtual environment and dependencies ready and installed? If not, click "Install Environment". +4. On macOS, both "Accessibility Permission" and "Screen Recording Permission" must show as granted. Grant them under System Settings → Privacy & Security. +5. **Restart Claude Code Haha after granting them.** System permissions don't apply to an already-running process. +6. Is the app you want to control listed under "Authorized Apps"? + +Full details in [Computer Use](../desktop/computer-use.md). + +## Still stuck + +Go to Settings → Diagnostics: + +1. Click "Copy issue report" for a structured snapshot of the current state. +2. Search [GitHub Issues](https://github.com/NanmiCoder/cc-haha/issues) for the same problem before opening a new one. +3. If the report alone isn't enough to diagnose it, click "Export Bundle" and attach that too. + +Including these makes a fix much faster: app version, OS and CPU architecture, installer filename, which kind of provider you're using (**never paste an API key**), the shortest reproduction steps, the full error text, and whether the problem is in the desktop app, on the phone, or in the CLI. + +:::warning +Diagnostic reports make a real effort to omit chat content, file contents, full environment variables, and API keys — but they can still include local paths and provider hostnames. **Read one before you share it.** +::: diff --git a/docs/features/computer-use.md b/docs/features/computer-use.md deleted file mode 100644 index 70c8293f..00000000 --- a/docs/features/computer-use.md +++ /dev/null @@ -1,189 +0,0 @@ -# Computer Use 使用指南 - -Computer Use 允许模型读取屏幕并操作鼠标、键盘、应用和剪贴板。它直接作用于当前电脑,启用前请先理解授权范围和平台差异。 - -## 支持平台 - -| 平台 | 状态 | 关键差异 | -|---|---|---| -| macOS Apple Silicon / Intel | 支持 | 需要“辅助功能”和“屏幕录制”权限;支持原生截图过滤 | -| Windows | 支持 | 使用 Windows Python 运行时;截图不会过滤未授权窗口 | -| Linux | 不支持 | 当前没有 Linux 执行器 | - -打包桌面端不要求用户安装 Bun。Computer Use 需要可用的 Python 3,应用会在用户配置目录下创建隔离的虚拟环境并安装平台依赖。 - -## 快速开始 - -### 桌面端 - -1. 打开“设置 → Computer Use”。 -2. 开启 Computer Use。 -3. 检查 Python 状态;自动检测失败时,可选择 Python 可执行文件。 -4. 执行安装或修复,让应用创建虚拟环境和安装依赖。 -5. macOS 用户授予“辅助功能”和“屏幕录制”权限,然后重新检查。 -6. 选择允许控制的应用,并按需要开启剪贴板和系统组合键权限。 -7. 新建会话,用自然语言描述目标和允许操作的应用。 - -可以从简单、可撤销的任务开始: - -```text -截取当前屏幕并告诉我你看到了什么。 -打开 Notes,新建一条标题为“测试”的空白笔记。 -在已授权的应用中,帮我找到设置入口,但不要修改任何内容。 -``` - -### CLI - -源码模式需要先安装项目依赖,并保证 Python 3 可用: - -```bash -bun install -python3 --version -./bin/claude-haha -``` - -可以通过环境变量关闭动态 Computer Use MCP: - -```bash -CLAUDE_COMPUTER_USE_ENABLED=0 ./bin/claude-haha -``` - -也可以在 `~/.claude/cc-haha/computer-use-config.json` 中设置: - -```json -{ - "enabled": false -} -``` - -桌面设置页修改的是同一份托管配置。优先使用设置页,不要手工覆盖其中未知字段。 - -## 工具与 Teach 能力 - -代码中定义了 27 个 Computer Use 工具: - -| 类别 | 工具 | -|---|---| -| 授权 | `request_access`、`list_granted_applications` | -| 截图 | `screenshot`、`zoom` | -| 鼠标 | `left_click`、`right_click`、`middle_click`、`double_click`、`triple_click`、`left_click_drag`、`mouse_move`、`left_mouse_down`、`left_mouse_up`、`cursor_position`、`scroll` | -| 键盘 | `type`、`key`、`hold_key` | -| 应用 | `open_application`、`switch_display` | -| 剪贴板 | `read_clipboard`、`write_clipboard` | -| 控制流 | `wait`、`computer_batch` | -| Teach | `request_teach_access`、`teach_step`、`teach_batch` | - -基础控制能力包含 24 个工具。Teach 的 3 个工具只在宿主启用 Teach capability 时公开;未启用时,当前会话只会看到基础工具。 - -Teach 用于“带着用户一步一步操作”的场景: - -1. `request_teach_access` 请求教学所需的应用授权。 -2. `teach_step` 展示一个带锚点的说明,并等待用户点击下一步。 -3. `teach_batch` 把可以预判的多个教学步骤合并,减少模型往返。 - -Teach 授权与普通控制授权相互独立。教学步骤仍经过应用白名单和输入安全检查;用户退出教学后,模型不应继续调用 Teach 工具。 - -## 工作原理 - -Computer Use 使用“截图 → 分析 → 操作 → 再截图”的闭环: - -```text -模型 - → 调用 Computer Use MCP 工具 - → TypeScript 调度与安全检查 - → Python Bridge - → macOS / Windows 系统操作 - → 截图或操作结果返回模型 -``` - -- 工具定义与授权逻辑位于 `src/vendor/computer-use-mcp/`。 -- CLI 集成和 Python Bridge 位于 `src/utils/computerUse/`。 -- 平台执行器位于 `runtime/mac_helper.py` 和 `runtime/win_helper.py`。 -- 桌面安装、权限和预授权由 `src/server/api/computer-use.ts` 与 `desktop/src/pages/ComputerUseSettings.tsx` 管理。 - -## 授权模型 - -### 应用授权 - -模型必须先调用 `request_access`,说明需要哪些应用以及原因。用户可以批准或拒绝。设置页中的预授权应用是默认授权配置,不代表任意应用都可以被控制;运行中新增应用仍需要走相应授权流程。 - -应用按能力分为三个等级: - -| 等级 | 能力 | -|---|---| -| `read` | 读取截图,不执行输入 | -| `click` | 点击、移动和滚动,不输入文字或执行高权限动作 | -| `full` | 在其他安全检查通过后允许键盘、拖拽等完整操作 | - -### 剪贴板与系统组合键 - -剪贴板读取、剪贴板写入和系统级组合键是单独的授权标志。允许控制某个应用,不会自动获得这些权限。 - -### 并发 - -Computer Use 使用会话锁防止多个会话同时争夺鼠标和键盘。看到“正在被其他会话使用”时,应先停止或完成原会话,而不是删除锁文件。 - -## 平台安全边界 - -### macOS - -- 需要“辅助功能”才能输入和操作应用。 -- 需要“屏幕录制”才能截图。 -- 截图支持原生窗口过滤,只保留授权应用和桌面。 - -### Windows - -- 当前截图过滤能力为 `none`:截图中可能出现所有可见窗口。 -- 应用白名单仍会阻止把输入动作发送给未授权的前台应用。 -- 因为截图本身不做过滤,开始前应主动关闭或最小化包含敏感信息的窗口。 - -### 当前明确没有的保护 - -- **没有全局 Escape 中止热键。** 桌面端应使用当前任务的停止操作;CLI 运行可用终端中断。 -- **不会在每次操作前自动隐藏未授权窗口。** 不要依赖自动隐藏来保护敏感内容。 -- **像素陈旧验证默认关闭。** UI 变化后,模型应重新截图再点击。 - -这些限制是当前实现边界,不应在文档或 UI 中描述成已经可用。 - -## Python 运行时 - -首次安装或修复时,应用会: - -1. 把当前平台的 helper 和 requirements 同步到用户配置目录。 -2. 使用自动检测或用户选择的 Python 创建 venv。 -3. 安装或升级 pip。 -4. 按 requirements 内容哈希决定是否重新安装依赖。 -5. 通过 JSON payload 调用平台 helper,并解析统一 JSON 结果。 - -macOS 主要依赖 `mss`、Pillow、PyAutoGUI 和 PyObjC;Windows 还使用 pywin32、psutil、pyperclip 与 screeninfo。准确版本约束以 `runtime/requirements*.txt` 为准。 - -## 故障排查 - -### macOS 仍提示缺少权限 - -- 确认授权的是实际启动 Claude Code Haha 的应用。 -- 权限变更后完全退出并重新打开应用。 -- 在设置页重新执行权限检查。 - -### Python 安装失败 - -- 在设置页选择明确的 Python 3 可执行文件。 -- 确认该 Python 支持 `venv`。 -- 使用“安装/修复”重新创建运行时。 -- 查看“诊断”页中的 Computer Use 安装日志。 - -### 截图可以但点击失败 - -- 确认目标应用处于授权列表。 -- 确认它是当前前台应用。 -- 检查授权等级是否允许该动作。 -- UI 已变化时重新截图,不要复用旧坐标。 - -### Windows 截图出现其他窗口 - -这是当前 Windows 截图能力的已知边界。输入白名单不会过滤截图内容;执行前请关闭或最小化敏感窗口。 - -## 深入阅读 - -- [Computer Use 架构](./computer-use-architecture.md) -- [桌面端架构](../desktop/02-architecture.md) diff --git a/docs/guide/faq.md b/docs/guide/faq.md deleted file mode 100644 index 7cb5fdaa..00000000 --- a/docs/guide/faq.md +++ /dev/null @@ -1,153 +0,0 @@ -# 常见问题与求助 - -## 遇到问题时,怎样最快获得帮助? - -先确认正在使用 [GitHub Releases Latest](https://github.com/NanmiCoder/cc-haha/releases/latest),并重试一次能稳定复现问题的最短步骤。 - -如果 Desktop 仍能打开: - -1. 进入 **设置 → 诊断**。 -2. 点击 **复制 Issue 报告**。 -3. 在 [GitHub Issues](https://github.com/NanmiCoder/cc-haha/issues) 搜索相同问题;没有重复 Issue 时再新建。 -4. 粘贴报告,并补充复现步骤、预期结果和实际结果。 - -如果仅凭报告无法定位,可以再点击 **导出诊断包**。Issue 报告和诊断包会尽力省略聊天内容、文件内容、完整环境变量和 API Key,但仍可能含有路径、Provider 主机名等私密元数据;**分享前必须自行检查**。 - -建议同时提供: - -- Claude Code Haha 版本 -- 操作系统、CPU 架构和安装包类型 -- 使用的 Provider 类型与 API 格式,不要提供 API Key -- 最短复现步骤和完整错误文字 -- 问题发生在 Desktop、H5 还是 CLI - -如果应用无法启动,提供安装包版本、系统错误截图和启动前最后一个可见操作即可。不要为了排错直接删除或覆盖 `~/.claude`。 - -## Provider 与 OAuth - -### 我一定要安装 LiteLLM 才能用 OpenAI 兼容服务吗? - -不需要。Desktop 的 Custom Provider 支持: - -- Anthropic Messages(原生) -- OpenAI Chat Completions(本地代理转换) -- OpenAI Responses API(本地代理转换) - -只有当上游服务使用了应用尚未支持的协议或特殊字段时,才需要考虑 LiteLLM 等外部网关。 - -### 自定义 Provider 返回 401 或 API Key invalid - -在 **设置 → 服务商** 中编辑该 Provider,依次检查: - -1. Base URL 是否是服务商要求的 API 根地址,没有多写或漏写路径。 -2. API 格式是否与服务商真实接口一致。 -3. 认证方式是否正确:部分 Anthropic 兼容服务使用 Bearer Token,官方 Anthropic API 使用 `x-api-key`。 -4. 主模型 ID 是否真实存在,并且当前 Key 有权访问。 -5. 点击 **测试连接**,先处理第一步连通失败;OpenAI 格式还会显示第二步代理转换结果。 - -不要同时在截图、Issue 或诊断附件中公开 Base URL 查询参数、Token 或 API Key。 - -### Claude、ChatGPT 或 Grok 官方登录没有完成 - -- 保持 Claude Code Haha 正在运行,不要提前关闭登录页面或应用。 -- 允许系统浏览器打开授权页,并完成同一个账号的授权。 -- 检查系统时间是否正确,并确认代理、防火墙或浏览器扩展没有阻止服务商页面和本机 OAuth 回调。 -- 如果浏览器显示授权成功但应用没有更新,回到 **设置 → 服务商** 重新发起登录。 - -仍然失败时,复制 Issue 报告,并注明卡在“未打开浏览器”“授权页报错”还是“授权后未回到应用”。不要分享 OAuth code、access token 或浏览器 Cookie。 - -### 测试连接成功,聊天仍然失败 - -确认当前任务实际选择了刚配置的 Provider 和模型,而不只是把 Provider 保存到了列表中。再检查主模型和角色模型映射是否受该账号支持。 - -用新任务发送一条最短纯文本消息复测;如果仍失败,在诊断页复制 Issue 报告。连接测试证明接口、认证和基础转换可用,不等于所有模型能力、工具调用和长上下文组合都已验证。 - -## 安装与更新 - -### macOS 提示无法验证、已损坏或打不开 - -优先确认安装包来自 [官方 Latest Release](https://github.com/NanmiCoder/cc-haha/releases/latest),并选择了正确的 Intel 或 Apple Silicon 架构。正式签名和公证的版本通常只显示标准的下载来源确认。 - -旧版、draft 或临时 unsigned 包可能需要额外放行,见 [Desktop 安装指南](/desktop/04-installation#macos)。不要从不明镜像下载后绕过系统安全提示。 - -### Windows 出现 SmartScreen - -确认安装包来自官方 Release。未签名的 Windows 安装包可能显示 SmartScreen,可以展开“更多信息”核对发布者和文件名后再决定是否运行。 - -覆盖升级前先完全退出 Claude Code Haha。如果安装器提示进程仍在运行,关闭对应窗口和后台进程后重试,不要先删除用户配置目录。 - -### Linux AppImage 无法启动 - -先赋予执行权限: - -```bash -chmod +x Claude-Code-Haha-<版本>-linux-<架构>.AppImage -``` - -部分发行版还需要 FUSE。不同发行版的安装方式见 [Desktop 安装指南](/desktop/04-installation#linux)。 - -## H5 访问 - -### 手机或另一台电脑无法连接 H5 - -依次确认: - -1. Desktop 的 **H5 访问**已经开启。 -2. 使用设置页当前显示或二维码生成的 Server URL,不要复用旧局域网 IP。 -3. 客户端填写了当前 H5 Token。 -4. 两台设备位于可互通的网络,系统防火墙允许当前端口。 -5. 经过反向代理时,已正确转发 HTTP 和 WebSocket,并配置允许的来源。 - -H5 是当前 Desktop 服务的远程入口,不等同于完整 Desktop。终端、原生预览、宠物窗口和部分系统能力只能在桌面应用中使用。 - -详细部署与安全边界见 [H5 访问](/desktop/06-h5-access)。 - -## Git 分支与 Worktree - -### 创建隔离 Worktree 失败 - -常见原因包括: - -- 所选目录不是 Git 仓库 -- 分支不存在,或已被另一个 Worktree 占用 -- 当前工作树有未提交修改,无法安全执行预期操作 -- 目标 Worktree 路径已存在或不可写 - -先阅读界面显示的具体错误。可以改用当前工作树、选择其他分支,或在 Git 中安全处理已有改动后重试。不要为了创建 Worktree 自动删除现有目录或丢弃未提交修改。 - -### 当前工作树和隔离 Worktree 应该选哪个? - -- **当前工作树**:适合继续处理当前目录里已经存在的修改。 -- **隔离 Worktree**:适合并行任务、独立分支,或希望与当前目录改动分开的工作。 - -如果不确定且当前目录已有重要的未提交修改,先查看 Git 状态并备份,再决定使用哪条路径。 - -## Computer Use - -### Computer Use 不可用或无法控制应用 - -先打开 **设置 → Computer Use**,检查: - -- 全局开关已经开启 -- Python 环境和依赖检查通过 -- macOS 或 Windows 所需的系统权限已经授予 -- 目标应用已在允许控制的应用列表中 -- 当前会话的权限请求已经明确批准 - -授予系统权限后,通常需要重新打开 Claude Code Haha 或目标应用。Linux 当前不支持 Computer Use;请不要把 Desktop 安装成功当成 Computer Use 已配置完成。 - -如果设置页仍显示错误,复制 Issue 报告,并附上设置页状态;不要上传包含其他应用内容的完整屏幕截图。 - -## CLI - -### `bun install` 或 CLI 启动失败 - -确认已经进入仓库根目录,并使用项目支持的 Bun 版本: - -```bash -bun --version -bun install -./bin/claude-haha --help -``` - -如果提示缺少 `bun:bundle` 等 Bun 内置模块,先升级 Bun。完整安装路径见 [3 分钟上手](./quick-start.md#路径二从源码运行-cli),参数说明见 [CLI 参考](./cli-reference.md)。 diff --git a/docs/guide/global-usage.md b/docs/guide/global-usage.md deleted file mode 100644 index 21d76abb..00000000 --- a/docs/guide/global-usage.md +++ /dev/null @@ -1,58 +0,0 @@ -# 全局使用(任意目录启动) - - -如果你希望在任意项目目录直接运行 `claude-haha`,可以通过以下方式配置。配置完成后,`claude-haha` 会自动识别你当前所在的工作目录。 - -## macOS / Linux - -在 `~/.bashrc` 或 `~/.zshrc` 中添加: - -```bash -# 方式一:添加 PATH(推荐) -export PATH="$HOME/path/to/claude-code-haha/bin:$PATH" - -# 方式二:alias -alias claude-haha="$HOME/path/to/claude-code-haha/bin/claude-haha" -``` - -然后重新加载配置: - -```bash -source ~/.bashrc # 或 source ~/.zshrc -``` - -## Windows (Git Bash) - -在 `~/.bashrc` 中添加: - -```bash -export PATH="$HOME/path/to/claude-code-haha/bin:$PATH" -``` - -### Windows + WSL 工具链 - -如果 `claude-haha` 运行在 Windows / Git Bash,但 Node、Python、uv、bun 等工具主要安装在 WSL 里,可以显式通过 WSL 调用: - -```bash -wsl -e bash -lc 'node --version && python3 --version' -``` - -cc-haha 会在检测到 `wsl` / `wsl.exe` 调用时自动设置 `MSYS2_ARG_CONV_EXCL=*`,避免 Git Bash 把 `/home/...` 这类 WSL 路径错误转换成 `C:/Program Files/Git/home/...`。 - -如果你想让 Bash 工具默认进入 WSL,可以在启动前设置: - -```bash -export CLAUDE_CODE_SHELL_PREFIX='wsl -e bash -lc' -``` - -Computer Use 仍然控制 Windows 桌面应用,WSL 内的 CLI 工具不需要写入 `computer-use-config.json`。如果只使用 WSL 工具链、不需要桌面控制,建议使用 `--no-computer-use` 或在 Settings > Computer Use 中关闭它。 - -## 验证 - -配置完成后,进入任意项目目录测试: - -```bash -cd ~/your-other-project -claude-haha -# 启动后询问「当前目录是什么?」,应显示 ~/your-other-project -``` diff --git a/docs/guide/quick-start.md b/docs/guide/quick-start.md deleted file mode 100644 index a4007a3a..00000000 --- a/docs/guide/quick-start.md +++ /dev/null @@ -1,109 +0,0 @@ -# 3 分钟上手 - -Claude Code Haha 有两种使用方式。日常开发推荐安装 **Desktop**;只有在需要终端交互、脚本调用或参与源码开发时,才需要从源码运行 **CLI**。 - -| 方式 | 适合谁 | 需要准备 | -|------|--------|----------| -| **Desktop(推荐)** | 想直接管理项目、会话、Worktree、代码 Diff 和权限审批 | 下载对应系统的安装包;**不需要安装 Bun** | -| **CLI** | 偏好终端、需要 `--print` 自动化或准备参与开发 | Git、Bun 和一个模型服务 | - -## 路径一:安装 Desktop - -### 1. 下载当前稳定版 - -打开 [GitHub Releases Latest](https://github.com/NanmiCoder/cc-haha/releases/latest),按系统和 CPU 架构选择安装包: - -- macOS Apple Silicon(M 系列):`mac-arm64.dmg` -- macOS Intel:`mac-x64.dmg` -- Windows:`win-x64.exe` 或 `win-arm64.exe` -- Linux:对应架构的 `.AppImage` 或 `.deb` - -安装细节和系统提示处理见 [Desktop 安装指南](/desktop/04-installation)。 - -### 2. 首次配置模型服务 - -启动应用,打开 **设置 → 服务商**。 - -最快的方式是选择一个官方入口,并按界面提示完成连接或登录: - -- **Claude 官方** -- **ChatGPT 官方**:使用 ChatGPT 账号完成 OAuth -- **Grok 官方**:使用 xAI 账号完成 OAuth - -这些官方入口不要求手工填写 API Key。也可以点击 **添加服务商**,选择内置预设或 Custom,填写 API Key、接口地址、API 格式和模型映射。 - -自定义服务商保存前先点击 **测试连接**;测试通过后,将它 **设为默认**。OpenAI Chat Completions 和 OpenAI Responses API 可以由应用内置的本地代理转换,不必默认再安装 LiteLLM。 - -更多配置解释见 [第三方模型与自定义服务商](./third-party-models.md)。 - -### 3. 新建第一个任务 - -点击侧边栏的 `+`: - -1. 选择本地项目目录。 -2. 如果目录是 Git 仓库,选择要使用的分支。 -3. 决定在当前工作树中运行,还是创建隔离 Worktree。 - -当前工作树会直接看到目录里已有的未提交修改;隔离 Worktree 更适合并行任务或不希望影响当前工作目录的改动。 - -### 4. 确认模型和权限 - -发送第一条消息前,确认当前 Provider、模型和 effort。第一次使用建议保留默认的 **询问权限(Default)**:应用会在执行敏感工具或命令前请求确认。 - -只有明确理解影响时再使用自动接受或绕过权限模式。权限模式的区别见 [Desktop 快速上手](/desktop/01-quick-start#三选择正确的权限模式)。 - -### 5. 发送第一条消息 - -可以从一个可验证的小任务开始,例如: - -```text -先只读分析这个项目,告诉我它如何启动、主要目录分别负责什么,不要修改文件。 -``` - -看到流式回复、工具调用和权限请求后,说明 Desktop、模型服务和项目目录已经连通。 - -## 路径二:从源码运行 CLI - -### 1. 获取源码并安装 Bun - -先安装 [Git](https://git-scm.com/downloads) 和 [Bun](https://bun.sh),然后执行: - -```bash -git clone https://github.com/NanmiCoder/cc-haha.git -cd cc-haha -bun install -``` - -### 2. 配置模型服务 - -```bash -cp .env.example .env -``` - -编辑 `.env`,至少配置一种可用的认证方式、接口地址和模型。变量含义与认证头区别见 [环境变量](./env-vars.md)。 - -不要把真实 API Key 提交到 Git,也不要在 Issue、截图或诊断附件中公开它。 - -### 3. 启动并验证 - -macOS、Linux 或 Git Bash: - -```bash -./bin/claude-haha -./bin/claude-haha -p "概括当前项目的目录结构" -``` - -Windows PowerShell 或 cmd: - -```powershell -bun --env-file=.env ./src/entrypoints/cli.tsx -``` - -命令参数、无头模式、恢复模式和全局调用方式见 [CLI 参考](./cli-reference.md)。 - -## 下一步 - -- [Desktop 快速上手](/desktop/01-quick-start):会话、权限、附件和工作区操作 -- [第三方模型与自定义服务商](./third-party-models.md):Provider、API 格式与模型映射 -- [常见问题](./faq.md):安装、OAuth、H5、Worktree 和 Computer Use 排查 -- [全局使用](./global-usage.md):在任意目录启动 CLI diff --git a/docs/guide/third-party-models.md b/docs/guide/third-party-models.md deleted file mode 100644 index 1aaa924b..00000000 --- a/docs/guide/third-party-models.md +++ /dev/null @@ -1,98 +0,0 @@ -# 第三方模型 - -Claude Code Haha 可以连接 Anthropic Messages、OpenAI Chat Completions 和 OpenAI Responses 三类接口。对桌面端用户来说,最可靠的入口不是手写环境变量,而是 **设置 → Providers**:应用会保存认证方式、模型映射,并在需要时启动本机协议代理。 - -## 推荐配置流程 - -1. 打开桌面端的 **设置 → Providers**。 -2. 选择内置预设,或新建自定义 Provider。 -3. 填写 Base URL、认证信息和协议格式。 -4. 至少配置主模型;服务商使用不同模型名时,再配置 Haiku、Sonnet 和 Opus 槽位。 -5. 先点击测试,确认认证和模型可用,再激活 Provider。 -6. 新建会话验证工具调用;只返回文本并不代表 Agent 工作流完整可用。 - -激活后,桌面端和由它启动的 CLI 会复用同一份 Provider 配置。不要再把另一套旧密钥写进 `.env` 或 `~/.claude/settings.json`。 - -## 选择正确的协议 - -| Provider 格式 | 上游接口 | 适用场景 | -|----------------|----------|----------| -| `anthropic` | Anthropic Messages | 服务商原生兼容 Anthropic 请求与响应 | -| `openai_chat` | `/v1/chat/completions` | 服务商提供 OpenAI Chat Completions | -| `openai_responses` | `/v1/responses` | 服务商提供 OpenAI Responses | - -`anthropic` 会直接请求服务商,不替换协议。`openai_chat` 和 `openai_responses` 会由 Claude Code Haha 在本机启动回环代理,把 Claude Agent 流量转换成所选协议。这个代理只用于当前运行时,不需要把端口手工暴露到局域网。 - -如果提供商同时宣传“OpenAI 兼容”和“Anthropic 兼容”,以它实际实现完整、工具调用稳定的接口为准。不要仅根据 URL 中是否含有 `/v1` 推断协议。 - -## 认证与模型映射 - -认证头由服务商决定: - -- Anthropic API Key 通常使用 `x-api-key`。 -- Bearer Token 通常使用 `Authorization: Bearer`。 -- OpenAI 兼容服务通常使用 Bearer Token,但私有网关可能不同。 - -模型槽位不是额外下载的模型,而是 Claude Agent 在不同任务中请求的逻辑档位。主模型必须能处理工具调用;其他槽位可以映射到同一个模型,也可以按成本和能力分别配置。某个服务商不支持的槽位可以留空。 - -模型名和服务商能力会持续变化,所以本文不维护一份容易过期的型号清单。请在 Provider 设置页使用服务商当前返回的模型 ID,并通过测试按钮确认。 - -## 内置运行时 - -Claude Code Haha 还提供 Claude、OpenAI 和 Grok 的内置运行时。可用的登录方式取决于当前版本和本机账号状态;按照 Provider 页面显示的授权流程完成登录即可。 - -内置运行时与“自定义兼容接口”是两条路径。已有官方账号时优先使用内置运行时;连接中转服务、自建网关或本地模型时再创建自定义 Provider。 - -## 仅使用 CLI - -纯 CLI 用户可以直接配置 Anthropic Messages 兼容端点: - -```bash -ANTHROPIC_AUTH_TOKEN=sk-example -ANTHROPIC_BASE_URL=https://provider.example.com/anthropic -ANTHROPIC_MODEL=provider-model -./bin/claude-haha -``` - -这个方式不会自动把 Anthropic 请求转换成 OpenAI 协议。OpenAI Chat Completions 或 Responses 服务应先在桌面端创建 Provider,让应用管理协议代理。完整变量和生效顺序见 [环境变量](./env-vars.md)。 - -Azure OpenAI 使用项目内置的专用 Responses 路径,配置项见 [Azure OpenAI 环境变量](./env-vars.md#azure-openai)。 - -## LiteLLM:进阶兼容层 - -只有在服务商没有可靠的 Anthropic 接口、又无法直接使用 Provider 协议转换时,才需要额外部署 LiteLLM。它会增加一个服务、一次协议转换和一层排查成本。 - -最小示例: - -```yaml -model_list: - - model_name: provider-model - litellm_params: - model: openai/provider-model - api_base: https://provider.example.com/v1 - api_key: os.environ/PROVIDER_API_KEY -``` - -启动 LiteLLM 后,把它的 Anthropic 兼容地址作为 `anthropic` Provider 接入。部署、鉴权和模型前缀以 [LiteLLM 官方文档](https://docs.litellm.ai/) 为准。 - -## 能力边界 - -第三方模型要稳定运行 Agent 工作流,至少需要: - -- 正确处理多轮消息、system 内容和工具调用; -- 保留 tool call ID,并能接收对应的 tool result; -- 支持足够长的上下文和输出; -- 在流式模式下发送结构完整、顺序正确的事件。 - -思考模式、effort、Prompt Cache、图像输入和结构化输出是否可用,取决于所选协议、服务商和具体模型,不能统一认定为“支持”或“不支持”。遇到问题时先关闭可选能力,验证最小文本与工具调用,再逐项恢复。 - -## 常见问题 - -| 现象 | 优先检查 | -|------|----------| -| `401` / `403` | 认证头类型、Token 权限、Base URL 是否属于同一服务 | -| `404` | Provider 格式是否选错、Base URL 是否重复包含接口路径 | -| 模型不存在 | 使用服务商真实模型 ID,不要沿用其他平台的别名 | -| 一直输出文本但不执行工具 | 模型或网关是否完整支持工具调用 | -| 切换 Provider 后仍请求旧地址 | 是否同时在 `.env`、`settings.json` 和桌面端保存了配置 | -| 流式响应中断 | 先关闭可选能力,并检查网关是否改写或缓冲事件流 | diff --git a/docs/im/dingtalk.md b/docs/im/dingtalk.md index 0fa0f92e..83b4408b 100644 --- a/docs/im/dingtalk.md +++ b/docs/im/dingtalk.md @@ -1,106 +1,89 @@ +--- +title: 钉钉接入 +nav_title: 钉钉 +description: 用钉钉扫码授权机器人,走 DingTalk Stream 长连接,不需要公网回调地址。 +order: 4 +--- + # 钉钉接入 -> 钉钉 Adapter 的接入教程。钉钉走 DingTalk Stream,不需要公网回调地址。 +适合把钉钉当主力办公工具的国内团队:扫一次码就能授权出一个机器人,走 DingTalk Stream 长连接,不需要公网回调地址和内网穿透。回复默认用 AI Card 流式输出。限制是只处理单聊,不处理群聊;想要可点的权限卡片还得额外配一个模板 ID,否则只能回复文本命令。 -## 适用场景 +## 扫码绑定机器人 -钉钉方案适合通过钉钉单聊远程驱动本机 Claude Code。当前实现只处理单聊消息,不处理群聊。 +1. 打开「设置」→「IM 接入」,切到「钉钉」Tab。 +2. 在「扫码绑定钉钉机器人」里点「扫码绑定」。 +3. 用钉钉手机 App 扫码,并在钉钉里确认创建和授权。 +4. 等页面显示「钉钉机器人已绑定」。 -当前实现支持文本聊天、图片附件、项目选择、状态查看、停止生成、AI Card 流式输出,以及文本或卡片形式的权限审批。 +![扫码绑定与手动填写凭据](../images/im/dingding/dingding01.png) -实现入口:`adapters/dingtalk/index.ts` +授权成功后 `Client ID` 和 `Client Secret` 会自动填好并写入本机配置,adapter 随即用新凭据重连。 -## 1. 绑定钉钉机器人 +## 手动填写凭据 -打开桌面端 `设置 -> IM 接入 -> 钉钉`。 +扫码走不通时,也可以自己填。这几个字段就在扫码区域下面: -推荐使用扫码绑定: +- **Client ID** — 钉钉应用的 `appKey` +- **Client Secret** — 钉钉应用的 `appSecret` +- **Stream Endpoint** — 可选,默认 `https://api.dingtalk.com` +- **权限卡片模板 ID** — 可选,填了之后权限请求优先发钉钉互动卡片 +- **允许的用户** — 可选,直接放行指定的钉钉用户 ID -1. 点击「扫码绑定」。 -2. 使用钉钉手机 App 扫码。 -3. 在钉钉里确认创建并授权机器人。 -4. 等待桌面端显示「钉钉机器人已绑定」。 -5. 点击保存,或确认当前配置已经写入本地。 +填完点「保存」,adapter 会用新凭据重新建立 Stream 长连接。 -授权成功后,桌面端会把 `clientId` 和 `clientSecret` 写入 `~/.claude/adapters.json` 的 `dingtalk` 配置。发布版桌面端会重启 adapter sidecar,让新凭据立即生效。 +## 授权具体用户 -![钉钉机器人绑定和手动凭据配置](../images/im/dingding/dingding01.png) +机器人绑定完成后,还要让具体的人通过配对码获得授权。 -## 2. 手动填写凭据 +1. 回到页面顶部的「配对管理」,点「生成配对码」。 +2. 在钉钉里私聊机器人,把这枚 6 位码发过去。 +3. 看到配对成功提示后就能开始聊。 -如果扫码不可用,也可以手动填写: +![在钉钉里发送配对码并开始对话](../images/im/dingding/dingding02.png) -- `Client ID` — 钉钉应用的 `appKey` -- `Client Secret` — 钉钉应用的 `appSecret` -- `Stream Endpoint` — 可选,默认 `https://api.dingtalk.com` -- `权限卡片模板 ID` — 可选,配置后权限请求优先使用钉钉互动卡片 -- `Allowed Users` — 可选,直接放行指定钉钉用户 ID +配对码 60 分钟内有效、只能用一次。配对成功的人会出现在「已配对用户」列表里,随时可以解绑;被解绑的人要重新发一枚新码。 -保存后 adapter 会用 DingTalk Stream 建立长连接。 +## 选项目并开始对话 -## 3. 授权具体钉钉用户 +配了「默认项目」的话,第一条消息就直接在那个目录下开会话。没配的话,机器人会先列出最近用过的项目,回复编号、项目名或绝对路径即可。 -机器人绑定完成后,还需要让具体用户通过配对码授权: - -1. 在 `设置 -> IM 接入` 顶部的「配对管理」里点击「生成配对码」。 -2. 在钉钉机器人单聊里发送这枚 6 位配对码。 -3. 看到配对成功提示后,就可以开始聊天。 - -![钉钉里发送配对码并开始对话](../images/im/dingding/dingding02.png) - -配对码有效期 60 分钟,一次性使用。配对用户会出现在「已配对用户」列表里,可以随时解绑。解绑后该用户需要重新发送新的配对码才能使用。 - -## 4. 选择项目并开始对话 - -如果已经配置默认项目,发送任意消息会直接在该目录下创建或复用 Claude Code session。 - -如果没有配置默认项目,adapter 会返回最近项目列表。回复编号、项目名或绝对路径后,会新建会话并绑定到当前钉钉 chat。 - -后续消息会复用 `~/.claude/adapter-sessions.json` 里的 chat 到 session 映射。发送 `/new` 可以重新选择项目并开启新会话。 +之后同一个聊天窗口的消息会复用同一条会话。发 `/new` 重新选项目,发 `/clear` 清空上下文但保留项目绑定。 ## 支持的命令 -钉钉这里没有像飞书那样的机器人菜单配置流程;当前命令入口就是单聊输入框。配对成功后,bot 会提示发送 `/help`;用户也可以随时发送 `/help` 或 `帮助` 查看完整命令列表。 +钉钉没有像飞书那样的机器人菜单,命令入口就是单聊输入框。配对成功后机器人会提示发 `/help`。 - `/help` 或 `帮助` — 显示可用命令 -- `/status` 或 `状态` — 查看当前会话的项目、分支、模型、运行状态和任务摘要 -- `/projects` 或 `项目列表` — 重新显示最近项目列表 -- `/new` 或 `新会话` — 清空当前 chat 绑定的 session,并重新选择项目 -- `/new <编号、项目名或绝对路径>` — 直接在指定项目下新建会话 -- `/clear` 或 `清空` — 清空当前会话上下文,保留项目绑定 -- `/stop` 或 `停止` — 向当前 session 发送 `stop_generation` +- `/status` 或 `状态` — 当前项目、分支、模型、运行状态和任务摘要 +- `/projects` 或 `项目列表` — 重新列出最近项目 +- `/new` 或 `新会话` — 清空当前绑定并重新选择项目 +- `/new <编号、项目名或绝对路径>` — 直接在指定项目下开新会话 +- `/clear` 或 `清空` — 清空上下文,保留项目绑定 +- `/stop` 或 `停止` — 停止本轮生成 -## 权限审批 +## 权限审批与消息表现 -默认情况下,钉钉 adapter 会发送文本审批消息。回复: +默认情况下,权限请求以文本消息发出,回复其中一条: -- `/allow ` — 允许一次权限请求 -- `/always ` — 永久允许同类权限请求 -- `/deny ` — 拒绝一次权限请求 +- `/allow ` — 允许这一次 +- `/always ` — 永久允许同类请求 +- `/deny ` — 拒绝 -如果配置了已发布的钉钉互动卡片模板 ID,adapter 会优先发送权限卡片,并通过 DingTalk Stream 的 `/v1.0/card/instances/callback` 接收按钮回调。卡片发送失败或不可见时,仍可使用文本命令审批。 +如果在设置页填了已发布的「权限卡片模板 ID」,adapter 会优先发互动卡片,按钮回调走 DingTalk Stream 回传。卡片发不出去或看不见时,文本命令始终可用。 -## 返回消息的表现 +普通回复优先用 AI Card 做流式输出,图片附件会下载后以内联图片进入模型输入,附件超出大小限制时机器人会在钉钉里提示。 -- 使用 `dingtalk-stream` 接收机器人单聊消息。 -- 使用消息里的 `sessionWebhook` 回复 Markdown 文本。 -- 普通回复优先使用 AI Card 做流式输出。 -- 权限审批可以使用互动卡片;没有模板时自动回退文本命令。 -- 图片附件会下载后以内联图片形式进入模型输入。 -- 附件会走公共大小限制检查,超限时 adapter 会在钉钉里返回提示。 +## 解除绑定 -## 解绑 +两种粒度: -有两种解绑: - -- 解绑钉钉机器人账号:在钉钉标签页点击「解绑机器人账号」,会清空 `dingtalk.clientId`、`dingtalk.clientSecret`、`allowedUsers`、`pairedUsers` 和权限卡片模板 ID。 -- 解绑某个用户:在「已配对用户」列表里点击对应钉钉用户右侧的「解绑」,只移除该用户的 `pairedUsers` 记录。 - -解绑机器人账号后需要重新扫码或手动填写凭据。解绑用户后,该用户需要重新发送新的配对码才能继续使用。 +- 「解除机器人绑定」清掉钉钉凭据、权限卡片模板 ID 和这个平台的授权名单,之后要重新扫码或手动填凭据。 +- 在「已配对用户」列表里点某个用户右侧的「解绑」,只撤销那一个人。 ## 本地开发启动 -发布版桌面端会自动启动 adapter sidecar。只有本地开发或单独调试时才需要手动运行: +发布版桌面端会自动把 adapter 作为 sidecar 拉起。只有从源码运行或单独调试时才需要手动启动: ```bash cd adapters @@ -108,7 +91,7 @@ bun install bun run dingtalk ``` -可选环境变量: +可选的环境变量覆盖: ```bash export DINGTALK_CLIENT_ID="..." @@ -118,54 +101,22 @@ export DINGTALK_PERMISSION_CARD_TEMPLATE_ID="..." export ADAPTER_SERVER_URL="ws://127.0.0.1:3456" ``` -正常桌面端使用不需要手动设置这些环境变量,扫码绑定或手动填写会写入本地配置。 +正常使用桌面端不需要手动设置这些,扫码绑定或手动填写会写好本机配置。 ## 常见问题 -### 扫码成功后发消息仍提示未授权 +**扫码成功后发消息仍提示未授权**:这是正常流程。扫码只写入机器人凭据,具体用户还要发配对码,或被写进「允许的用户」。 -这是正常授权流程的一部分。扫码绑定只写入机器人凭据;具体钉钉用户还需要发送配对码,或被加入 `Allowed Users`。 +**扫码失败或一直等待确认**:重新点「扫码绑定」生成新二维码;仍然不行就手动填 `Client ID` 和 `Client Secret`。 -### 扫码失败或一直等待确认 +**adapter 启动时报缺少 clientId/clientSecret**:环境变量和 `~/.claude/adapters.json` 里的 `dingtalk.clientId`/`dingtalk.clientSecret` 都没生效,先在设置页完成扫码或手动填写。 -重新点击「扫码绑定」生成新二维码。如果仍失败,可以手动填写 `Client ID` 和 `Client Secret`。 +**权限卡片没出现**:确认模板 ID 填的是已发布的模板、模板支持 adapter 用的回调路由、机器人已发布新版本。卡片不可见时用 `/allow`、`/always`、`/deny` 照样能批。 -### adapter 启动时报缺少 clientId / clientSecret +**收不到回复**:依次确认桌面端在运行、Tab 里显示已绑定、当前用户已配对或在「允许的用户」里、`~/.claude/adapters.json` 能正常写入;源码运行时还要确认 `ADAPTER_SERVER_URL` 指向正在跑的桌面服务。 -说明 `DINGTALK_CLIENT_ID / DINGTALK_CLIENT_SECRET` 和 `~/.claude/adapters.json` 里的 `dingtalk.clientId / dingtalk.clientSecret` 都没有生效。先在桌面端钉钉标签页完成扫码绑定或手动填写凭据。 - -### 权限卡片没有出现 - -优先检查: - -- 是否已经配置发布后的权限卡片模板 ID。 -- 模板是否支持 adapter 使用的回调路由。 -- 当前机器人是否已经发布新版本。 - -即使卡片不可见,也可以用 `/allow `、`/always `、`/deny ` 完成审批。 - -### 收不到回复 - -优先检查: - -- 桌面端是否正在运行。 -- 钉钉标签页是否显示已绑定。 -- 当前用户是否已经配对或在 `Allowed Users` 中。 -- `~/.claude/adapters.json` 是否能正常写入。 -- 本地开发时 `ADAPTER_SERVER_URL` 是否指向正在运行的 Desktop server WebSocket 地址。 - -### 会话没恢复 - -检查 `~/.claude/adapter-sessions.json` 是否能正常写入,以及 Desktop server 里的 session 是否仍存在。 +**重启后会话没接回来**:检查 `~/.claude/adapter-sessions.json` 能否正常写入,以及桌面端里那条会话是否还在。 ## 源码入口 -- `adapters/dingtalk/index.ts` -- `adapters/dingtalk/helpers.ts` -- `adapters/dingtalk/ai-card.ts` -- `adapters/dingtalk/permission-card.ts` -- `adapters/dingtalk/media.ts` -- `adapters/common/pairing.ts` -- `adapters/common/session-store.ts` -- `adapters/common/ws-bridge.ts` -- `adapters/common/http-client.ts` +`adapters/dingtalk/` 下的 `index.ts`、`helpers.ts`、`ai-card.ts`、`permission-card.ts`、`media.ts`,以及 `adapters/common/` 下的 `pairing.ts`、`session-store.ts`、`ws-bridge.ts`、`http-client.ts`。 diff --git a/docs/im/feishu.md b/docs/im/feishu.md index cb447b92..807bc4d2 100644 --- a/docs/im/feishu.md +++ b/docs/im/feishu.md @@ -1,120 +1,102 @@ +--- +title: 飞书接入 +nav_title: 飞书 +description: 用官方模板一键创建飞书机器人,在飞书单聊里驱动桌面端并用卡片批权限。 +order: 1 +--- + # 飞书接入 -> 飞书 Adapter 的接入教程。官方已经提供了**预配好权限的模板机器人**,跟着下面几步点一点就能完成接入。 +适合国内团队:飞书官方给了一个权限预配好的模板机器人,点几下就能建出来,权限审批是可点的交互卡片,还能把常用命令做成机器人菜单。限制是只处理单聊(`p2p`),不处理群聊;改机器人配置要回开放平台发一次新版本。 -## 适用场景 +## 一键创建机器人 -飞书方案适合在中国区环境下通过企业自建应用私聊 Claude Code。当前实现只处理 `p2p` 私聊,不处理群聊。 +打开[创建飞书机器人](https://open.feishu.cn/page/openclaw?form=multiAgent)。这是官方为 OpenClaw 预配好消息、事件订阅、卡片回调等全部权限的模板,不需要再手动加 scope。桌面端「设置」→「IM 接入」→「飞书」里的「一键创建飞书机器人」按钮打开的也是这个页面。 -实现入口:`adapters/feishu/index.ts` +![模板创建入口](../images/im/feishu/01-create-app-entry.png) -## 1. 一键创建飞书机器人 +给机器人取个名字,点创建。 -直接打开下面的链接创建机器人——这是官方为 OpenClaw 提前配好所有权限(消息、事件、卡片回调等)的模板,省去手动配 scope 和事件订阅: +![给机器人取名](../images/im/feishu/02-name-bot.png) -👉 [立即创建飞书机器人](https://open.feishu.cn/page/openclaw?form=multiAgent) +创建成功后把**App ID**和**App Secret**留着,下一步要填进桌面端。 -![一键创建入口](../images/im/feishu/01-create-app-entry.png) +## 配置机器人菜单 -随便给你自己的机器人取一个名字,点击创建: +这一步可选,但配了之后在飞书里就能点按钮切项目、开新会话,不用手打命令。 -![取名并创建](../images/im/feishu/02-name-bot.png) +进入[飞书开放平台](https://open.feishu.cn/app?lang=zh-CN),选中刚创建的机器人。 -创建成功后,把 **App ID** 和 **App Secret** 保存下来,接着去配置机器人菜单。 +![开放平台里的机器人](../images/im/feishu/03-dev-console.png) -## 2. 配置自定义菜单(/projects /new /clear) +打开「机器人菜单」。 -进入[飞书开发者后台](https://open.feishu.cn/app?lang=zh-CN),选择刚创建的机器人,进入机器人配置页: +![进入机器人菜单](../images/im/feishu/04-menu-enter.png) -![开发者后台](../images/im/feishu/03-dev-console.png) +依次添加三个命令,每个都是一样的填法:菜单名称自定,命令填下面的值。 -进入「机器人菜单」开始配置: +- `/projects` — 列出最近项目并切换 +- `/new` — 开一条新会话 +- `/clear` — 清空当前上下文 -![进入菜单配置](../images/im/feishu/04-menu-enter.png) +![添加 menu 命令](../images/im/feishu/05-menu-projects.png) -依次添加 3 个命令: - -**/projects** — 切换最近使用的项目 - -![菜单 /projects](../images/im/feishu/05-menu-projects.png) - -**/new** — 开启新对话 - -![菜单 /new](../images/im/feishu/06-menu-new.png) - -**/clear** — 清空上下文 - -![菜单 /clear](../images/im/feishu/07-menu-clear.png) - -三个都配好后点击保存: +三个都加完后保存。 ![保存菜单](../images/im/feishu/08-menu-save.png) -最后点击「创建新版本并发布」让菜单生效: +菜单只有发布后才生效,点「创建新版本并发布」。 -![创建版本并发布](../images/im/feishu/09-publish-version.png) +![创建新版本并发布](../images/im/feishu/09-publish-version.png) -**命令作用说明:** +## 在桌面端填凭据 -- `/projects`:列出最近使用的项目,支持切换当前会话绑定的目录 -- `/new`:开启新对话 -- `/clear`:清空当前会话上下文 +1. 打开「设置」→「IM 接入」,切到「飞书」Tab。 +2. 把上一步的 App ID 和 App Secret 填进「App ID」和「App Secret」。 +3. 「Encrypt Key」和「Verification Token」留空即可,模板机器人不需要。 +4. 需要长时间流式更新同一张卡片时,勾上「流式卡片模式」。 +5. 点「保存」。 -## 3. 在 Claude Code Haha 桌面端填写 +![填写 App ID 和 App Secret](../images/im/feishu/10-fill-app-credentials.png) -### 3.1 填写 App ID / App Secret +「允许的用户」可以留空。留空时只有完成配对的人能用,这通常就是你想要的。 -打开桌面端 `设置 → IM 接入 → 飞书`,把前面拿到的两把钥匙填进去: +## 配对 -![填写 App ID / App Secret](../images/im/feishu/10-fill-app-credentials.png) - -### 3.2 生成配对码 - -点击「生成配对码」按钮,得到 6 位码: +回到页面顶部的「配对管理」,点「生成配对码」,会出现一枚 6 位码。这一步会立即写入本机配置,不需要再点保存。 ![生成配对码](../images/im/feishu/11-generate-pairing-code.png) -![配对码详情](../images/im/feishu/12-pairing-code-detail.png) +在飞书里私聊刚创建的机器人,随便发一条消息,按提示把这枚码发过去。 -**记得点保存!!** +![在飞书里发送配对码](../images/im/feishu/13-send-code-in-feishu.png) -## 4. 飞书机器人与桌面端配对 - -随便给刚才创建的机器人发送一条消息,按提示把上一步的 6 位配对码发给它: - -![在飞书里发配对码](../images/im/feishu/13-send-code-in-feishu.png) - -看到配对成功提示后,就可以用飞书在手机上远程驱动桌面端 Claude Code Haha 了: +看到配对成功提示,就可以直接对话了。 ![配对成功](../images/im/feishu/14-pair-success.png) -![可以开始对话](../images/im/feishu/15-pair-done.png) +配对码 60 分钟内有效、只能用一次,重新生成后旧码立刻作废。 ## 支持的命令 -除菜单按钮外,飞书 adapter 还支持文本命令和中文别名: +除菜单按钮外,聊天框里随时可以打: - `/help` 或 `帮助` - `/status` 或 `状态` -- `/clear` 或 `清空` - `/projects` 或 `项目列表` - `/new` 或 `新会话` +- `/clear` 或 `清空` - `/stop` 或 `停止` -## 权限审批 +## 权限审批与消息表现 -当 Claude 请求敏感权限时,adapter 会在飞书里发送交互卡片,点击「允许 / 拒绝」即可把结果回传给桌面端。 +Claude 请求敏感权限时,飞书里会收到一张交互卡片,点「允许」或「拒绝」,结果直接回传给桌面端会话。 -## 返回消息的表现 +普通回复走飞书的富文本消息,流式内容优先原地更新同一条消息,完成后过长的正文会自动分片发送。 -- 普通文本通过 `post` 消息发送 -- 权限审批通过卡片发送 -- 流式内容优先 patch 同一条消息 -- 完成后按 30000 字左右分片 +## 本地开发启动 -## 启动 adapter - -桌面端会自动把 adapter 作为 sidecar 拉起。如果你在本地开发,需要手动启动: +发布版桌面端会自动把 adapter 作为 sidecar 拉起。只有从源码运行或单独调试时才需要手动启动: ```bash cd adapters @@ -122,7 +104,7 @@ bun install bun run feishu ``` -## 环境变量覆盖(可选) +可选的环境变量覆盖: ```bash export FEISHU_APP_ID="cli_xxx" @@ -132,35 +114,14 @@ export ADAPTER_SERVER_URL="ws://127.0.0.1:3456" ## 常见问题 -### 一键创建后的机器人权限够用吗? +**收不到消息**:确认机器人已发布(改过菜单要重新「创建新版本并发布」),以及聊天窗口是和机器人的单聊而不是群。 -OpenClaw 官方模板已预配 `im:message`、`im:message:send_as_bot`、`im:resource`、`im.message.receive_v1`、`card.action.trigger` 等所有所需权限,**不需要再手动去配 scope 或事件订阅**。 +**权限卡片点了没反应**:一般是卡片回调能力没随版本发布,回开放平台重新发一次版本。 -### 收不到消息 +**一直提示未授权**:检查配对码是否还在 60 分钟有效期内、发的是不是当前这一枚(重新生成后旧码失效),以及这个飞书账号是否已经出现在桌面端的「已配对用户」列表里。 -优先检查: - -- 机器人是否已发布(菜单改完需要「创建新版本并发布」) -- 是否真的是和 bot 的私聊,而不是群聊 - -### 权限按钮点了没反应 - -通常是 `card.action.trigger` 没生效,重新在开发者后台发布一次版本即可。 - -### 一直提示未授权 - -- 配对码是否仍在 60 分钟有效期内 -- 发的是不是桌面端当前这一枚(重新生成后旧的立即失效) -- `feishu.pairedUsers` 里是否已经写入当前 `open_id` - -### 会话没恢复 - -检查 `~/.claude/adapter-sessions.json` 是否能正常写入,以及 Desktop server 里的 session 是否仍存在。 +**重启后会话没接回来**:检查 `~/.claude/adapter-sessions.json` 能否正常写入,以及桌面端里那条会话是否还在。 ## 源码入口 -- `adapters/feishu/index.ts` -- `adapters/common/pairing.ts` -- `adapters/common/session-store.ts` -- `adapters/common/ws-bridge.ts` -- `adapters/common/http-client.ts` +`adapters/feishu/index.ts`,以及 `adapters/common/` 下的 `pairing.ts`、`session-store.ts`、`ws-bridge.ts`、`http-client.ts`。 diff --git a/docs/im/index.md b/docs/im/index.md index 26d0d2e9..368e2291 100644 --- a/docs/im/index.md +++ b/docs/im/index.md @@ -1,180 +1,94 @@ +--- +title: IM 接入 +nav_title: 总览 +description: 把飞书、Telegram、微信、钉钉或 WhatsApp 的私聊接到桌面端,用手机继续同一条会话。 +order: 0 +--- + # IM 接入 -> 当前可用的 IM 接入方案总览。 -> 如果你只是想把微信、钉钉、WhatsApp、Telegram 或飞书接进来,从这篇开始。 +桌面端跑着的会话,可以接到你手机上的 IM 里。绑定之后,在飞书、Telegram、微信、钉钉或 WhatsApp 的私聊里发一句话,就是在驱动本机的 Claude Code:出门前让它跑一个长任务,路上用手机看进度、批权限、换项目。 -## 当前方案是什么 +对话对象是你自己绑定的机器人或账号,消息只经过你本机的桌面端,没有中间服务器托管你的代码。 -当前仓库里的 IM 接入,已经不是早期文档里设想的 “IM Gateway / MCP Channel 插件” 主路径。 +![设置里的 IM 接入页,顶部是配对管理,下面是五个平台 Tab](../images/app/settings-im.webp) -现在真实在跑的是一条更轻的链路: +## 接进来之后能做什么 -```mermaid -flowchart TD - A["Desktop Webapp
Settings -> IM 接入"] --> B["GET / PUT /api/adapters"] - B --> C["Desktop Server
配置接口"] - C --> D["本地配置持久化"] - D --> E["~/.claude/adapters.json"] +- **接着聊同一条会话**:手机上发的消息进的是本机的 Claude Code 会话,改文件、跑命令、读代码都在你电脑上真实发生。 +- **换项目**:发 `/projects` 列出最近用过的项目,回复编号或路径就切过去;`/new` 直接开一条新会话。 +- **批权限**:Claude 要写文件或执行高风险命令时,会把请求推到 IM 里。飞书和钉钉是可点的卡片,Telegram 是按钮,微信和 WhatsApp 回复一条文本命令。 +- **看状态、叫停**:`/status` 看当前项目、模型和运行状态,`/stop` 中断正在跑的这一轮。 - E --> F["微信 / 钉钉 / WhatsApp / Telegram / 飞书 Adapter 进程"] - F --> G["加载平台配置"] - F --> H["校验配对与授权"] - F --> I["读取 / 写入会话映射"] - I --> J["~/.claude/adapter-sessions.json"] +前提是桌面端一直开着。IM 侧只是遥控器,真正干活的是你的电脑。 - F --> K{"当前 chatId
是否已有 session?"} - K -->|有历史映射| L["复用已有 sessionId"] - K -->|无历史映射| M["解析工作目录
defaultProjectDir 或当前用户工作目录"] - M --> N["POST /api/sessions
创建新 session"] +## 选哪个平台 - N --> Q["获得 sessionId"] - L --> R["连接 /ws/:sessionId"] - Q --> R +五个平台的能力是同一套,差别在接入成本和审批体验。 - R --> S["adapters/common/ws-bridge.ts"] - S --> T["Desktop Server"] - T --> U["Claude Code session / CLI 子进程"] +| 平台 | 怎么接 | 适合谁 | 已知限制 | +|---|---|---|---| +| 飞书 | 点「一键创建飞书机器人」用官方模板建一个,回填 App ID 和 App Secret | 国内团队,想要点按钮就能批权限 | 只处理单聊,不处理群聊;改机器人菜单要去开放平台发版本 | +| Telegram | 找 `@BotFather` 要一个 Bot Token,粘进设置页 | 能连上 Telegram 的个人用户,接入最快 | 只处理私聊;国内网络下需要自己解决连通性 | +| 微信 | 在设置页扫码登录机器人账号 | 只想用微信、不愿再装一个 App | 只处理私聊;权限审批只能回复文本命令 | +| 钉钉 | 在设置页扫码授权,Client ID 和 Secret 自动写入 | 国内企业,钉钉是主力办公工具 | 只处理单聊;要卡片审批得额外配一个模板 ID | +| WhatsApp | 用手机 WhatsApp 的「已关联设备」扫码 | 海外用户 | 走个人号的 Web 登录,不是官方 Cloud API;只处理个人私聊 | - U --> V["流式消息 / 权限请求 / 状态事件"] - V --> S - S --> F - F --> W["微信 / 钉钉 / WhatsApp / Telegram / 飞书用户"] -``` +拿不定主意就先接 Telegram 或飞书,两者的权限审批体验最好。 -可以把这条链路理解成四层: +## 配对流程 -- 配置层:桌面端 webapp 负责填写平台凭据、默认项目和配对码管理 -- 存储层:本地服务端把配置写入 `~/.claude/adapters.json` -- 适配层:微信 / 钉钉 / WhatsApp / Telegram / 飞书 adapter 进程负责接 IM 平台、做授权检查、恢复或创建会话 -- 会话层:adapter 通过 HTTP 创建 session,再通过 WebSocket 把 IM 消息桥接到 Claude Code 会话 +五个平台的绑定分两层:先让桌面端拿到平台凭据,再让你这个 IM 账号本人通过配对码获得授权。第二层所有平台都一样。 -## 用户怎么用 +1. 打开「设置」→「IM 接入」。 +2. 在下方的平台 Tab 里完成绑定:飞书和 Telegram 填凭据,微信、钉钉、WhatsApp 扫码。 +3. 在「默认项目」里挑一个目录。 +4. 点「保存」。 +5. 回到顶部的「配对管理」,点「生成配对码」,拿到一枚 6 位码。 +6. 在对应 IM 里私聊你的机器人,把这 6 位码发过去。 +7. 看到配对成功提示后,直接发消息就是在跟 Claude Code 对话。 -### 1. 在 Desktop Webapp 里配置 +配对码 60 分钟内有效,只能用一次,重新生成后旧码立刻作废。同一枚码是平台无关的,发到哪个平台就绑哪个平台的账号。同一个用户 5 分钟内连续输错 5 次会被限流。 -打开桌面端设置页的 `IM 接入` 标签,填写: +生成配对码和扫码绑定都会立即写入本机配置,不需要再点「保存」;App ID、Bot Token、「允许的用户」和「默认项目」这类手填内容才需要。 -- `serverUrl` -- `defaultProjectDir` -- 微信 / 钉钉 / WhatsApp 扫码绑定,或填写 Telegram / 飞书各自的凭据 -- 可选 `allowedUsers` +配对成功的账号会出现在「已配对用户」列表里,点右侧的「解绑」即可撤销,被解绑的人要重新发一枚新码。 -这里的配置会通过 `GET /api/adapters` 和 `PUT /api/adapters` 读写到 `~/.claude/adapters.json`。 +## 默认项目决定它在哪干活 -### 2. 生成配对码 +「默认项目」是新建 IM 会话的工作目录。填了它,手机上发的第一句话就直接在这个目录下开会话;留空的话,机器人会先把最近用过的项目列出来让你选。 -配对也在同一个设置页完成: +同一个 IM 聊天窗口后续的消息会复用同一条会话,桌面端重启后也能接回去。想换目录发 `/new`,想清空上下文但保留项目发 `/clear`。 -- 点击“生成配对码” -- 前端生成 6 位码并写入 `pairing.code / expiresAt / createdAt` -- 码有效期 60 分钟 -- 配对成功后立即失效 +## 通用命令 -配对码是平台无关的,同一个码可以在微信、钉钉、WhatsApp、Telegram 或飞书私聊里使用一次。 +各平台的入口略有差异(飞书可以把命令配成机器人菜单),但这几条到处都能用: -微信、钉钉和 WhatsApp 的“扫码绑定”只负责把机器人或账号凭据写到本机;具体 IM 用户仍然需要发送配对码,或被加入 `allowedUsers`。 +- `/help` — 列出当前可用命令 +- `/status` — 当前项目、模型、运行状态 +- `/projects` — 列出最近项目并切换 +- `/new` — 开一条新会话,可带项目编号或路径 +- `/clear` — 清空上下文,保留项目绑定 +- `/stop` — 停止本轮生成 -### 3. 启动对应 Adapter 进程 +微信、钉钉、飞书还支持中文别名,例如 `帮助`、`状态`、`项目列表`、`新会话`、`清空`、`停止`。 -发布版桌面端会在本地 server 启动后自动拉起 adapter sidecar,并在保存凭据、扫码绑定或解绑后重启 adapter 让新配置生效。本地开发或单独调试 adapter 时可以手动启动: +## 安全须知 -```bash -cd adapters -bun install -bun run telegram -# 或 -bun run feishu -# 或 -bun run wechat -# 或 -bun run dingtalk -# 或 -bun run whatsapp -``` +::: warning 这是一把能改你电脑的遥控器 +配对成功的 IM 账号可以让 Claude 在你本机读写文件、执行命令。只把配对码发给你自己,别在群里贴,也别把机器人凭据提交进仓库。 +::: -### 4. 在 IM 里私聊 Bot +授权规则是「允许的用户」加已配对用户取并集,两者都为空时一律拒绝。扫码绑定只是让桌面端拿到账号凭据,不等于放行这个账号的所有联系人。 -- 未配对用户:先把配对码发给 bot -- 已配对用户:直接发送自然语言消息 -- 没有默认项目时:bot 会使用当前用户工作目录作为新会话目录 -- 后续消息会复用同一个 Claude session +平台凭据、配对状态和授权名单保存在 `~/.claude/adapters.json`,聊天窗口到会话的映射保存在 `~/.claude/adapter-sessions.json`。这两个文件都在本机,含有可直接操控你机器的凭据,不要外传。设置页读回配置时敏感字段会被打码。 -## 配置和状态分别存哪 +想在手机浏览器里获得完整界面而不只是聊天,看[H5 访问](../desktop/remote.md)。 -### `~/.claude/adapters.json` +## 分平台教程 -保存平台配置和授权状态,包括: - -- `serverUrl` -- `defaultProjectDir` -- `pairing` -- `telegram` -- `feishu` -- `wechat` -- `dingtalk` -- `whatsapp` - -其中: - -- 敏感字段会在 API 返回时被脱敏 -- 配对码也会被 `/api/adapters` 返回值掩码为 `******` - -### `~/.claude/adapter-sessions.json` - -保存 IM chat 到 Claude session 的映射: - -- `chatId` -- `sessionId` -- `workDir` -- `updatedAt` - -这让 bot 重启后仍然能接回原来的会话。 - -## 当前安全模型 - -授权规则是: - -- `allowedUsers` 和 `pairedUsers` 取并集 -- 两者都为空时,默认拒绝访问 -- 配对码为 6 位安全字符集 -- 配对码一次性使用 -- 同一用户 5 分钟内最多失败 5 次 - -这和旧 README 里“`allowedUsers` 为空就允许所有人”的说法已经不同,旧说法已过时。 - -## 会话行为 - -Adapter 不是直接把消息丢给一个全局 Claude 进程,而是: - -1. 先用 `POST /api/sessions` 创建 session -2. 再用 `ws://.../ws/:sessionId` 建立桥接 -3. 把 IM 消息转成: - - `user_message` - - `permission_response` - - `stop_generation` -4. 把服务端流式消息再格式化回 IM - -如果没有 `defaultProjectDir`,Adapter 会优先使用当前用户工作目录作为新 session 的工作目录,避免扫码绑定后还必须先在桌面端打开项目。 - -## 平台差异 - -- Telegram:`grammy`,按钮审批,纯私聊模式 -- 飞书:`@larksuiteoapi/node-sdk`,长连接事件订阅,交互卡片审批,当前只处理 `p2p` -- 微信:扫码绑定账号,`getupdates` 长轮询接收消息,文本命令审批使用 `/allow ` / `/deny ` -- 钉钉:扫码给本机写入 Stream 凭据,`dingtalk-stream` 长连接收发消息,配对码绑定用户后开始聊天 -- WhatsApp:`@whiskeysockets/baileys` 连接 WhatsApp Web,扫码写入本机 auth state,当前只处理个人私聊,文本命令审批 - -分别看: - -- [微信接入](./wechat.md) -- [钉钉接入](./dingtalk.md) -- [WhatsApp 接入](./whatsapp.md) -- [Telegram 接入](./telegram.md) -- [飞书接入](./feishu.md) - -## 和 `docs/channel/` 的关系 - -`docs/channel/` 主要是 Claude Code 原生 Channel/MCP 体系的源码研究资料,不是这个仓库当前推荐的 IM 接入方式。 - -如果你是要“把 bot 真跑起来”,看本目录。 -如果你是要研究 Claude Code 原始 Channel 设计,再去看 `docs/channel/`。 +- [飞书](./feishu.md) — 一键模板建机器人,卡片审批 +- [Telegram](./telegram.md) — BotFather 拿 Token,按钮审批 +- [微信](./wechat.md) — 扫码绑定账号,文本命令审批 +- [钉钉](./dingtalk.md) — 扫码授权,AI Card 流式输出 +- [WhatsApp](./whatsapp.md) — 个人号关联设备,文本命令审批 diff --git a/docs/im/telegram.md b/docs/im/telegram.md index f5fca031..24a80756 100644 --- a/docs/im/telegram.md +++ b/docs/im/telegram.md @@ -1,88 +1,74 @@ +--- +title: Telegram 接入 +nav_title: Telegram +description: 找 BotFather 要一个 Bot Token 填进桌面端,在 Telegram 私聊里点按钮批权限。 +order: 2 +--- + # Telegram 接入 -> Telegram Adapter 的接入教程。找 BotFather 拿 Token,桌面端填完配对即可。 +五个平台里接入最快的一个:找 `@BotFather` 要一个 Token,粘进桌面端就完事,权限审批是原生按钮。适合能连上 Telegram 的个人用户。限制是只处理私聊,不处理群组;国内网络下的连通性要你自己解决。 -## 适用场景 +## 创建机器人 -Telegram 方案适合个人私聊远程使用。当前实现只处理 `private chat`,不处理群聊。 - -实现入口:`adapters/telegram/index.ts` - -## 1. 创建 Telegram 机器人 - -在 Telegram 里搜索官方账号 **@BotFather**: +在 Telegram 里搜索官方账号 `@BotFather`。 ![搜索 BotFather](../images/im/telegram/01-search-botfather.png) -给它发送 `/newbot`: +给它发送 `/newbot`。 -![发送 /newbot](../images/im/telegram/02-newbot-command.png) +![发送 newbot 命令](../images/im/telegram/02-newbot-command.png) -按提示走完三步: +按提示走完两步: -- **取一个机器人名称**,例如 `ClaudeCodeHaha机器人` -- **取一个机器人用户名**,要求全英文字母,且必须以 `_bot` 结尾,例如 `jiang_cc_hah_bot` -- 创建成功后,复制 BotFather 返回的 **Bot Token** +1. 取一个机器人名称,例如 `ClaudeCodeHaha机器人`。 +2. 取一个用户名,全英文且必须以 `_bot` 结尾,例如 `jiang_cc_hah_bot`。 + +创建成功后复制 BotFather 返回的**Bot Token**。这枚 Token 等同于机器人的密码,别贴到公开的地方。 ![复制 Bot Token](../images/im/telegram/03-bot-token.png) -## 2. 在 Claude Code Haha 桌面端填写 +## 在桌面端填 Token -### 2.1 填写 Bot Token - -打开桌面端 `设置 → IM 接入 → Telegram`,把上一步的 Bot Token 填进去: +1. 打开「设置」→「IM 接入」,切到「Telegram」Tab。 +2. 把 Bot Token 粘进「Bot Token」。 +3. 点「保存」。 ![填写 Bot Token](../images/im/telegram/04-fill-bot-token.png) -### 2.2 生成配对码 +「允许的用户」可以留空。留空时只有完成配对的人能用。要直接放行已知账号,就填 Telegram 数字用户 ID,多个用逗号分隔。 -点击「生成配对码」按钮,拿到 6 位配对码后点击保存: +## 配对 + +回到页面顶部的「配对管理」,点「生成配对码」,拿到一枚 6 位码。这一步立即生效,不需要再点保存。 ![生成配对码](../images/im/telegram/05-generate-pairing-code.png) -## 3. 机器人与桌面端配对 - -随便给刚才创建的机器人发送一条消息,按提示输入配对码。看到下面的配对成功提示,就可以从手机 Telegram 远程驱动桌面端 Claude Code Haha 了: +在 Telegram 里私聊刚创建的机器人,随便发一条消息,按提示把这枚码发过去。看到配对成功提示就可以开始对话。 ![配对成功](../images/im/telegram/06-pair-success.png) +配对码 60 分钟内有效、只能用一次,重新生成后旧码立刻作废。连续输错会被限流,等几分钟再试。 + ## 支持的命令 - `/start` — 显示帮助和可用命令 -- `/projects` — 切换项目,重新显示最近项目列表 -- `/status` — 查看当前会话的项目、模型、运行状态和任务摘要 -- `/clear` — 清空当前会话上下文,保留项目绑定 -- `/new` — 清空当前 chat 绑定的 session,并重新选择项目 - `/help` — 显示当前可用命令 -- `/stop` — 向当前 session 发送 `stop_generation` +- `/projects` — 列出最近项目并切换 +- `/status` — 当前项目、模型、运行状态和任务摘要 +- `/new` — 清空当前绑定并重新选择项目 +- `/clear` — 清空上下文,保留项目绑定 +- `/stop` — 停止本轮生成 -## 权限审批 +## 权限审批与消息表现 -当 Claude 请求敏感权限时,Telegram adapter 会发带按钮的消息: +Claude 请求敏感权限时,Telegram 里会收到一条带按钮的消息,三个选项分别是允许一次、永久允许同类操作、拒绝。点完结果直接回传给桌面端会话。 -- `✅ 允许` -- `♾️ 永久允许` -- `❌ 拒绝` +回复走一层流式缓冲:思考阶段先发占位消息,正文逐步累积更新,完成后按 Telegram 的长度上限分片发送。 -点击后 adapter 会把结果通过 `permission_response` 回传给 Desktop server。 +## 本地开发启动 -## 返回消息的表现 - -Telegram 侧有一层流式缓冲: - -- thinking 时先发占位消息 -- text delta 逐步累积 -- 完成时按 4000 字分片发送 - -对应公共模块: - -- `adapters/common/message-buffer.ts` -- `adapters/common/format.ts` -- `adapters/common/ws-bridge.ts` - -## 启动 adapter - -桌面端会自动把 adapter 作为 sidecar 拉起。如果你在本地开发,需要手动启动: +发布版桌面端会自动把 adapter 作为 sidecar 拉起。只有从源码运行或单独调试时才需要手动启动: ```bash cd adapters @@ -90,7 +76,7 @@ bun install bun run telegram ``` -## 环境变量覆盖(可选) +可选的环境变量覆盖: ```bash export TELEGRAM_BOT_TOKEN="123456:ABC-DEF..." @@ -99,29 +85,14 @@ export ADAPTER_SERVER_URL="ws://127.0.0.1:3456" ## 常见问题 -### bot 启动时报缺少 token +**adapter 启动时报缺少 Token**:`TELEGRAM_BOT_TOKEN` 和 `~/.claude/adapters.json` 里的 `telegram.botToken` 都没生效,回设置页把 Token 填好并保存。 -说明 `TELEGRAM_BOT_TOKEN` 和 `~/.claude/adapters.json` 里的 `telegram.botToken` 都没有生效。 +**设置页能打开但机器人没反应**:源码运行时 webapp 只负责写配置,不会自动拉起 `bun run telegram`;发布版桌面端才会通过 sidecar 自动启动。 -### 能打开设置页但 bot 不工作 +**发消息提示未授权**:检查是否已生成配对码、码是否还在 60 分钟有效期内、是否发到了正确的机器人私聊里。 -Webapp 只负责配置,不会自动拉起 `bun run telegram`(桌面端发布版会通过 sidecar 自动拉起)。 - -### 发消息提示未授权 - -- 是否已经在桌面端生成配对码 -- 配对码是否在 60 分钟有效期内 -- 是否把码发到了正确的 bot 私聊 -- 连续输错会有速率限制,等几分钟再试 - -### 每次重启后会话丢失 - -检查 `~/.claude/adapter-sessions.json` 是否能正常写入,以及 Desktop server 的 session 是否仍存在。 +**重启后会话没接回来**:检查 `~/.claude/adapter-sessions.json` 能否正常写入,以及桌面端里那条会话是否还在。 ## 源码入口 -- `adapters/telegram/index.ts` -- `adapters/common/pairing.ts` -- `adapters/common/session-store.ts` -- `adapters/common/ws-bridge.ts` -- `adapters/common/http-client.ts` +`adapters/telegram/index.ts`,以及 `adapters/common/` 下的 `pairing.ts`、`session-store.ts`、`ws-bridge.ts`、`message-buffer.ts`、`format.ts`。 diff --git a/docs/im/wechat.md b/docs/im/wechat.md index 2dabe031..e67502c5 100644 --- a/docs/im/wechat.md +++ b/docs/im/wechat.md @@ -1,103 +1,81 @@ +--- +title: 微信接入 +nav_title: 微信 +description: 在设置页扫码登录微信机器人账号,再用配对码授权具体微信用户。 +order: 3 +--- + # 微信接入 -> 微信 Adapter 的接入教程。桌面端扫码绑定机器人账号后,再用配对码授权具体微信用户。 +适合只想用微信、不愿意再装一个 App 的人:整个接入就是扫一次码,不需要去任何开放平台申请。代价是权限审批只能靠回复文本命令,没有可点的按钮;而且只处理私聊,不面向群聊设计。 -## 适用场景 +除文本外,微信这条链路还支持语音转出的文字、图片和文件附件。 -微信方案适合个人私聊远程使用。当前实现支持文本聊天、语音转文字内容、图片和文件附件、项目选择、状态查看、停止生成和权限审批。 +## 扫码绑定机器人账号 -当前只按私聊用户维度授权和保存会话,不面向微信群聊设计。 +1. 打开「设置」→「IM 接入」,切到「微信」Tab。 +2. 点「扫码绑定」。 +3. 用微信扫描页面上的二维码,并在手机上确认登录。 +4. 等页面状态变成「微信已绑定」。 -实现入口:`adapters/wechat/index.ts` +![未绑定时的扫码入口](../images/im/wechat/wechat01.png) -## 1. 扫码绑定微信机器人账号 +绑定成功后凭据会自动写入本机配置并重启 adapter,不需要再点「保存」。这时 Tab 里会出现「重新扫码」和「解除微信绑定」两个按钮。 -打开桌面端 `设置 -> IM 接入 -> 微信`。 +![绑定成功后的状态](../images/im/wechat/wechat03.png) -在微信标签页里: +扫码绑定的是机器人账号本身,不等于放行所有微信联系人。谁能用,还要看下一步。 -1. 点击「扫码绑定」。 -2. 使用微信扫描页面里的二维码。 -3. 在微信里确认登录。 -4. 等待页面显示绑定成功。 -5. 点击保存,或确认当前配置已经写入本地。 +## 授权具体用户 -![微信未绑定时的扫码入口](../images/im/wechat/wechat01.png) +1. 回到页面顶部的「配对管理」,点「生成配对码」。 +2. 在微信里把这枚 6 位码发给刚绑定的机器人。 +3. 看到配对成功提示后就能直接发消息。 -扫码成功后,桌面端会把微信网关返回的 `accountId`、`botToken`、`baseUrl` 和 `userId` 写入 `~/.claude/adapters.json` 的 `wechat` 配置。发布版桌面端会重启 adapter sidecar,让新凭据立即生效。 +![在微信里发送配对码并开始对话](../images/im/wechat/wechat02.jpg) -绑定成功后,微信标签页会显示「微信已绑定」,并提供「重新扫码」和「解除微信绑定」: +配对码 60 分钟内有效、只能用一次,重新生成后旧码立刻作废。 -![微信绑定成功状态](../images/im/wechat/wechat03.png) +也可以把已知的微信用户 ID 直接填进「允许的用户」,多个用逗号分隔。这适合固定白名单,但不如配对码直观。 -注意:扫码绑定的是机器人账号凭据,不等于把所有微信用户都授权了。用户访问仍然走 `allowedUsers + pairedUsers` 授权模型。 +## 选项目并开始对话 -## 2. 授权具体微信用户 +配了「默认项目」的话,第一条消息就直接在那个目录下开会话。没配的话,机器人会先列出最近用过的项目,回复编号、项目名或绝对路径即可。 -推荐走配对码: - -1. 在 `设置 -> IM 接入` 顶部的「配对管理」里点击「生成配对码」。 -2. 把 6 位配对码发给微信里的机器人。 -3. 看到配对成功提示后,就可以直接发送自然语言消息。 - -![微信里发送配对码并开始对话](../images/im/wechat/wechat02.jpg) - -配对码有效期 60 分钟,一次性使用。重新生成配对码后,旧码会失效。 - -也可以把已知微信用户 ID 手动写进微信标签页的 `Allowed Users`。这种方式适合固定账号白名单,但一般没有配对码直观。 - -## 3. 选择项目并开始对话 - -如果已经配置默认项目,发送任意消息会直接在该目录下创建或复用 Claude Code session。 - -如果没有配置默认项目,微信 adapter 会返回最近项目列表。回复编号、项目名或绝对路径后,会新建会话并绑定到当前微信用户。 - -后续消息会复用 `~/.claude/adapter-sessions.json` 里的 chat 到 session 映射。发送 `/new` 可以重新选择项目并开启新会话。 +之后同一个聊天窗口的消息会复用同一条会话,桌面端重启也能接回去。发 `/new` 重新选项目,发 `/clear` 清空上下文但保留项目绑定。 ## 支持的命令 -微信没有可配置菜单,命令入口就是聊天框。配对成功后,bot 会提示发送 `/help`;后续随时发 `/help` 或 `帮助` 都能看到当前支持的命令。 +微信没有可配置的菜单,命令入口就是聊天框。配对成功后机器人会提示发 `/help`。 - `/help` 或 `帮助` — 显示可用命令 -- `/status` 或 `状态` — 查看当前会话的项目、分支、模型、运行状态和任务摘要 -- `/projects` 或 `项目列表` — 重新显示最近项目列表 -- `/new` 或 `新会话` — 清空当前 chat 绑定的 session,并重新选择项目 -- `/new <编号、项目名或绝对路径>` — 直接在指定项目下新建会话 -- `/clear` 或 `清空` — 清空当前会话上下文,保留项目绑定 -- `/stop` 或 `停止` — 向当前 session 发送 `stop_generation` +- `/status` 或 `状态` — 当前项目、分支、模型、运行状态和任务摘要 +- `/projects` 或 `项目列表` — 重新列出最近项目 +- `/new` 或 `新会话` — 清空当前绑定并重新选择项目 +- `/new <编号、项目名或绝对路径>` — 直接在指定项目下开新会话 +- `/clear` 或 `清空` — 清空上下文,保留项目绑定 +- `/stop` 或 `停止` — 停止本轮生成 -## 权限审批 +## 权限审批与消息表现 -当 Claude 请求敏感权限时,微信 adapter 会发送一条文本审批消息,里面包含 request id。 +Claude 请求敏感权限时,微信里会收到一条带 request id 的文本消息。回复其中一条: -在微信里回复: +- `/allow ` — 允许这一次 +- `/always ` — 永久允许同类请求 +- `/deny ` — 拒绝 -- `/allow ` — 允许一次权限请求 -- `/always ` — 永久允许同类权限请求 -- `/deny ` — 拒绝一次权限请求 +回复走长轮询接收,正文按微信的长度上限分片发送,思考和工具执行期间会尽量显示「正在输入」。图片会以内联图片进入模型输入,其他文件会以本机临时路径传给会话,超出大小限制时机器人会在微信里提示。 -审批结果会通过 WebSocket 桥接回桌面端 session。 +## 解除绑定 -## 返回消息的表现 +两种粒度: -- 使用 `getupdates` 长轮询接收微信消息。 -- 文本回复按 3500 字左右分片发送。 -- thinking 和工具执行期间会尽量发送微信 typing 状态。 -- 图片会以内联图片形式进入模型输入;非图片文件会以本地临时文件路径传给会话。 -- 附件会走公共大小限制检查,超限时 adapter 会在微信里返回提示。 - -## 解绑 - -有两种解绑: - -- 解绑微信机器人账号:在微信标签页点击「解绑微信账号」,会清空 `wechat.accountId`、`wechat.botToken`、`wechat.userId`、`allowedUsers` 和 `pairedUsers`。 -- 解绑某个用户:在「已配对用户」列表里点击对应微信用户右侧的「解绑」,只移除该用户的 `pairedUsers` 记录。 - -解绑机器人账号后需要重新扫码绑定。解绑用户后,该用户需要重新发送新的配对码才能继续使用。 +- 「解除微信绑定」清掉整个机器人账号凭据和这个平台的授权名单,之后要重新扫码。 +- 在「已配对用户」列表里点某个用户右侧的「解绑」,只撤销那一个人,其他人不受影响。被解绑的人要重新发一枚新配对码。 ## 本地开发启动 -发布版桌面端会自动启动 adapter sidecar。只有本地开发或单独调试时才需要手动运行: +发布版桌面端会自动把 adapter 作为 sidecar 拉起。只有从源码运行或单独调试时才需要手动启动: ```bash cd adapters @@ -105,7 +83,7 @@ bun install bun run wechat ``` -可选环境变量: +可选的环境变量覆盖: ```bash export WECHAT_ACCOUNT_ID="..." @@ -115,42 +93,20 @@ export WECHAT_USER_ID="..." export ADAPTER_SERVER_URL="ws://127.0.0.1:3456" ``` -正常桌面端使用不需要手动设置这些环境变量,扫码绑定会写入本地配置。 +正常使用桌面端不需要手动设置这些,扫码绑定会写好本机配置。 ## 常见问题 -### 扫码成功后发消息仍提示未授权 +**扫码成功后发消息仍提示未授权**:这是正常流程。扫码只写入机器人账号凭据,具体用户还要发配对码,或被写进「允许的用户」。 -这是正常授权流程的一部分。扫码绑定只写入机器人账号凭据;具体微信用户还需要发送配对码,或被加入 `Allowed Users`。 +**二维码提示过期**:重新点「扫码绑定」生成新的,微信的扫码登录状态只在短时间内有效。 -### 页面显示二维码过期 +**adapter 启动时报缺少微信账号**:`WECHAT_ACCOUNT_ID`/`WECHAT_BOT_TOKEN` 和 `~/.claude/adapters.json` 里的对应字段都没生效,先在设置页完成扫码绑定。 -重新点击「扫码绑定」生成新二维码。微信二维码登录状态只在短时间内有效。 +**收不到回复**:依次确认桌面端在运行、Tab 里显示「微信已绑定」、当前用户已配对或在「允许的用户」里、`~/.claude/adapters.json` 能正常写入;源码运行时还要确认 `ADAPTER_SERVER_URL` 指向正在跑的桌面服务。 -### adapter 启动时报缺少微信账号 - -说明 `WECHAT_ACCOUNT_ID / WECHAT_BOT_TOKEN` 和 `~/.claude/adapters.json` 里的 `wechat.accountId / wechat.botToken` 都没有生效。先在桌面端微信标签页完成扫码绑定。 - -### 收不到回复 - -优先检查: - -- 桌面端是否正在运行。 -- 微信标签页是否显示已绑定。 -- 当前用户是否已经配对或在 `Allowed Users` 中。 -- `~/.claude/adapters.json` 是否能正常写入。 -- 本地开发时 `ADAPTER_SERVER_URL` 是否指向正在运行的 Desktop server WebSocket 地址。 - -### 会话没恢复 - -检查 `~/.claude/adapter-sessions.json` 是否能正常写入,以及 Desktop server 里的 session 是否仍存在。 +**重启后会话没接回来**:检查 `~/.claude/adapter-sessions.json` 能否正常写入,以及桌面端里那条会话是否还在。 ## 源码入口 -- `adapters/wechat/index.ts` -- `adapters/wechat/protocol.ts` -- `adapters/wechat/media.ts` -- `adapters/common/pairing.ts` -- `adapters/common/session-store.ts` -- `adapters/common/ws-bridge.ts` -- `adapters/common/http-client.ts` +`adapters/wechat/` 下的 `index.ts`、`protocol.ts`、`media.ts`,以及 `adapters/common/` 下的 `pairing.ts`、`session-store.ts`、`ws-bridge.ts`、`http-client.ts`。 diff --git a/docs/im/whatsapp.md b/docs/im/whatsapp.md index c3149760..c1a293ba 100644 --- a/docs/im/whatsapp.md +++ b/docs/im/whatsapp.md @@ -1,87 +1,80 @@ +--- +title: WhatsApp 接入 +nav_title: WhatsApp +description: 用手机 WhatsApp 的「已关联设备」扫码,把桌面端挂成一台已登录的 Web 设备。 +order: 5 +--- + # WhatsApp 接入 -> WhatsApp Adapter 使用 WhatsApp Web linked device 方式接入个人号。实现基于 `@whiskeysockets/baileys`。 +适合海外用户:不需要 Meta 开发者账号,扫一次码就能用自己的 WhatsApp 个人号远程驱动桌面端。代价是它走的是 WhatsApp Web 的关联设备登录,不是官方 Cloud API,所以没有官方 SLA、模板消息和群发能力;只处理个人私聊,不处理群组、频道和状态。权限审批靠回复文本。 -## 适用场景 +## 它不是「创建一个机器人」 -WhatsApp 方案适合用自己的 WhatsApp 个人号在私聊里远程驱动 Claude Code Haha。当前实现只处理个人私聊,不处理群聊、频道、状态广播。 +这条链路的本质,是把桌面端挂成你 WhatsApp 账号下的一台已登录 Web 设备。因此不需要在 Meta for Developers 建 App,不需要 WhatsApp Business Account,也不需要 Phone Number ID、Access Token、Webhook 回调地址和消息模板审核。 -实现入口:`adapters/whatsapp/index.ts` +对面的人看到的对话对象,就是你扫码绑定的那个 WhatsApp 账号本身,不是一个另外创建的 bot。 -## 它不是“创建机器人” +如果之后要做客服、模板消息、官方 SLA 或规模化群发,那是另一套官方 Cloud API 方案,需要单独实现。参考[Linked Devices 帮助](https://faq.whatsapp.com/1317564962315842/)和[WhatsApp Cloud API 概览](https://developers.facebook.com/docs/whatsapp/cloud-api/overview)。 -当前实现走的是 WhatsApp Web 的 linked device 方式,不是官方 WhatsApp Business Platform / Cloud API。 +## 扫码绑定 -这意味着: +1. 打开「设置」→「IM 接入」,切到「WhatsApp」Tab。 +2. 点「扫码绑定」。 +3. 在手机 WhatsApp 里打开「设置」→「已关联设备」。 +4. 扫描桌面端显示的二维码。 +5. 等页面状态变成「WhatsApp 已绑定」。 -- 不需要在 Meta for Developers 创建 App -- 不需要 WhatsApp Business Account(WABA) -- 不需要 Phone Number ID、WABA ID、Access Token -- 不需要配置 Webhook callback URL -- 不需要申请或审核 message template - -它的工作方式更接近“把 Claude Code Haha 作为一台已登录的 WhatsApp Web 设备挂到你的个人 WhatsApp 账号上”: - -1. 桌面端用 Baileys 生成 WhatsApp Web 登录二维码 -2. 你用手机 WhatsApp 的 `Linked devices` 扫码 -3. 本机保存 WhatsApp Web auth state -4. adapter 监听这个账号收到的个人私聊消息 -5. 已授权用户发来的消息会被转成 Claude Code Haha session 输入 -6. Claude 的回复再由这个 WhatsApp 账号发回同一个私聊 - -因此,WhatsApp 这里没有 Telegram BotFather、飞书机器人、钉钉 Stream 机器人那种“后台创建 bot”的概念。对用户来说,对话对象就是你扫码绑定的那个 WhatsApp 账号本身。 - -如果后续要做面向客户服务、模板消息、官方 SLA、Webhook、规模化群发或合规商业账号接入,那是另一套官方 Cloud API 方案,需要单独实现,至少会涉及 Meta App、WABA、业务手机号、永久 access token、Webhook 和模板消息。参考官方资料: - -- [WhatsApp Linked Devices 帮助](https://faq.whatsapp.com/1317564962315842/) -- [Meta WhatsApp Cloud API Overview](https://developers.facebook.com/docs/whatsapp/cloud-api/overview) -- [Meta WhatsApp Cloud API Get Started](https://developers.facebook.com/docs/whatsapp/cloud-api/get-started) - -## 1. 在桌面端扫码绑定 - -打开桌面端 `设置 -> IM 接入 -> WhatsApp`: - -1. 点击「扫码绑定」 -2. 在手机 WhatsApp 打开 `Settings -> Linked devices` -3. 扫描桌面端显示的二维码 -4. 等待页面提示绑定成功 - -绑定成功后,桌面端会把 Baileys auth state 保存到本机,并重启 adapter sidecar。 - -默认 auth 目录: +绑定成功后登录状态会保存到本机并自动重启 adapter,不需要再点「保存」。默认目录: ```text ~/.claude/whatsapp-auth/default ``` -配置记录写在: +这个目录等同于一份可直接收发消息的登录凭据,不要外传、不要提交进仓库。 -```json -{ - "whatsapp": { - "accountJid": "15551234567@s.whatsapp.net", - "authDir": "~/.claude/whatsapp-auth/default", - "allowedUsers": [], - "pairedUsers": [] - } -} -``` +## 授权具体用户 -## 2. 生成配对码 +扫码只是把账号挂上来,不等于放行所有联系人。 -扫码只绑定 WhatsApp Web 账号能力,不等于授权所有聊天用户。 +1. 回到页面顶部的「配对管理」,点「生成配对码」。 +2. 用需要授权的那个 WhatsApp 账号,私聊你刚绑定的账号,把这枚 6 位码发过去。 +3. 配对成功后这个 JID 会被记进本机授权名单。 -在 `IM 接入` 页点击「生成配对码」,然后用需要授权的 WhatsApp 私聊给当前账号发送该配对码。配对成功后,这个 WhatsApp JID 会写入 `whatsapp.pairedUsers`。 - -也可以直接在 `Allowed Users` 里填写允许的 WhatsApp JID,例如: +也可以直接把已知 JID 填进「允许的用户」,格式是国家码加手机号,例如: ```text 15551234567@s.whatsapp.net ``` -## 3. 启动 adapter +配对码 60 分钟内有效、只能用一次,重新生成后旧码立刻作废。 -发布版桌面端会自动拉起 adapter sidecar。本地开发或单独调试时: +## 支持的命令 + +- `/start` 或 `/help` — 显示帮助和可用命令 +- `/projects` — 列出最近项目并切换 +- `/status` — 当前项目、模型和运行状态 +- `/new [项目]` — 开新会话或切换项目 +- `/clear` — 清空上下文,保留项目绑定 +- `/stop` — 停止本轮生成 + +## 权限审批与消息表现 + +WhatsApp 没有可点的按钮,权限请求按消息里的提示回复: + +- `1` 或 `/allow ` — 允许这一次 +- `2` 或 `/always ` — 永久允许同类请求 +- `3` 或 `/deny ` — 拒绝 + +回复方式上,adapter 不靠反复编辑同一条消息做逐字流式:思考阶段发一条简短状态提示,完成后把正文按长度上限分片发送;Agent 输出里的 markdown 图片引用会被识别成图片消息发出。 + +## 解除绑定 + +在 WhatsApp Tab 点「解除 WhatsApp 绑定」,会删掉本机保存的登录状态,之后要重新扫码。只想撤销某一个人,就在「已配对用户」列表里点那个人右侧的「解绑」,账号本身的关联不受影响。 + +## 本地开发启动 + +发布版桌面端会自动把 adapter 作为 sidecar 拉起。只有从源码运行或单独调试时才需要手动启动: ```bash cd adapters @@ -89,32 +82,7 @@ bun install bun run whatsapp ``` -## 支持的命令 - -- `/start` 或 `/help` — 显示帮助和可用命令 -- `/projects` — 切换项目,重新显示最近项目列表 -- `/status` — 查看当前会话的项目、模型和运行状态 -- `/clear` — 清空当前会话上下文,保留项目绑定 -- `/new [项目]` — 新建会话或切换项目 -- `/stop` — 向当前 session 发送 `stop_generation` - -## 权限审批 - -WhatsApp 没有使用按钮审批。收到权限请求后,按消息提示回复: - -- `1` 或 `/allow ` — 允许一次 -- `2` 或 `/always ` — 永久允许 -- `3` 或 `/deny ` — 拒绝 - -## 返回消息的表现 - -WhatsApp adapter 不依赖 WhatsApp message edit 做 token 级流式更新。它会: - -- thinking 时发送一个简短状态提示 -- 完成后把正文按约 4000 字分片发送 -- 识别 Agent 输出里的 markdown 图片引用并作为图片消息发送 - -## 环境变量覆盖(可选) +可选的环境变量覆盖: ```bash export WHATSAPP_AUTH_DIR="$HOME/.claude/whatsapp-auth/default" @@ -124,24 +92,12 @@ export ADAPTER_SERVER_URL="ws://127.0.0.1:3456" ## 常见问题 -### adapter 启动时报没有绑定账号 +**adapter 启动时报没有绑定账号**:先在桌面端完成扫码,`bun run whatsapp` 本身不会弹二维码。 -先在桌面端 WhatsApp 标签页扫码绑定。`bun run whatsapp` 不会单独弹出二维码。 +**绑定后发消息提示未授权**:扫码绑的是账号,不是用户授权。还要生成配对码,并用要授权的那个 WhatsApp 私聊发过来。 -### 绑定后发消息提示未授权 - -扫码绑定的是 WhatsApp Web 账号,不是用户授权。还需要在桌面端生成配对码,并用 WhatsApp 私聊发送给当前账号。 - -### WhatsApp 提示已登出 - -在桌面端 WhatsApp 标签页解除绑定后重新扫码。解除绑定会删除本机 WhatsApp auth state。 +**WhatsApp 提示已登出**:在设置页解除绑定后重新扫码。手机端在「已关联设备」里移除这台设备也会导致登出。 ## 源码入口 -- `adapters/whatsapp/index.ts` -- `adapters/whatsapp/protocol.ts` -- `adapters/whatsapp/session.ts` -- `adapters/whatsapp/media.ts` -- `adapters/common/pairing.ts` -- `adapters/common/session-store.ts` -- `adapters/common/ws-bridge.ts` +`adapters/whatsapp/` 下的 `index.ts`、`protocol.ts`、`session.ts`、`media.ts`,以及 `adapters/common/` 下的 `pairing.ts`、`session-store.ts`、`ws-bridge.ts`。 diff --git a/docs/images/app/agent-create.webp b/docs/images/app/agent-create.webp new file mode 100644 index 00000000..fb9d6353 Binary files /dev/null and b/docs/images/app/agent-create.webp differ diff --git a/docs/images/app/composer-mention.webp b/docs/images/app/composer-mention.webp new file mode 100644 index 00000000..c4126030 Binary files /dev/null and b/docs/images/app/composer-mention.webp differ diff --git a/docs/images/app/composer-slash.webp b/docs/images/app/composer-slash.webp new file mode 100644 index 00000000..dfcea60f Binary files /dev/null and b/docs/images/app/composer-slash.webp differ diff --git a/docs/images/app/h5-session.webp b/docs/images/app/h5-session.webp new file mode 100644 index 00000000..01e336f9 Binary files /dev/null and b/docs/images/app/h5-session.webp differ diff --git a/docs/images/app/h5-sessions.webp b/docs/images/app/h5-sessions.webp new file mode 100644 index 00000000..eae52aca Binary files /dev/null and b/docs/images/app/h5-sessions.webp differ diff --git a/docs/images/app/permission-modes.webp b/docs/images/app/permission-modes.webp new file mode 100644 index 00000000..54bf092e Binary files /dev/null and b/docs/images/app/permission-modes.webp differ diff --git a/docs/images/app/pet-desktop.webp b/docs/images/app/pet-desktop.webp new file mode 100644 index 00000000..938d73b8 Binary files /dev/null and b/docs/images/app/pet-desktop.webp differ diff --git a/docs/images/app/schedule-create.webp b/docs/images/app/schedule-create.webp new file mode 100644 index 00000000..70be4645 Binary files /dev/null and b/docs/images/app/schedule-create.webp differ diff --git a/docs/images/app/session-dark.webp b/docs/images/app/session-dark.webp new file mode 100644 index 00000000..ed3d29d7 Binary files /dev/null and b/docs/images/app/session-dark.webp differ diff --git a/docs/images/app/session-main.webp b/docs/images/app/session-main.webp new file mode 100644 index 00000000..80fe9152 Binary files /dev/null and b/docs/images/app/session-main.webp differ diff --git a/docs/images/app/session-new.webp b/docs/images/app/session-new.webp new file mode 100644 index 00000000..d197aa10 Binary files /dev/null and b/docs/images/app/session-new.webp differ diff --git a/docs/images/app/session-permission.webp b/docs/images/app/session-permission.webp new file mode 100644 index 00000000..0bc36410 Binary files /dev/null and b/docs/images/app/session-permission.webp differ diff --git a/docs/images/app/settings-agents.webp b/docs/images/app/settings-agents.webp new file mode 100644 index 00000000..56d6208d Binary files /dev/null and b/docs/images/app/settings-agents.webp differ diff --git a/docs/images/app/settings-computer-use.webp b/docs/images/app/settings-computer-use.webp new file mode 100644 index 00000000..5a5de494 Binary files /dev/null and b/docs/images/app/settings-computer-use.webp differ diff --git a/docs/images/app/settings-general.webp b/docs/images/app/settings-general.webp new file mode 100644 index 00000000..db34e396 Binary files /dev/null and b/docs/images/app/settings-general.webp differ diff --git a/docs/images/app/settings-h5.webp b/docs/images/app/settings-h5.webp new file mode 100644 index 00000000..31ef2e6e Binary files /dev/null and b/docs/images/app/settings-h5.webp differ diff --git a/docs/images/app/settings-im.webp b/docs/images/app/settings-im.webp new file mode 100644 index 00000000..c2b39225 Binary files /dev/null and b/docs/images/app/settings-im.webp differ diff --git a/docs/images/app/settings-pets.webp b/docs/images/app/settings-pets.webp new file mode 100644 index 00000000..164fe05d Binary files /dev/null and b/docs/images/app/settings-pets.webp differ diff --git a/docs/images/app/settings-provider-add.webp b/docs/images/app/settings-provider-add.webp new file mode 100644 index 00000000..c5265523 Binary files /dev/null and b/docs/images/app/settings-provider-add.webp differ diff --git a/docs/images/app/settings-usage.webp b/docs/images/app/settings-usage.webp new file mode 100644 index 00000000..58cadf63 Binary files /dev/null and b/docs/images/app/settings-usage.webp differ diff --git a/docs/images/app/skill-market.webp b/docs/images/app/skill-market.webp new file mode 100644 index 00000000..613e845b Binary files /dev/null and b/docs/images/app/skill-market.webp differ diff --git a/docs/images/app/workspace-changes.webp b/docs/images/app/workspace-changes.webp new file mode 100644 index 00000000..d1ae348b Binary files /dev/null and b/docs/images/app/workspace-changes.webp differ diff --git a/docs/images/app/workspace-diff.webp b/docs/images/app/workspace-diff.webp new file mode 100644 index 00000000..960e14db Binary files /dev/null and b/docs/images/app/workspace-diff.webp differ diff --git a/docs/images/app/workspace-preview.webp b/docs/images/app/workspace-preview.webp new file mode 100644 index 00000000..129a0030 Binary files /dev/null and b/docs/images/app/workspace-preview.webp differ diff --git a/docs/images/desktop_ui/01_full_ui.png b/docs/images/desktop_ui/01_full_ui.png deleted file mode 100644 index 6083716b..00000000 Binary files a/docs/images/desktop_ui/01_full_ui.png and /dev/null differ diff --git a/docs/images/desktop_ui/02_edit_code.png b/docs/images/desktop_ui/02_edit_code.png deleted file mode 100644 index 6b4b9c5c..00000000 Binary files a/docs/images/desktop_ui/02_edit_code.png and /dev/null differ diff --git a/docs/images/desktop_ui/03_ask_question_and_permission.png b/docs/images/desktop_ui/03_ask_question_and_permission.png deleted file mode 100644 index c27b3867..00000000 Binary files a/docs/images/desktop_ui/03_ask_question_and_permission.png and /dev/null differ diff --git a/docs/images/desktop_ui/04_tasktodo_list.png b/docs/images/desktop_ui/04_tasktodo_list.png deleted file mode 100644 index b2a55ba3..00000000 Binary files a/docs/images/desktop_ui/04_tasktodo_list.png and /dev/null differ diff --git a/docs/images/desktop_ui/05_settings.png b/docs/images/desktop_ui/05_settings.png deleted file mode 100644 index 3186b8e7..00000000 Binary files a/docs/images/desktop_ui/05_settings.png and /dev/null differ diff --git a/docs/images/desktop_ui/06_settings_computer_use.png b/docs/images/desktop_ui/06_settings_computer_use.png deleted file mode 100644 index c3f9bff1..00000000 Binary files a/docs/images/desktop_ui/06_settings_computer_use.png and /dev/null differ diff --git a/docs/images/desktop_ui/07_im.png b/docs/images/desktop_ui/07_im.png deleted file mode 100644 index c91bfe9d..00000000 Binary files a/docs/images/desktop_ui/07_im.png and /dev/null differ diff --git a/docs/images/desktop_ui/08_scheduled_task.png b/docs/images/desktop_ui/08_scheduled_task.png deleted file mode 100644 index c9376f40..00000000 Binary files a/docs/images/desktop_ui/08_scheduled_task.png and /dev/null differ diff --git a/docs/images/desktop_ui/09_file_search.png b/docs/images/desktop_ui/09_file_search.png deleted file mode 100644 index c9927b10..00000000 Binary files a/docs/images/desktop_ui/09_file_search.png and /dev/null differ diff --git a/docs/images/desktop_ui/09_slash_command.png b/docs/images/desktop_ui/09_slash_command.png deleted file mode 100644 index 8cf38246..00000000 Binary files a/docs/images/desktop_ui/09_slash_command.png and /dev/null differ diff --git a/docs/images/desktop_ui/10_desktop_workspace.png b/docs/images/desktop_ui/10_desktop_workspace.png deleted file mode 100644 index 72159790..00000000 Binary files a/docs/images/desktop_ui/10_desktop_workspace.png and /dev/null differ diff --git a/docs/images/desktop_ui/11_token_usage.png b/docs/images/desktop_ui/11_token_usage.png deleted file mode 100644 index 68737ffb..00000000 Binary files a/docs/images/desktop_ui/11_token_usage.png and /dev/null differ diff --git a/docs/images/desktop_ui/12_h5_access.png b/docs/images/desktop_ui/12_h5_access.png deleted file mode 100644 index 97466aff..00000000 Binary files a/docs/images/desktop_ui/12_h5_access.png and /dev/null differ diff --git a/docs/images/desktop_ui/13_workspace_changes_worktree.png b/docs/images/desktop_ui/13_workspace_changes_worktree.png deleted file mode 100644 index 9ff45fb4..00000000 Binary files a/docs/images/desktop_ui/13_workspace_changes_worktree.png and /dev/null differ diff --git a/docs/images/desktop_ui/14_pet_settings_overview.png b/docs/images/desktop_ui/14_pet_settings_overview.png deleted file mode 100644 index 988c5198..00000000 Binary files a/docs/images/desktop_ui/14_pet_settings_overview.png and /dev/null differ diff --git a/docs/images/desktop_ui/15_pet_create_methods.png b/docs/images/desktop_ui/15_pet_create_methods.png deleted file mode 100644 index ab4ec543..00000000 Binary files a/docs/images/desktop_ui/15_pet_create_methods.png and /dev/null differ diff --git a/docs/images/desktop_ui/16_pet_custom_result.png b/docs/images/desktop_ui/16_pet_custom_result.png deleted file mode 100644 index 35412139..00000000 Binary files a/docs/images/desktop_ui/16_pet_custom_result.png and /dev/null differ diff --git a/docs/images/desktop_ui/17_agent_create.png b/docs/images/desktop_ui/17_agent_create.png deleted file mode 100644 index d14b9c86..00000000 Binary files a/docs/images/desktop_ui/17_agent_create.png and /dev/null differ diff --git a/docs/images/desktop_ui/17_pet_action_sheet_en.png b/docs/images/desktop_ui/17_pet_action_sheet_en.png deleted file mode 100644 index 9f7daa8b..00000000 Binary files a/docs/images/desktop_ui/17_pet_action_sheet_en.png and /dev/null differ diff --git a/docs/images/desktop_ui/17_pet_action_sheet_zh.png b/docs/images/desktop_ui/17_pet_action_sheet_zh.png deleted file mode 100644 index a68a0833..00000000 Binary files a/docs/images/desktop_ui/17_pet_action_sheet_zh.png and /dev/null differ diff --git a/docs/images/desktop_ui/18_permission_modes.png b/docs/images/desktop_ui/18_permission_modes.png deleted file mode 100644 index c6b36cb1..00000000 Binary files a/docs/images/desktop_ui/18_permission_modes.png and /dev/null differ diff --git a/docs/images/desktop_ui/20_scheduled_task.png b/docs/images/desktop_ui/20_scheduled_task.png deleted file mode 100644 index 50f7c4b4..00000000 Binary files a/docs/images/desktop_ui/20_scheduled_task.png and /dev/null differ diff --git a/docs/images/desktop_ui/21_skill_marketplace.png b/docs/images/desktop_ui/21_skill_marketplace.png deleted file mode 100644 index cb4f7ef0..00000000 Binary files a/docs/images/desktop_ui/21_skill_marketplace.png and /dev/null differ diff --git a/docs/images/desktop_ui/22_workspace_changed_files.png b/docs/images/desktop_ui/22_workspace_changed_files.png deleted file mode 100644 index f732b71a..00000000 Binary files a/docs/images/desktop_ui/22_workspace_changed_files.png and /dev/null differ diff --git a/docs/images/desktop_ui/23_workspace_diff_review.png b/docs/images/desktop_ui/23_workspace_diff_review.png deleted file mode 100644 index 65a295d3..00000000 Binary files a/docs/images/desktop_ui/23_workspace_diff_review.png and /dev/null differ diff --git a/docs/images/desktop_ui/24_browser_preview.png b/docs/images/desktop_ui/24_browser_preview.png deleted file mode 100644 index 11995e16..00000000 Binary files a/docs/images/desktop_ui/24_browser_preview.png and /dev/null differ diff --git a/docs/images/desktop_ui/25_main_session.png b/docs/images/desktop_ui/25_main_session.png deleted file mode 100644 index 71cae246..00000000 Binary files a/docs/images/desktop_ui/25_main_session.png and /dev/null differ diff --git a/docs/images/mascots/bubu.png b/docs/images/mascots/bubu.png new file mode 100644 index 00000000..e835150d Binary files /dev/null and b/docs/images/mascots/bubu.png differ diff --git a/docs/images/mascots/dada.png b/docs/images/mascots/dada.png new file mode 100644 index 00000000..d4c21f11 Binary files /dev/null and b/docs/images/mascots/dada.png differ diff --git a/docs/images/mascots/huhu.png b/docs/images/mascots/huhu.png new file mode 100644 index 00000000..4f060b07 Binary files /dev/null and b/docs/images/mascots/huhu.png differ diff --git a/docs/images/mascots/huihui.png b/docs/images/mascots/huihui.png new file mode 100644 index 00000000..de1b2a83 Binary files /dev/null and b/docs/images/mascots/huihui.png differ diff --git a/docs/agent/03-agent-framework.md b/docs/internals/agent-framework.md similarity index 88% rename from docs/agent/03-agent-framework.md rename to docs/internals/agent-framework.md index cd0d2ec1..20f4c0fc 100644 --- a/docs/agent/03-agent-framework.md +++ b/docs/internals/agent-framework.md @@ -1,31 +1,30 @@ -# Claude Code Agent 框架深度解析 +--- +title: Agent 框架深度解析 +nav_title: Agent 框架 +description: 从源码视角拆解核心 Agent 循环、提示词工程、工具系统与上下文压缩。 +order: 6 +--- -> 从源码视角剖析全球最流行 AI Code Editor 背后的 Agent 架构设计哲学。 +# Agent 框架深度解析 -

-核心循环 · 提示词工程 · 工具系统 · 上下文管理 · 技能与插件 · 权限与安全 · 故障恢复 · 对比分析 · 成功之道 -

+从源码视角剖析全球最流行 AI Code Editor 背后的 Agent 架构设计哲学。 ![Agent 框架架构总览](./images/11-agent-framework-overview.png) ---- +## 这套框架要解决什么 -## 导读:一个根本性的问题 - -如果你仔细观察 Claude Code 的行为,会发现一些非常有趣的现象: +观察 Claude Code 的行为,有几件事值得解释: - 它能在一次对话中修改几十个文件,且极少出错 - 它能自动恢复各种边界情况(token 溢出、API 超时、工具失败) -- 它能同时管理多个子代理协作完成复杂任务 +- 它能同时管理多个子 Agent 协作完成复杂任务 - 长对话不会退化,反而能越来越精准 -这些能力的背后,是一套精心设计的 Agent 框架。本文从源码层面,完整解构这套框架的设计哲学。 +这些能力都不是模型自带的,而是框架设计出来的。下面按源码顺序拆开看。 ---- +## 核心 Agent 循环 -## 一、核心 Agent 循环 - -### 1.1 不是 ReAct,而是 Async Generator 状态机 +### 不是 ReAct,而是 Async Generator 状态机 大多数 Agent 框架(包括 LangChain)采用经典的 **ReAct** 模式: @@ -42,7 +41,7 @@ export async function* query(params: QueryParams): AsyncGenerator<...> 这个函数是整个 Agent 的心脏。它不是简单的"想-做-看"循环,而是一个**流式状态机**,通过 `yield` 实时产出消息,通过状态赋值(而非递归调用)驱动循环。 -### 1.2 状态结构 +### 状态结构 ```typescript // src/query.ts:204-217 @@ -60,7 +59,7 @@ type State = { } ``` -### 1.3 核心循环的五个阶段 +### 核心循环的五个阶段 整个 `while (true)` 循环(`src/query.ts:307-1728`)分为五个阶段: @@ -140,11 +139,9 @@ state = next - **状态可追溯**:每一轮的状态转换原因都被记录 - **恢复可控**:任何阶段的错误都可以通过修改 state 来恢复 ---- +## 系统提示词工程 -## 二、系统提示词工程 - -### 2.1 分层构建架构 +### 分层构建架构 系统提示词不是一个静态字符串,而是通过**分层管道**动态组装的(`src/constants/prompts.ts:444-577`): @@ -171,7 +168,7 @@ state = next 这意味着 Claude Code 的系统提示词**不需要每次都重新处理**——静态部分在全球范围内共享缓存,大幅降低延迟和成本。 -### 2.2 两种 Section 类型 +### 两种 Section 类型 ```typescript // src/constants/systemPromptSections.ts @@ -187,7 +184,7 @@ DANGEROUS_uncachedSystemPromptSection('mcp_instructions', async () => { }, 'MCP servers can connect/disconnect mid-session') ``` -### 2.3 CLAUDE.md 的加载机制 +### CLAUDE.md 的加载机制 CLAUDE.md 是用户自定义指令系统,按**优先级从低到高**加载(`src/utils/claudemd.ts`): @@ -205,7 +202,7 @@ CLAUDE.md 是用户自定义指令系统,按**优先级从低到高**加载( 支持 `@path` 语法递归引用其他文件,并自动防止循环引用。 -### 2.4 系统提示词的优先级解析 +### 系统提示词的优先级解析 最终的系统提示词通过 `buildEffectiveSystemPrompt()`(`src/utils/systemPrompt.ts:41-123`)按优先级决定: @@ -216,11 +213,9 @@ CLAUDE.md 是用户自定义指令系统,按**优先级从低到高**加载( 5. **默认提示词** — 标准系统提示词 6. **Append 提示词** — 始终追加到末尾 ---- +## 工具系统设计 -## 三、工具系统设计 - -### 3.1 工具接口:不只是函数调用 +### 工具接口:不只是函数调用 Claude Code 的工具不是简单的"名称 + 参数 + 执行"。每个工具是一个**完整的生命周期管理单元**(`src/Tool.ts:362-695`): @@ -258,7 +253,7 @@ type Tool = { 这种设计使得每个工具都是**自描述、自验证、自渲染**的——框架不需要了解工具的内部逻辑,只需调用标准接口。 -### 3.2 工具注册:三阶段流水线 +### 工具注册:三阶段流水线 工具的发现和注册分三个阶段(`src/tools.ts`): @@ -278,7 +273,7 @@ type Tool = { 排序(缓存稳定性) ``` -### 3.3 工具执行管道 +### 工具执行管道 一次工具调用要经过**7 步管道**(`src/services/tools/toolExecution.ts`): @@ -290,7 +285,7 @@ type Tool = { 每一步都可以**中断、修改或增强**执行流程。这不是简单的 `try { tool.call(input) } catch`,而是一个完整的中间件管道。 -### 3.4 工具延迟加载(Tool Deferred Loading) +### 工具延迟加载(Tool Deferred Loading) Claude Code 有 48+ 个内置工具。如果每次 API 调用都把所有工具定义发给模型,会浪费大量 token。解决方案: @@ -305,11 +300,9 @@ Claude Code 有 48+ 个内置工具。如果每次 API 调用都把所有工具 模型需要时通过 `ToolSearch` 工具动态获取完整定义。这大幅减少了系统提示词的大小。 ---- +## 上下文管理与压缩 -## 四、上下文管理与压缩 - -### 4.1 无限对话的秘密 +### 无限对话的秘密 Claude Code 宣称"对话没有上下文限制",这背后是一套**四级压缩系统**: @@ -331,7 +324,7 @@ Claude Code 宣称"对话没有上下文限制",这背后是一套**四级压 当所有局部优化都不够时,通过 Claude 自身生成一个完整的对话摘要,替换所有历史消息。 -### 4.2 系统上下文注入 +### 系统上下文注入 每次 API 调用前,自动注入两种上下文(`src/context.ts`): @@ -350,7 +343,7 @@ getUserContext() → { } ``` -### 4.3 系统提醒(System Reminders) +### 系统提醒(System Reminders) 系统提醒是一种特殊的**附件消息**,注入到工具结果或用户消息中(`src/utils/attachments.ts`): @@ -366,11 +359,9 @@ getUserContext() → { - 用户侧问的附带信息 - Deferred 工具的可用通知 ---- +## 技能与插件生态 -## 五、技能与插件生态 - -### 5.1 技能系统(Skills) +### 技能系统(Skills) 技能是 Claude Code 最强大的扩展机制之一。它不是简单的"命令别名",而是**完整的 AI 行为定义**。 @@ -395,7 +386,7 @@ type BundledSkillDefinition = { | 上下文 | 行为 | 适用场景 | |--------|------|----------| | `inline` | 技能内容直接展开到当前对话 | 简单指令、格式模板 | -| `fork` | 技能作为子代理在独立上下文中运行 | 复杂工作流、需要独立 token 预算 | +| `fork` | 技能作为子 Agent 在独立上下文中运行 | 复杂工作流、需要独立 token 预算 | #### 技能发现来源 @@ -411,7 +402,7 @@ type BundledSkillDefinition = { 策略技能(policy) ← 组织管理 ``` -### 5.2 插件系统(Plugins) +### 插件系统(Plugins) 插件是更高层级的扩展单元,可以包含**技能、钩子、MCP 服务器、LSP 服务器**: @@ -430,7 +421,7 @@ type BuiltinPluginDefinition = { 插件的关键设计:**用户可切换启用/禁用**,这与直接注册的技能不同。 -### 5.3 钩子系统(Hooks) +### 钩子系统(Hooks) 钩子是整个生命周期的**可编程拦截点**: @@ -449,7 +440,7 @@ SessionStart ─→ UserPromptSubmit ─→ PreToolUse ─→ [工具执行] - **2**:stderr 内容展示给模型或用户 - **其他**:仅展示给用户 -### 5.4 MCP:模型上下文协议 +### MCP:模型上下文协议 MCP 是 Claude Code 与外部世界交互的标准协议。工具命名规范: @@ -462,11 +453,9 @@ mcp__{标准化服务器名}__{工具名} MCP 工具在运行时动态发现,与内置工具**无缝合并**到统一的工具池中。 ---- +## 权限与安全体系 -## 六、权限与安全体系 - -### 6.1 分层权限模型 +### 分层权限模型 ``` ┌─────────────────────────────────────┐ @@ -486,7 +475,7 @@ MCP 工具在运行时动态发现,与内置工具**无缝合并**到统一的 └─────────────────────────────────────┘ ``` -### 6.2 权限决策流 +### 权限决策流 每次工具调用的权限检查: @@ -504,7 +493,7 @@ type PermissionResult = - `type: 'hook'` — 钩子拦截 - `type: 'classifier'` — ML 分类器判定 -### 6.3 权限规则模式匹配 +### 权限规则模式匹配 ```javascript // 精确匹配 @@ -518,9 +507,7 @@ type PermissionResult = { tool: 'File*', behavior: 'allow' } // 允许所有 File 开头的工具 ``` ---- - -## 七、故障恢复机制 +## 故障恢复机制 这是 Claude Code 最精妙的设计之一。`src/query.ts` 的核心循环内置了**6 种恢复策略**: @@ -545,25 +532,23 @@ if (error.type === 'prompt_too_long') { } ``` -### 7.1 模型降级 +### 模型降级 当主模型流式传输失败时,系统会: 1. 清理孤立的未完成消息 2. 切换到备用模型 3. 用新模型重试 -### 7.2 媒体大小恢复 +### 媒体大小恢复 当图片等媒体内容导致 token 超限时: - 触发反应式压缩 - 自动剥离图片内容 - 保留文本信息重试 ---- +## 与 LangChain/ReAct 的本质区别 -## 八、与 LangChain/ReAct 的本质区别 - -### 8.1 架构范式对比 +### 架构范式对比 | 维度 | LangChain | Claude Code | |------|-----------|-------------| @@ -577,7 +562,7 @@ if (error.type === 'prompt_too_long') { | **扩展机制** | Python 类继承 | 技能 + 插件 + 钩子 + MCP | | **缓存策略** | 无 | 全局/会话/按轮三级缓存 | -### 8.2 为什么不用 ReAct? +### 为什么不用 ReAct? ReAct 模式有几个固有限制: @@ -593,7 +578,7 @@ Claude Code 的 Async Generator 模式解决了所有这些问题: - **缓存优化**:静态提示词全局缓存,动态部分最小化 - **并行能力**:只读工具自动并行,写入工具串行保序 -### 8.3 与 LangChain Agent 的具体差异 +### 与 LangChain Agent 的具体差异 ``` LangChain Agent: @@ -616,7 +601,7 @@ Claude Code Agent: - LangChain 需要 OutputParser 解析模型输出中的工具调用 - Claude Code 直接使用 Anthropic API 的原生 `tool_use` 能力,无需解析 -### 8.4 与 LangGraph 的对比 +### 与 LangGraph 的对比 LangGraph 是 LangChain 的升级版,引入了图结构: @@ -630,13 +615,11 @@ LangGraph 是 LangChain 的升级版,引入了图结构: Claude Code 的优势在于**简单性**——不需要定义图结构,一个 while 循环就能处理所有情况。 ---- - -## 九、为什么 Claude Code 能做到这么好? +## 为什么 Claude Code 能做到这么好? 从源码分析中,我们可以总结出以下核心设计原则: -### 9.1 流式优先(Streaming First) +### 流式优先(Streaming First) 整个架构围绕 `AsyncGenerator` 设计,一切都是流式的: - 模型响应是流式的 @@ -646,7 +629,7 @@ Claude Code 的优势在于**简单性**——不需要定义图结构,一个 这意味着用户**永远不需要等待**——看到模型在思考、工具在执行、结果在产出。 -### 9.2 智能缓存(Intelligent Caching) +### 智能缓存(Intelligent Caching) 三级提示词缓存系统(`src/services/api/claude.ts:3213-3237`): @@ -660,7 +643,7 @@ Section Cache(轮级) ← systemPromptSection 记忆化 这大幅降低了每次 API 调用的延迟和成本。 -### 9.3 优雅降级(Graceful Degradation) +### 优雅降级(Graceful Degradation) 6 种恢复策略确保 Claude Code **几乎不会因为技术问题中断用户的工作流**: - Token 超限?自动压缩 @@ -668,7 +651,7 @@ Section Cache(轮级) ← systemPromptSection 记忆化 - 模型失败?降级到备用模型 - 工具失败?记录错误,继续对话 -### 9.4 最小抽象原则(Minimal Abstraction) +### 最小抽象原则(Minimal Abstraction) 与 LangChain 的"万物皆抽象"不同,Claude Code 的核心只有: - **一个循环**(`while (true)` in `query()`) @@ -677,7 +660,7 @@ Section Cache(轮级) ← systemPromptSection 记忆化 没有 Agent → AgentExecutor → Chain → Memory → Callback 的嵌套抽象层。这使得代码**易于理解、调试和扩展**。 -### 9.5 原生 API 集成(Native API Integration) +### 原生 API 集成(Native API Integration) Claude Code 直接使用 Anthropic API 的原生能力: - **原生工具调用**:无需 OutputParser,直接使用 `tool_use` 块 @@ -687,18 +670,18 @@ Claude Code 直接使用 Anthropic API 的原生能力: 这避免了"框架税"——LangChain 等框架在 LLM 和开发者之间增加的抽象层。 -### 9.6 工具驱动的 Agent(Tool-Driven Agent) +### 工具驱动的 Agent(Tool-Driven Agent) Claude Code 的哲学是:**Agent 的能力等于其工具的能力**。 -- 子代理生成?是一个工具(`AgentTool`) +- 子 Agent 生成?是一个工具(`AgentTool`) - 团队管理?是一个工具(`TeamCreate`/`SendMessage`) - 文件编辑?是一个工具(`FileEdit`) - 技能执行?是一个工具(`SkillTool`) 这意味着**所有能力都通过统一的工具接口暴露**,模型通过自然语言推理来决定使用哪个工具。不需要显式的编排逻辑——模型本身就是编排器。 -### 9.7 深度集成的开发体验 +### 深度集成的开发体验 Claude Code 不是"通用 Agent + 代码插件",而是**从底层为编码场景深度优化**: @@ -708,11 +691,7 @@ Claude Code 不是"通用 Agent + 代码插件",而是**从底层为编码场 - **LSP 集成**:语言服务器协议提供类型信息和诊断 - **MCP 生态**:通过标准协议连接各种外部工具 ---- - -## 十、架构总结 - -### 核心组件关系 +## 核心组件关系 ``` 用户输入 @@ -742,30 +721,24 @@ query() 异步生成器循环(src/query.ts) └─ Teammate(邮箱通信) ``` -### 一句话总结 - -> **Claude Code 的 Agent 框架是一个以 AsyncGenerator 为核心的流式状态机,通过统一的工具接口暴露所有能力,配合四级上下文压缩、三级提示词缓存、六种故障恢复策略,实现了一个无需显式编排即可自主完成复杂编程任务的 AI 系统。** - ---- - -## 十一、关键源文件索引 +## 关键源文件索引 | 组件 | 文件路径 | 说明 | |------|----------|------| | 核心循环 | `src/query.ts` | Agent 主循环(~1730 行) | -| 查询引擎 | `src/QueryEngine.ts` | 高层封装(~687 行) | +| 查询引擎 | `src/QueryEngine.ts` | 高层封装 | | 工具定义 | `src/Tool.ts` | Tool 类型系统(~792 行) | | 工具注册 | `src/tools.ts` | 工具发现和注册(~389 行) | -| 工具执行 | `src/services/tools/toolExecution.ts` | 执行管道(~1500 行) | +| 工具执行 | `src/services/tools/toolExecution.ts` | 执行管道 | | 工具编排 | `src/services/tools/toolOrchestration.ts` | 并行/串行策略 | -| 系统提示词 | `src/constants/prompts.ts` | 提示词组装(~577 行) | +| 系统提示词 | `src/constants/prompts.ts` | 提示词组装 | | 提示词 Sections | `src/constants/systemPromptSections.ts` | 分段缓存 | | 上下文管理 | `src/context.ts` | 系统/用户上下文 | | CLAUDE.md | `src/utils/claudemd.ts` | 用户指令加载 | | 记忆系统 | `src/memdir/memdir.ts` | 持久化记忆 | | Agent 生成 | `src/tools/AgentTool/AgentTool.tsx` | Agent 工具入口 | | Agent 运行 | `src/tools/AgentTool/runAgent.ts` | Agent 执行逻辑 | -| Fork 代理 | `src/tools/AgentTool/forkSubagent.ts` | Fork 缓存优化 | +| Fork Agent | `src/tools/AgentTool/forkSubagent.ts` | Fork 缓存优化 | | 团队管理 | `src/utils/swarm/teamHelpers.ts` | Teams 基础设施 | | 邮箱通信 | `src/utils/teammateMailbox.ts` | 异步消息队列 | | 技能系统 | `src/skills/bundledSkills.ts` | 技能注册与管理 | @@ -780,11 +753,9 @@ query() 异步生成器循环(src/query.ts) | 远程会话 | `src/remote/RemoteSessionManager.ts` | CCR 连接管理 | | Bridge | `src/bridge/bridgeMain.ts` | 远程桥接 | ---- +## 进一步阅读 -## 十二、进一步阅读 - -- [使用指南](./01-usage-guide.md) — 面向用户的多 Agent 使用手册 -- [实现原理](./02-implementation.md) — 多 Agent 编排的技术细节 +- [使用指南](./agent.md) — 面向用户的多 Agent 使用手册 +- [实现原理](./agent-internals.md) — 多 Agent 编排的技术细节 - [Anthropic API 文档](https://docs.anthropic.com/) — 原生 API 能力 - [MCP 协议规范](https://modelcontextprotocol.io/) — 模型上下文协议 diff --git a/docs/agent/02-implementation.md b/docs/internals/agent-internals.md similarity index 93% rename from docs/agent/02-implementation.md rename to docs/internals/agent-internals.md index 19c9137f..00cd3e30 100644 --- a/docs/agent/02-implementation.md +++ b/docs/internals/agent-internals.md @@ -1,20 +1,21 @@ -# Claude Code 多 Agent 系统 — 实现原理 +--- +title: 多 Agent 实现原理 +nav_title: 多 Agent 实现 +description: 多 Agent 的生成路径、工具池过滤、上下文传递与后台任务引擎。 +order: 5 +--- -> 深入剖析多 Agent 编排的架构设计、生成流程、上下文传递和协作机制。 +# 多 Agent 实现原理 -

-架构总览 · 生成流程 · 工具池系统 · 上下文传递 · Teams 内部机制 · 后台任务引擎 · DreamTask · Worktree 隔离 · 权限同步 · 生命周期数据流 · 源文件索引 · Feature Flags -

+深入剖析多 Agent 编排的架构设计、生成流程、上下文传递和协作机制。 ![实现架构总览](./images/05-architecture.png) ---- - -## 一、架构总览 +## 架构总览 Claude Code 的多 Agent 系统由以下核心模块组成: -### 5 大核心模块 +### 大核心模块 | 模块 | 职责 | 关键文件 | |------|------|----------| @@ -24,7 +25,7 @@ Claude Code 的多 Agent 系统由以下核心模块组成: | **任务系统** | 状态追踪,进度更新,通知队列 | `src/tasks/LocalAgentTask/` | | **Swarm 基础设施** | 团队管理,邮箱通信,权限同步 | `src/utils/swarm/` | -### 5 大 Agent 类别 +### 大 Agent 类别 ``` ┌─────────────────────────────────────────────────┐ @@ -46,9 +47,7 @@ Claude Code 的多 Agent 系统由以下核心模块组成: └───────────┘ ``` ---- - -## 二、Agent 生成流程 — 四条路径 +## Agent 生成流程 — 四条路径 ### 入口:`AgentTool.call()` @@ -156,7 +155,7 @@ buildForkedMessages(directive, assistantMessage) └─ 结果:字节级一致的 API 前缀 → prompt cache 命中! ``` -**Fork 子代理的行为约束**(通过 `FORK_BOILERPLATE_TAG` 注入): +**Fork 子 Agent 的行为约束**(通过 `FORK_BOILERPLATE_TAG` 注入): ``` 1. 你是分叉的工作进程,不是主代理 @@ -196,9 +195,7 @@ runAgent(promptMessages, toolUseContext, options) └─ totalTokens ``` ---- - -## 三、工具池系统 — 三层过滤 +## 工具池系统 — 三层过滤 ![工具池系统](./images/07-tool-pool.png) @@ -208,11 +205,11 @@ runAgent(promptMessages, toolUseContext, options) | 工具 | 禁止原因 | |------|----------| -| TaskOutput | 仅主代理可读取任务输出 | -| ExitPlanMode | 仅主代理可退出计划模式 | -| EnterPlanMode | 仅主代理可进入计划模式 | -| AskUserQuestion | 子代理不应直接问用户 | -| TaskStop | 仅主代理可终止任务 | +| TaskOutput | 仅主 Agent 可读取任务输出 | +| ExitPlanMode | 仅主 Agent 可退出计划模式 | +| EnterPlanMode | 仅主 Agent 可进入计划模式 | +| AskUserQuestion | 子 Agent 不应直接问用户 | +| TaskStop | 仅主 Agent 可终止任务 | | Agent | 防止递归生成(Ant 内部例外) | ### 第二层:Agent 类型过滤 @@ -267,9 +264,7 @@ function resolveAgentTools(agentDef, availableTools) { └─ 最终工具池 ``` ---- - -## 四、上下文传递机制 +## 上下文传递机制 ![上下文传递](./images/06-context-passing.png) @@ -340,7 +335,7 @@ function getAgentSystemPrompt(agentDef, toolUseContext) { } ``` -### SubagentContext — 子代理上下文隔离 +### SubagentContext — 子 Agent 上下文隔离 ```typescript // src/utils/forkedAgent.ts @@ -389,9 +384,7 @@ agentDefinition.model ← Agent 定义 'inherit' ← 继承父代理模型 ``` ---- - -## 五、Agent Teams 内部机制 +## Agent Teams 内部机制 ### TeamFile 结构 @@ -512,9 +505,7 @@ SendMessage({ to, message }) └─ sendToUdsSocket(socketPath, message) ``` ---- - -## 六、后台任务引擎 +## 后台任务引擎 ![后台任务引擎](./images/08-background-task.png) @@ -614,9 +605,7 @@ function enqueuePendingNotification(taskId, result) { | 写入方式 | 队列异步写入,防内存堆积 | | 安全措施 | O_NOFOLLOW 防符号链接攻击 | ---- - -## 七、DreamTask — 自动记忆整合 +## DreamTask — 自动记忆整合 DreamTask 是特殊的后台 Agent,用于跨会话记忆整合。 @@ -645,9 +634,7 @@ type DreamTaskState = { } ``` ---- - -## 八、Worktree 隔离实现 +## Worktree 隔离实现 ### 创建流程 @@ -680,9 +667,7 @@ async function createAgentWorktree(slug) { - 无改动:自动删除 worktree(`removeAgentWorktree()`) - 异常退出:通过 `registerTeamForSessionCleanup()` 确保清理 ---- - -## 九、权限同步机制 +## 权限同步机制 ### 团队级权限 @@ -699,7 +684,7 @@ type TeamAllowedPath = { ### Bubble 模式 -Fork Agent 使用 `bubble` 权限模式 — 权限提示冒泡到父代理终端: +Fork Agent 使用 `bubble` 权限模式 — 权限提示冒泡到父 Agent 终端: ``` Fork Agent 需要权限 @@ -724,9 +709,7 @@ Fork Agent 需要权限 └─ Leader 的 useSwarmPermissionPoller 处理 ``` ---- - -## 十、Agent 生命周期完整数据流 +## Agent 生命周期完整数据流 ``` 1. 用户触发 Agent Tool @@ -774,9 +757,7 @@ Fork Agent 需要权限 └─ 异步:收到 后处理 ``` ---- - -## 十一、关键源文件索引 +## 关键源文件索引 ### Agent Tool 核心 @@ -813,7 +794,7 @@ Fork Agent 需要权限 | 文件 | 职责 | |------|------| -| `src/utils/forkedAgent.ts` | 缓存安全参数,子代理上下文 | +| `src/utils/forkedAgent.ts` | 缓存安全参数,子 Agent 上下文 | | `src/utils/systemPrompt.ts` | 系统提示词优先级构建 | | `src/utils/model/agent.ts` | Agent 模型解析 | | `src/utils/worktree.ts` | Git worktree 隔离 | @@ -836,9 +817,7 @@ Fork Agent 需要权限 |------|------| | `src/coordinator/coordinatorMode.ts` | 协调器模式配置 | ---- - -## 十二、Feature Flags +## Feature Flags | Flag | 控制内容 | |------|----------| diff --git a/docs/agent/01-usage-guide.md b/docs/internals/agent.md similarity index 84% rename from docs/agent/01-usage-guide.md rename to docs/internals/agent.md index 0c771beb..8999f906 100644 --- a/docs/agent/01-usage-guide.md +++ b/docs/internals/agent.md @@ -1,18 +1,19 @@ -# Claude Code 多 Agent 系统 — 使用指南 +--- +title: 多 Agent 使用指南 +nav_title: 多 Agent 使用 +description: 内置 Agent、生成方式、后台任务、Agent Teams 与自定义 Agent 的写法。 +order: 4 +--- -> 让 Claude Code 同时调度多个专业代理,并行处理复杂任务。 +# 多 Agent 使用指南 -

-多 Agent 系统 · 六种内置 Agent · 如何生成 Agent · 后台任务管理 · Agent Teams · 自定义 Agent · 权限模式 · 快速参考 -

+让 Claude Code 同时调度多个专业 Agent,并行处理复杂任务。 ![多 Agent 系统概览](./images/01-agent-overview.png) ---- +## 什么是多 Agent 系统? -## 一、什么是多 Agent 系统? - -Claude Code 的多 Agent 系统是一套**智能任务编排框架**,让主代理能够生成多个专业化的子代理(Subagent),各自独立执行不同的任务,最终将结果汇总给用户。 +Claude Code 的多 Agent 系统是一套**智能任务编排框架**,让主 Agent 能够生成多个专业化的子 Agent(Subagent),各自独立执行不同的任务,最终将结果汇总给用户。 核心理念:**把大任务拆分为多个专业小任务,并行执行,提高效率。** @@ -23,15 +24,13 @@ Claude Code 的多 Agent 系统是一套**智能任务编排框架**,让主代 | 代码审查 | 单线程逐文件看 | 多个 reviewer 并行审查 | | 调试复杂 bug | 一个假设一个假设试 | 多个 debugger 并行验证 | ---- - -## 二、六种内置 Agent +## 六种内置 Agent ![六种内置 Agent](./images/02-agent-types.png) -Claude Code 内置了 6 种专业代理,每种都有特定的工具池和适用场景: +Claude Code 内置了 6 种专业 Agent,每种都有特定的工具池和适用场景: -### 1. general-purpose(通用代理) +### 1. general-purpose(通用型) **适用场景**:复杂的多步骤研究、代码搜索、需要完整工具访问的任务。 @@ -44,10 +43,10 @@ Agent({ ``` - **工具池**:全部工具(`*`) -- **模型**:继承父代理 +- **模型**:继承父 Agent - **特点**:万能型,不确定用哪个 agent 时选它 -### 2. Explore(探索代理) +### 2. Explore(探索型) **适用场景**:快速搜索文件、搜索代码模式、回答代码库结构问题。 @@ -59,11 +58,11 @@ Agent({ }) ``` -- **工具池**:只读工具(Glob、Grep、Read、Bash) +- **工具池**:除 Agent、ExitPlanMode、Edit、Write、NotebookEdit 外的全部工具。**注意 Bash 在内**——它不能改文件,但能跑任意命令 - **模型**:Haiku(快速低成本) - **特点**:不能修改文件,速度快,适合调研 -### 3. Plan(规划代理) +### 3. Plan(规划型) **适用场景**:设计实现方案、分析架构权衡、生成分步计划。 @@ -75,11 +74,11 @@ Agent({ }) ``` -- **工具池**:只读工具(同 Explore) -- **模型**:继承父代理(需要强推理能力) +- **工具池**:同 Explore,同一组禁用清单 +- **模型**:继承父 Agent(需要强推理能力) - **特点**:输出结构化计划,包含关键文件和依赖分析 -### 4. verification(验证代理) +### 4. verification(验证型) **适用场景**:独立验证实现是否正确,运行测试,边界检查。 @@ -91,11 +90,11 @@ Agent({ }) ``` -- **工具池**:只读工具 -- **模型**:继承父代理 +- **工具池**:同 Explore;系统提示额外允许在 tmp 下写临时测试脚本 +- **模型**:继承父 Agent - **特点**:始终在后台运行,输出 PASS/FAIL/PARTIAL 判定,红色标识 -### 5. claude-code-guide(指南代理) +### 5. claude-code-guide(指南型) **适用场景**:回答关于 Claude Code、Agent SDK、Claude API 的问题。 @@ -107,11 +106,11 @@ Agent({ }) ``` -- **工具池**:Bash、Read、WebFetch、WebSearch +- **工具池**:Glob、Grep、Read、WebFetch、WebSearch(内部构建下换成 Bash + Read + WebFetch + WebSearch) - **模型**:Haiku - **特点**:专注文档查询,dontAsk 权限模式 -### 6. statusline-setup(状态栏配置代理) +### 6. statusline-setup(状态栏配置) **适用场景**:配置 Claude Code 状态栏显示。 @@ -124,15 +123,13 @@ Agent({ | Agent | 读写 | 工具池 | 模型 | 用途 | |-------|------|--------|------|------| | general-purpose | 读写 | 全部 | 继承 | 通用任务 | -| Explore | 只读 | 搜索+读取 | Haiku | 快速探索 | -| Plan | 只读 | 搜索+读取 | 继承 | 架构规划 | -| verification | 只读 | 搜索+读取 | 继承 | 独立验证 | +| Explore | 不能改文件 | 全部工具减去编辑类 | Haiku | 快速探索 | +| Plan | 不能改文件 | 同 Explore | 继承 | 架构规划 | +| verification | 不能改项目文件 | 同 Explore | 继承 | 独立验证 | | claude-code-guide | 只读 | 搜索+网络 | Haiku | 文档指南 | | statusline-setup | 读写 | Read+Edit | Sonnet | 状态栏配置 | ---- - -## 三、如何生成 Agent +## 如何生成 Agent ### 基本参数 @@ -161,11 +158,11 @@ Agent({ }) ``` -主代理会等待子代理完成,然后收到结果继续工作。 +主 Agent 会等待子 Agent 完成,然后收到结果继续工作。 ### 后台异步执行 -适合耗时任务,主代理可以继续做其他事情: +适合耗时任务,主 Agent 可以继续做其他事情: ``` Agent({ @@ -176,7 +173,7 @@ Agent({ ``` - Agent 立即返回 `async_launched` 状态和 taskId -- 主代理继续工作,不需要等待 +- 主 Agent 继续工作,不需要等待 - Agent 完成后自动收到 `` 通知 - 通知包含任务状态、输出文件路径和结果摘要 @@ -208,9 +205,7 @@ Agent({ - 完成后如有改动,返回 worktree 路径和分支名 - 无改动则自动清理 ---- - -## 四、后台任务管理 +## 后台任务管理 ![Agent 生成流程](./images/03-spawn-flow.png) @@ -236,7 +231,7 @@ Agent({ ### 完成通知 -当后台 Agent 完成时,主代理收到 XML 格式的通知: +当后台 Agent 完成时,主 Agent 收到 XML 格式的通知: ```xml @@ -249,15 +244,13 @@ Agent({ ### 自动后台化 -当 `tengu_auto_background_agents` 特性开启时,前台 Agent 运行超过 **120 秒**会自动转为后台执行,释放主代理继续工作。 +当 `tengu_auto_background_agents` 特性开启时,前台 Agent 运行超过 **120 秒**会自动转为后台执行,释放主 Agent 继续工作。 ---- - -## 五、Agent Teams — 多代理协作 +## Agent Teams — 多 Agent 协作 ![Agent Teams 协作](./images/04-agent-teams.png) -Agent Teams 是更高级的多代理协作模式,多个代理以团队形式工作,通过消息通信协调任务。 +Agent Teams 是更高级的多 Agent 协作模式,多个 Agent 以团队形式工作,通过消息通信协调任务。 ### 创建团队 @@ -271,7 +264,7 @@ TeamCreate({ 团队创建后: - 生成团队配置文件:`~/.claude/teams/{team_name}/config.json` - 创建共享任务目录:`~/.claude/tasks/{team_name}/` -- 当前代理自动成为 **Team Lead**(团队负责人) +- 当前 Agent 自动成为 **Team Lead**(团队负责人) ### 添加团队成员 @@ -344,11 +337,9 @@ Agent Teams 支持两种执行后端: | **tmux** | 独立 tmux pane 运行 | 需要独立终端视图 | | **iTerm2** | 独立 iTerm2 窗口运行 | macOS iTerm2 用户 | ---- +## 自定义 Agent -## 六、自定义 Agent - -除了内置 Agent,你还可以创建自己的专业代理。 +除了内置 Agent,你还可以创建自己的专业 Agent。 ### 桌面端管理与作用域 @@ -447,9 +438,7 @@ maxTurns: 10 桌面端仍会展示被覆盖的定义及其来源,但真正 spawn 时使用优先级最高的活动定义。 ---- - -## 七、权限模式 +## 权限模式 每个 Agent 可以设置不同的权限模式: @@ -461,15 +450,13 @@ maxTurns: 10 | `bypassPermissions` | 跳过所有权限检查 | | `dontAsk` | 拒绝所有未预批准的操作 | | `auto` | AI 驱动的权限分类(仅 Ant 内部) | -| `bubble` | 权限提示冒泡到父代理终端 | +| `bubble` | 权限提示冒泡到父 Agent 终端 | ---- - -## 八、快速参考 +## 快速参考 | 操作 | 方法 | |------|------| -| 生成子代理 | `Agent({ prompt: "...", subagent_type: "Explore" })` | +| 生成子 Agent | `Agent({ prompt: "...", subagent_type: "Explore" })` | | 后台运行 | `Agent({ ..., run_in_background: true })` | | 并行生成 | 单条消息中发送多个 Agent 调用 | | Worktree 隔离 | `Agent({ ..., isolation: "worktree" })` | diff --git a/docs/memory/03-autodream.md b/docs/internals/autodream.md similarity index 86% rename from docs/memory/03-autodream.md rename to docs/internals/autodream.md index c58f817b..32f91aa5 100644 --- a/docs/memory/03-autodream.md +++ b/docs/internals/autodream.md @@ -1,16 +1,17 @@ -# Claude Code 记忆系统 — AutoDream 记忆整合 +--- +title: AutoDream 记忆整合 +nav_title: AutoDream +description: 后台静默回顾近期会话、整合并修剪记忆的四阶段流程与安全限制。 +order: 11 +--- -> Claude 会"做梦"——在后台静默回顾近期会话,整合、更新、修剪记忆,就像人类睡眠中整理白天的记忆一样。 +# AutoDream 记忆整合 -

-AutoDream · 触发条件 · 整合流程 · 安全限制 · UI 展示 · 配置开关 · 对比 · 源码导航 -

+Claude 会"做梦"——在后台静默回顾近期会话,整合、更新、修剪记忆,就像人类睡眠中整理白天的记忆一样。 ![AutoDream 概览](./images/11-autodream-overview.png) ---- - -## 一、什么是 AutoDream? +## 什么是 AutoDream? AutoDream 是 Claude Code 的 **后台记忆整合机制**,内部代号 **"Dream: Memory Consolidation"**。 @@ -21,7 +22,7 @@ AutoDream 是 Claude Code 的 **后台记忆整合机制**,内部代号 **"Dre | 白天随手记笔记 | `extractMemories` — 每次对话后提取新记忆 | | 晚上睡觉时整理笔记本 | `autoDream` — 定期回顾多个会话,整合全部记忆 | -当你不活跃时(默认间隔 24 小时、积累 5 个会话后),Claude 会在后台静默启动一个 **"做梦"子智能体**(forked subagent),回顾所有近期会话记录,将零散的记忆整合为结构化的、去重的、去过时的持久化知识。 +当你不活跃时(默认间隔 24 小时、积累 5 个会话后),Claude 会在后台静默启动一个 **"做梦"子 Agent**(forked subagent),回顾所有近期会话记录,将零散的记忆整合为结构化的、去重的、去过时的持久化知识。 **关键源码**:`src/services/autoDream/autoDream.ts` @@ -30,9 +31,7 @@ AutoDream 是 Claude Code 的 **后台记忆整合机制**,内部代号 **"Dre // subagent when time-gate passes AND enough sessions have accumulated. ``` ---- - -## 二、触发条件 +## 触发条件 ![AutoDream 触发流程](./images/12-autodream-trigger.png) @@ -85,15 +84,13 @@ if (!toolUseContext.agentId) { - **远程模式**:`getIsRemoteMode() === true` - **autoMemory 未启用** - **`--bare` / SIMPLE 模式** -- **子代理内**:只有主代理才触发 +- **子 Agent 内**:只有主 Agent 才触发 ---- - -## 三、四阶段整合流程 +## 四阶段整合流程 ![AutoDream 四阶段流程](./images/13-autodream-phases.png) -一旦所有门控通过,AutoDream 启动一个 **分叉子智能体**,按照 `consolidationPrompt.ts` 定义的 4 阶段提示词工作: +一旦所有门控通过,AutoDream 启动一个 **分叉子 Agent**,按照 `consolidationPrompt.ts` 定义的 4 阶段提示词工作: ### Phase 1 — Orient(定向) @@ -132,11 +129,9 @@ if (!toolUseContext.agentId) { **关键源码** `src/services/autoDream/consolidationPrompt.ts:10-64` ---- +## 安全限制 -## 四、安全限制 - -AutoDream 子智能体受到严格的工具权限限制: +AutoDream 子 Agent 受到严格的工具权限限制: ### Bash 仅限只读 @@ -172,9 +167,7 @@ AutoDream 子智能体受到严格的工具权限限制: **关键源码** `src/services/autoDream/consolidationLock.ts` ---- - -## 五、UI 展示 +## UI 展示 ### 底部状态栏 @@ -213,9 +206,7 @@ appendSystemMessage({ - `src/tasks/DreamTask/DreamTask.ts` — 任务状态管理 - `src/components/tasks/DreamDetailDialog.tsx` — UI 组件 ---- - -## 六、配置与开关 +## 配置与开关 ### settings.json @@ -255,9 +246,7 @@ export function isAutoDreamEnabled(): boolean { 除了自动触发,用户可以通过 `/dream` 命令手动触发记忆整合。手动触发会调用 `recordConsolidation()` 更新锁文件时间戳。 ---- - -## 七、与 extractMemories 的关系 +## 与 extractMemories 的关系 | 维度 | extractMemories | autoDream | |------|----------------|-----------| @@ -267,7 +256,7 @@ export function isAutoDreamEnabled(): boolean { | **目标** | 提取新记忆 | 整合/去重/修剪已有记忆 | | **人类类比** | 白天随手记笔记 | 睡觉时整理笔记本 | | **共享组件** | `createAutoMemCanUseTool` | `createAutoMemCanUseTool` | -| **分叉代理** | 最多 5 turns | 无 turn 限制 | +| **分叉 Agent** | 最多 5 turns | 无 turn 限制 | | **转录** | 不记录(`skipTranscript`) | 不记录(`skipTranscript`) | ### 协作流 @@ -286,13 +275,11 @@ autoDream → 回顾所有记忆 + 会话记录 MEMORY.md 和主题文件焕然一新 ``` ---- - -## 八、源码导航 +## 源码导航 | 文件 | 职责 | |------|------| -| `src/services/autoDream/autoDream.ts` | 主逻辑:门控检查、启动分叉代理、进度监控 | +| `src/services/autoDream/autoDream.ts` | 主逻辑:门控检查、启动分叉 Agent、进度监控 | | `src/services/autoDream/config.ts` | 开关控制:settings.json 或 GrowthBook | | `src/services/autoDream/consolidationPrompt.ts` | 梦境提示词:4 阶段整合工作流 | | `src/services/autoDream/consolidationLock.ts` | 锁文件机制:防并发、时间戳、回滚 | @@ -303,9 +290,7 @@ MEMORY.md 和主题文件焕然一新 | `src/utils/backgroundHousekeeping.ts` | 初始化入口:`initAutoDream()` | | `src/services/extractMemories/extractMemories.ts` | 共享的 `createAutoMemCanUseTool` | ---- - -## 九、分析事件 +## 分析事件 AutoDream 通过以下事件记录运行状态: diff --git a/docs/channel/01-channel-system.md b/docs/internals/channel.md similarity index 89% rename from docs/channel/01-channel-system.md rename to docs/internals/channel.md index ed8684f8..2c7c5717 100644 --- a/docs/channel/01-channel-system.md +++ b/docs/internals/channel.md @@ -1,25 +1,17 @@ -# Channel 系统架构解析 +--- +title: Channel 系统 +nav_title: Channel 系统 +description: IM 平台远程控制 Agent 的消息协议、六层访问控制与权限中继。 +order: 13 +--- -> 从源码视角深度剖析 Claude Code 如何通过 IM 平台远程控制 Agent +# Channel 系统 -

-概念 · -架构 · -协议 · -访问控制 · -权限中继 · -UI · -插件 · -安全 · -CLI · -特性开关 -

+从源码视角深度剖析 Claude Code 如何通过 IM 平台远程控制 Agent ![Channel System Overview](./images/01-channel-overview.png) ---- - -## 一、什么是 Channel +## 什么是 Channel Channel 是 Claude Code 的 **IM 集成系统**,它允许用户通过 Telegram、Feishu(飞书)、Discord、Slack 等即时通讯平台远程控制正在运行的 Claude Code Agent。 @@ -45,9 +37,7 @@ type ChannelEntry = **plugin 类型**:来自 marketplace 的验证插件(如 `plugin:telegram@anthropic`) **server 类型**:直接指定的 MCP 服务器名称(始终需要 dev 旁路) ---- - -## 二、整体架构 +## 整体架构 ![Message Flow](./images/02-message-flow.png) @@ -107,11 +97,9 @@ Channel 系统的消息流转遵循一个清晰的双向路径: | **Plugin Integration** | `mcpPluginIntegration.ts` | 插件作用域命名 | | **State** | `bootstrap/state.ts` | 全局 Channel 白名单状态 | ---- +## 消息协议 -## 三、消息协议 - -### 3.1 入站通知 Schema +### 入站通知 Schema Channel Server 推送到 Claude Code 的通知格式: @@ -128,7 +116,7 @@ const ChannelMessageNotificationSchema = z.object({ }) ``` -### 3.2 XML 封装 +### XML 封装 收到通知后,系统将其封装为 `` XML 标签: @@ -158,7 +146,7 @@ ${content} Model 看到这个标签后,就知道消息来自 Telegram 的用户 alice,并会使用 Telegram 的 `reply` 工具回复。 -### 3.3 安全的元数据过滤 +### 安全的元数据过滤 Meta 键名会成为 XML 属性名,恶意构造的键(如 `x="" injected="y`)可能导致 XML 注入。系统使用严格的正则过滤: @@ -169,7 +157,7 @@ const SAFE_META_KEY = /^[a-zA-Z_][a-zA-Z0-9_]*$/ 实际场景中,Channel 服务器只发送 `chat_id`、`user`、`thread_ts`、`message_id` 这类安全键名。 -### 3.4 消息入队 +### 消息入队 封装后的消息通过 enqueue 推入消息队列: @@ -186,9 +174,7 @@ enqueue({ SleepTool 每约 1 秒轮询一次 `hasCommandsInQueue()`,发现新消息后唤醒 Agent。 ---- - -## 四、六层访问控制 +## 六层访问控制 ![Access Control](./images/03-access-control.png) @@ -205,7 +191,7 @@ function gateChannelServer( ): ChannelGateResult // { action: 'register' } | { action: 'skip', kind, reason } ``` -### 4.1 第一层:能力声明(Capability) +### 第一层:能力声明(Capability) ```typescript if (!capabilities?.experimental?.['claude/channel']) { @@ -216,7 +202,7 @@ if (!capabilities?.experimental?.['claude/channel']) { MCP Server 必须在握手时声明 `experimental['claude/channel']: {}` 能力。这是 MCP 的"存在信号"惯例(类似 `tools: {}`),将普通 MCP Server 与 Channel Server 区分开来。 -### 4.2 第二层:运行时开关(Runtime Gate) +### 第二层:运行时开关(Runtime Gate) ```typescript if (!isChannelsEnabled()) { @@ -227,7 +213,7 @@ if (!isChannelsEnabled()) { `isChannelsEnabled()` 检查 GrowthBook 特性开关 `tengu_harbor`(默认 false,5 分钟刷新)。这是全局的"紧急制动"——关闭此开关立即禁用所有 Channel,不需要发版。 -### 4.3 第三层:OAuth 认证(Auth) +### 第三层:OAuth 认证(Auth) ```typescript if (!getClaudeAIOAuthTokens()?.accessToken) { @@ -238,7 +224,7 @@ if (!getClaudeAIOAuthTokens()?.accessToken) { Channel 仅限 OAuth 认证用户。API Key 用户被阻止,因为 Console 端目前还没有 `channelsEnabled` 的管理界面。 -### 4.4 第四层:组织策略(Policy) +### 第四层:组织策略(Policy) ```typescript const sub = getSubscriptionType() @@ -252,7 +238,7 @@ if (managed && policy?.channelsEnabled !== true) { Teams/Enterprise 组织必须在托管设置中显式启用 `channelsEnabled: true`。默认关闭——即使没有配置任何策略的团队组织也不会回退到非托管路径。 -### 4.5 第五层:会话白名单(Session) +### 第五层:会话白名单(Session) ```typescript const entry = findChannelEntry(serverName, getAllowedChannels()) @@ -264,7 +250,7 @@ if (!entry) { MCP Server 必须在本次会话的 `--channels` 参数列表中。即使一个受信任的 Server 动态添加了 `claude/channel` 能力,也无法绕过——必须用户在启动时显式列出。 -### 4.6 第六层:Marketplace 验证 + 白名单(Allowlist) +### 第六层:Marketplace 验证 + 白名单(Allowlist) 对于 plugin 类型的 Channel: @@ -303,19 +289,17 @@ type ChannelGateResult = // kind 枚举:capability | disabled | auth | policy | session | marketplace | allowlist ``` ---- - -## 五、权限中继系统 +## 权限中继系统 ![Permission Relay](./images/04-permission-relay.png) -### 5.1 为什么需要权限中继 +### 为什么需要权限中继 当 Claude Code 需要执行敏感操作(如运行 Bash 命令),会弹出权限确认对话框。但如果用户通过 Telegram 远程控制 Agent,他看不到本地终端的对话框。 权限中继系统解决了这个问题:**将权限提示转发到 IM 平台,让用户在手机上也能审批或拒绝操作**。 -### 5.2 出站:CC → Channel(权限请求) +### 出站:CC → Channel(权限请求) 当 Agent 触发权限对话框且 Channel 声明了 `experimental['claude/channel/permission']` 能力时: @@ -334,7 +318,7 @@ type ChannelPermissionRequestParams = { Channel Server 收到后,按各平台格式化(Telegram markdown、Discord embed 等)发送给用户。 -### 5.3 Short Request ID 生成 +### Short Request ID 生成 5 字母标识符的设计充满巧思: @@ -359,7 +343,7 @@ function shortRequestId(toolUseID: string): string { - **纯字母**:手机用户不需要切换键盘模式(十六进制需要在字母和数字间切换) - **大小写不敏感**:适配手机自动更正 -### 5.4 入站:Channel → CC(权限响应) +### 入站:Channel → CC(权限响应) 用户在 IM 中回复格式:`yes tbxkq` 或 `no tbxkq` @@ -379,7 +363,7 @@ const ChannelPermissionNotificationSchema = z.object({ **关键设计**:Channel Server 负责解析用户回复并发出结构化事件,CC 端不做文本正则匹配。这意味着普通聊天中的文字永远不会意外触发权限审批。 -### 5.5 多源竞争 +### 多源竞争 权限响应来自四个来源,先到先得: @@ -401,7 +385,7 @@ const ChannelPermissionNotificationSchema = z.object({ `createChannelPermissionCallbacks()` 使用闭包维护 pending Map(不在模块级别,也不在 AppState 中——函数引用在状态中会导致序列化问题),构造一次后存入 AppState。 -### 5.6 过滤权限中继客户端 +### 过滤权限中继客户端 ```typescript // channelPermissions.ts:177-194 @@ -417,11 +401,9 @@ function filterPermissionRelayClients(clients, isInAllowlist) { **三个条件全部必须**:已连接 + 在白名单中 + 同时声明了两个能力(`claude/channel` 和 `claude/channel/permission`)。第二个能力是显式 opt-in——只推消息的 Channel 永远不会意外成为权限审批入口。 ---- +## UI 组件 -## 六、UI 组件 - -### 6.1 终端消息渲染(UserChannelMessage) +### 终端消息渲染(UserChannelMessage) ```typescript // UserChannelMessage.tsx @@ -447,7 +429,7 @@ const TRUNCATE_AT = 60 // 消息体截断长度 其中 `◁` 是 Channel 箭头符号(`CHANNEL_ARROW`),显示服务器叶子名称和可选的用户名。 -### 6.2 状态通知(ChannelsNotice) +### 状态通知(ChannelsNotice) 启动时显示 `--channels` 条目的状态,报告阻断原因: @@ -458,7 +440,7 @@ const TRUNCATE_AT = 60 // 消息体截断长度 | `policyBlocked` | 组织策略未启用 channels | | `unmatched` | 未在 `--channels` 列表中匹配到 | -### 6.3 开发者确认对话框(DevChannelsDialog) +### 开发者确认对话框(DevChannelsDialog) 使用 `--dangerously-load-development-channels` 时显示的警告对话框: @@ -480,11 +462,9 @@ const TRUNCATE_AT = 60 // 消息体截断长度 选择 "Exit" 会调用 `gracefulShutdownSync(1)` 立即退出。 ---- +## 插件 Channel 架构 -## 七、插件 Channel 架构 - -### 7.1 Plugin Manifest 中的 Channel 声明 +### Plugin Manifest 中的 Channel 声明 插件通过 `plugin.json` 中的 `channels` 数组声明 Channel: @@ -539,7 +519,7 @@ const PluginManifestChannelsSchema = z.object({ } ``` -### 7.2 配置流(PluginOptionsFlow) +### 配置流(PluginOptionsFlow) 启用插件后,如果 Channel 有未配置的 `userConfig` 字段: @@ -571,7 +551,7 @@ function getUnconfiguredChannels(plugin: LoadedPlugin): UnconfiguredChannel[] { } ``` -### 7.3 作用域命名(Scoped Naming) +### 作用域命名(Scoped Naming) 插件提供的 MCP Server 会被添加作用域前缀以避免冲突: @@ -601,7 +581,7 @@ function addPluginScopeToServers( `pluginSource` 被保存在配置上,后续 Channel Gate 的 marketplace 验证步骤会用到它。 -### 7.4 白名单有效来源 +### 白名单有效来源 ```typescript // channelNotification.ts:127-138 @@ -617,18 +597,16 @@ function getEffectiveChannelAllowlist(sub, orgList) { 组织管理员可以通过 `allowedChannelPlugins` 完全控制哪些 Channel 插件被信任,而不依赖全局 ledger。 ---- +## 安全设计 -## 八、安全设计 - -### 8.1 XML 注入防护 +### XML 注入防护 Channel 消息中的元数据会成为 XML 属性。两道防线: 1. **键名过滤**:`SAFE_META_KEY = /^[a-zA-Z_][a-zA-Z0-9_]*$/` 只允许纯标识符 2. **值转义**:`escapeXmlAttr()` 对属性值进行 XML 转义 -### 8.2 Marketplace 验证 +### Marketplace 验证 `--channels plugin:slack@anthropic` 只是用户的"意图声明"。运行时名称 `plugin:slack:X` 可能来自 `slack@anthropic` 或 `slack@evil`。Gate 验证两者必须匹配: @@ -641,25 +619,23 @@ if (actual !== entry.marketplace) { } ``` -### 8.3 权限中继的信任边界 +### 权限中继的信任边界 来自代码注释中 Kenneth 的分析(PR 讨论 #2956440848): > "Claude 会自我审批吗?" 答:审批方是通过 Channel 的人类,不是 Claude。但信任边界不是终端——而是白名单(tengu_harbor_ledger)。一个被妥协的 Channel Server 可以在人类没看到提示的情况下伪造 "yes \"。这是被接受的风险:一个已妥协的 Channel 本来就有无限的对话注入轮次(可以社会工程攻击、等待 acceptEdits 模式等);注入后自动审批更快,但能力上并不更强。权限对话框减缓了已妥协 Channel 的攻击速度,但不能完全阻止。 -### 8.4 skipSlashCommands +### skipSlashCommands Channel 消息入队时设置 `skipSlashCommands: true`,确保 IM 用户发送的 `/help` 等文本不会被解释为 Claude Code 的斜杠命令。 -### 8.5 dev 旁路的粒度 +### dev 旁路的粒度 `--dangerously-load-development-channels` 的 `dev` 标记是**每个条目**级别的,不是全局的。接受开发对话框后,只有显式标记为 dev 的条目才旁路白名单,`--channels` 中的正常条目仍然需要通过完整的白名单检查。 ---- +## 命令行接口 -## 九、命令行接口 - -### 9.1 启动参数 +### 启动参数 ```bash # 使用已审批的 Channel 插件 @@ -673,7 +649,7 @@ claude --channels plugin:telegram@anthropic \ --dangerously-load-development-channels plugin:dev-channel@local ``` -### 9.2 参数解析 +### 参数解析 ```typescript // main.tsx @@ -687,7 +663,7 @@ const parseChannelEntries = (raw: string[], flag: string): ChannelEntry[] => { setAllowedChannels(channelEntries) ``` -### 9.3 特性门控 +### 特性门控 这两个 CLI 选项只在特性开关启用时可用: @@ -701,11 +677,9 @@ if (feature('KAIROS') || feature('KAIROS_CHANNELS')) { `hideHelp()` 表示这些选项不会出现在 `--help` 输出中——Channel 功能目前处于隐藏特性阶段。 ---- +## 特性开关与分析 -## 十、特性开关与分析 - -### 10.1 特性开关 +### 特性开关 | 开关 | 来源 | 用途 | |------|------|------| @@ -714,7 +688,7 @@ if (feature('KAIROS') || feature('KAIROS_CHANNELS')) { | `tengu_harbor_ledger` | GrowthBook 运行时 | 审批插件白名单 | | `tengu_harbor_permissions` | GrowthBook 运行时 | 权限中继功能开关 | -### 10.2 分析事件 +### 分析事件 ```typescript // Channel Gate 结果 @@ -742,33 +716,7 @@ tengu_mcp_channel_flags: { } ``` ---- - -## 十一、总结 - -Channel 系统是 Claude Code 的 **IM 集成框架**,它的设计体现了几个核心原则: - -### 1. 安全优先 - -六层访问控制门确保只有经过审批、用户明确选择的 Channel 才能推送消息。从编译时特性开关到运行时白名单,每一层都可以独立中断。 - -### 2. 协议驱动 - -Channel 不是特殊代码——它们就是普通的 MCP Server,只是多了一个通知协议。这意味着任何能实现 MCP 的语言都可以编写 Channel 插件。 - -### 3. 松耦合 - -Channel 失败不会阻断本地工作流。权限中继是多源竞争机制,任一来源的响应都有效。 - -### 4. 渐进式信任 - -从全局开关 → 认证 → 组织策略 → 会话白名单 → Marketplace 验证 → 白名单,信任级别逐级递增,每一步都有明确的安全目的。 - -### 5. 插件友好 - -通过 `plugin.json` 的声明式 Channel 配置、自动的用户配置提示流和作用域命名,第三方开发者可以轻松构建自己的 Channel 插件。 - -### 源码文件索引 +## 源码文件索引 | 文件 | 行数 | 职责 | |------|------|------| diff --git a/docs/features/computer-use-architecture.md b/docs/internals/computer-use.md similarity index 94% rename from docs/features/computer-use-architecture.md rename to docs/internals/computer-use.md index 20eb312c..e908c215 100644 --- a/docs/features/computer-use-architecture.md +++ b/docs/internals/computer-use.md @@ -1,6 +1,13 @@ +--- +title: Computer Use 架构 +nav_title: Computer Use +description: 工具注册、坐标换算、授权检查、Python Bridge 与当前安全边界。 +order: 12 +--- + # Computer Use 架构 -> 本页说明 Computer Use 的工具、授权、平台执行器与当前安全边界。使用步骤请先阅读 [Computer Use 使用指南](./computer-use.md)。 +从模型发出一次点击,到鼠标真的动起来,中间隔着工具定义、授权检查、Python Bridge 和平台 Helper 四道关。这一篇拆开讲这四道关各自把住什么。开启步骤和日常用法见 [Computer Use 使用指南](../desktop/computer-use.md)。 ## 分层结构 diff --git a/docs/guide/contributing.md b/docs/internals/contributing.md similarity index 81% rename from docs/guide/contributing.md rename to docs/internals/contributing.md index ee3fb0f4..faafa889 100644 --- a/docs/guide/contributing.md +++ b/docs/internals/contributing.md @@ -1,4 +1,11 @@ -# 贡献指南与本地质量门禁 +--- +title: 参与贡献与质量门禁 +nav_title: 参与贡献 +description: 本地安装、影响面检查、质量门禁、测试要求与 PR 提交流程。 +order: 14 +--- + +# 参与贡献与质量门禁 这份文档说明贡献代码前应该如何在本地安装、开发、测试和运行质量门禁。目标是让维护者和贡献者都能在提交 PR 前回答一个问题:这次改动有没有破坏核心 Coding Agent 工作流。 @@ -200,7 +207,7 @@ bun run quality:gate --mode baseline --allow-live \ --provider-model minimax:main:minimax-main ``` -`provider` selector 来自桌面端「Settings > Providers」里保存的本机配置。别人 clone 代码后不需要知道你的 provider UUID,也不需要使用你的供应商;他们可以在自己的桌面端添加 provider 后运行 `bun run quality:providers` 选择自己的模型。 +`provider` selector 来自桌面端「设置 → 服务商」里保存的本机配置。别人 clone 代码后不需要知道你的 provider UUID,也不需要使用你的供应商;他们可以在自己的桌面端添加 provider 后运行 `bun run quality:providers` 选择自己的模型。 如果没有保存 provider,也可以用环境变量跑一条 unsaved provider smoke: @@ -236,6 +243,67 @@ release 模式会组合 PR checks、baseline catalog、live baseline、native ch release 模式下 live lane 不允许静默跳过。缺少 provider、真实模型额度或外部账号时,门禁会失败,并要求在发版记录里明确 blocker。 +## 发版与自动更新 + +桌面端版本号的唯一来源是 `desktop/package.json`。正式发布要求版本号、Git tag 和 `release-notes/vX.Y.Z.md` 三者严格一致。 + +应用内更新由 `electron-updater` 驱动,产物托管在 GitHub Releases: + +| 平台 | 安装/更新目标 | Metadata | +|---|---|---| +| macOS arm64 / x64 | `dmg` 首次安装,`zip` 供 Squirrel.Mac 更新 | `latest-mac.yml` | +| Windows x64 / ARM64 | NSIS `.exe` | `latest.yml` | +| Linux x64 | `.AppImage` 自动更新,`.deb` 供手动安装 | `latest-linux.yml` | +| Linux arm64 | `.AppImage` 自动更新,`.deb` 供手动安装 | `latest-linux-arm64.yml` | + +Release workflow 先在各平台 matrix 里生成 `latest*.yml`,把同名 metadata 临时改名为 `latest-.yml`,最后由 `scripts/release-update-metadata.ts` 合并回 electron-updater 期望的标准文件名。不要改成各 matrix job 直接发布 GitHub Release,否则 metadata 会互相覆盖。 + +### 签名 Secrets + +macOS 的签名与公证依赖以下 GitHub Actions repository secrets: + +```text +MACOS_CERTIFICATE +MACOS_CERTIFICATE_PASSWORD +APPLE_ID +APPLE_APP_SPECIFIC_PASSWORD +APPLE_TEAM_ID +``` + +`MACOS_CERTIFICATE` 是 Developer ID Application `.p12` 的 base64 内容。项目不发布 `.pkg`,不需要 Developer ID Installer 证书。 + +Windows 签名是可选项: + +```text +WINDOWS_CERTIFICATE +WINDOWS_CERTIFICATE_PASSWORD +``` + +缺少 Windows 签名时自动更新仍然可用,只是用户可能看到 SmartScreen 提示。 + +### 发版前检查 + +```bash +bun run scripts/release.ts --dry +bun test scripts/pr/release-workflow.test.ts scripts/release-update-metadata.test.ts scripts/quality-gate/package-smoke/index.test.ts +bun run check:policy +``` + +正式执行 `bun run scripts/release.ts ` 前,先确认对应的 `release-notes/v.md` 已经存在。 + +### 验证一条真实更新链路 + +每次发版至少验证一次从上一个正式版升上来的完整路径: + +1. 安装 GitHub Release 里的上一个正式版。 +2. 推 tag,让 `Release Desktop` workflow 完整通过。 +3. 打开旧版本,等待启动后的自动检查,或在设置里手动检查更新。 +4. 确认提示新版本,下载完成后安装并重启。 +5. 重启后确认「关于」里的版本号正确,且服务商、会话、Skills、Agents、记忆、自定义宠物和自定义数据目录仍然可用。 +6. 确认历史附件上下文、子 Agent 详情和任务状态可以恢复;打开桌宠,验证悬浮窗口与当前会话导航。 + +各平台的重点不同:macOS 要确认 release job 走的是签名产物且启动策略检查通过;Windows 要确认 `latest.yml`、`.exe`、`.exe.blockmap` 都在 Release 资产里,未签名时的 SmartScreen 提示不代表 updater 失败;Linux 优先用 AppImage 验证自动更新,`.deb` 只作手动安装包发布。 + ## PR 提交流程 1. 新建普通产品分支,例如 `fix/session-reconnect` 或 `feat/provider-quality-gate`。 @@ -257,7 +325,7 @@ release 模式下 live lane 不允许静默跳过。缺少 provider、真实模 bun run check:impact ``` -`bun run verify` 也不需要真实模型;只有 live baseline 需要。维护者可以先在桌面端 Settings > Providers 添加自己的 provider,再运行: +`bun run verify` 也不需要真实模型;只有 live baseline 需要。维护者可以先在桌面端 设置 → 服务商 添加自己的 provider,再运行: ```bash bun run quality:providers diff --git a/docs/desktop/02-architecture.md b/docs/internals/desktop.md similarity index 94% rename from docs/desktop/02-architecture.md rename to docs/internals/desktop.md index b8a1fbb9..68d45d0e 100644 --- a/docs/desktop/02-architecture.md +++ b/docs/internals/desktop.md @@ -1,10 +1,17 @@ +--- +title: 桌面端架构 +nav_title: 桌面端架构 +description: Electron 主进程、Server Sidecar、CLI 子进程与 IM Adapter 的进程边界。 +order: 1 +--- + # 桌面端架构 -> 本页面向维护者,说明 Claude Code Haha 桌面端当前的 Electron、Bun Server 和 CLI 运行边界。 +桌面端不是把 CLI 直接嵌进 React,而是四类进程协作跑起来的。维护时踩的坑,多半来自跨过了本该关死的那道边界。 -## 架构概览 +## 进程分布 -桌面端不是把 CLI 直接嵌进 React。它由四类进程协作: +四类进程各自的位置: ```text Electron main @@ -128,7 +135,7 @@ ws:///ws/ - 断线期间发送的消息进入内存队列。 - 重连成功后先发送队列中的消息,再发送 `sync_state` 获取 Server 的权威运行状态。 -因此,不应把旧文档里的“最多重试 10 次”当成当前语义。 +如果你在别处读到过“最多重试 10 次”,那是过期说法,以上面这几条为准。 ### 客户端断开不等于停止任务 diff --git a/docs/en/agent/images/01-agent-overview.png b/docs/internals/images/01-agent-overview.png similarity index 100% rename from docs/en/agent/images/01-agent-overview.png rename to docs/internals/images/01-agent-overview.png diff --git a/docs/channel/images/01-channel-overview.png b/docs/internals/images/01-channel-overview.png similarity index 100% rename from docs/channel/images/01-channel-overview.png rename to docs/internals/images/01-channel-overview.png diff --git a/docs/features/images/01-computer-use-architecture.jpg b/docs/internals/images/01-computer-use-architecture.jpg similarity index 100% rename from docs/features/images/01-computer-use-architecture.jpg rename to docs/internals/images/01-computer-use-architecture.jpg diff --git a/docs/memory/images/01-memory-overview.png b/docs/internals/images/01-memory-overview.png similarity index 100% rename from docs/memory/images/01-memory-overview.png rename to docs/internals/images/01-memory-overview.png diff --git a/docs/skills/images/01-skills-overview.png b/docs/internals/images/01-skills-overview.png similarity index 100% rename from docs/skills/images/01-skills-overview.png rename to docs/internals/images/01-skills-overview.png diff --git a/docs/en/agent/images/02-agent-types.png b/docs/internals/images/02-agent-types.png similarity index 100% rename from docs/en/agent/images/02-agent-types.png rename to docs/internals/images/02-agent-types.png diff --git a/docs/features/images/02-computer-use-security-gates.jpg b/docs/internals/images/02-computer-use-security-gates.jpg similarity index 100% rename from docs/features/images/02-computer-use-security-gates.jpg rename to docs/internals/images/02-computer-use-security-gates.jpg diff --git a/docs/memory/images/02-memory-types.png b/docs/internals/images/02-memory-types.png similarity index 100% rename from docs/memory/images/02-memory-types.png rename to docs/internals/images/02-memory-types.png diff --git a/docs/channel/images/02-message-flow.png b/docs/internals/images/02-message-flow.png similarity index 100% rename from docs/channel/images/02-message-flow.png rename to docs/internals/images/02-message-flow.png diff --git a/docs/skills/images/02-skill-sources.png b/docs/internals/images/02-skill-sources.png similarity index 100% rename from docs/skills/images/02-skill-sources.png rename to docs/internals/images/02-skill-sources.png diff --git a/docs/channel/images/03-access-control.png b/docs/internals/images/03-access-control.png similarity index 100% rename from docs/channel/images/03-access-control.png rename to docs/internals/images/03-access-control.png diff --git a/docs/features/images/03-computer-use-python-bridge.jpg b/docs/internals/images/03-computer-use-python-bridge.jpg similarity index 100% rename from docs/features/images/03-computer-use-python-bridge.jpg rename to docs/internals/images/03-computer-use-python-bridge.jpg diff --git a/docs/memory/images/03-memory-trigger.png b/docs/internals/images/03-memory-trigger.png similarity index 100% rename from docs/memory/images/03-memory-trigger.png rename to docs/internals/images/03-memory-trigger.png diff --git a/docs/skills/images/03-skill-invocation.png b/docs/internals/images/03-skill-invocation.png similarity index 100% rename from docs/skills/images/03-skill-invocation.png rename to docs/internals/images/03-skill-invocation.png diff --git a/docs/en/agent/images/03-spawn-flow.png b/docs/internals/images/03-spawn-flow.png similarity index 100% rename from docs/en/agent/images/03-spawn-flow.png rename to docs/internals/images/03-spawn-flow.png diff --git a/docs/en/agent/images/04-agent-teams.png b/docs/internals/images/04-agent-teams.png similarity index 100% rename from docs/en/agent/images/04-agent-teams.png rename to docs/internals/images/04-agent-teams.png diff --git a/docs/features/images/04-computer-use-patch.jpg b/docs/internals/images/04-computer-use-patch.jpg similarity index 100% rename from docs/features/images/04-computer-use-patch.jpg rename to docs/internals/images/04-computer-use-patch.jpg diff --git a/docs/memory/images/04-memory-lifecycle.png b/docs/internals/images/04-memory-lifecycle.png similarity index 100% rename from docs/memory/images/04-memory-lifecycle.png rename to docs/internals/images/04-memory-lifecycle.png diff --git a/docs/channel/images/04-permission-relay.png b/docs/internals/images/04-permission-relay.png similarity index 100% rename from docs/channel/images/04-permission-relay.png rename to docs/internals/images/04-permission-relay.png diff --git a/docs/skills/images/04-skills-architecture.png b/docs/internals/images/04-skills-architecture.png similarity index 100% rename from docs/skills/images/04-skills-architecture.png rename to docs/internals/images/04-skills-architecture.png diff --git a/docs/memory/images/05-architecture-overview.png b/docs/internals/images/05-architecture-overview.png similarity index 100% rename from docs/memory/images/05-architecture-overview.png rename to docs/internals/images/05-architecture-overview.png diff --git a/docs/en/agent/images/05-architecture.png b/docs/internals/images/05-architecture.png similarity index 100% rename from docs/en/agent/images/05-architecture.png rename to docs/internals/images/05-architecture.png diff --git a/docs/skills/images/05-skill-loading.png b/docs/internals/images/05-skill-loading.png similarity index 100% rename from docs/skills/images/05-skill-loading.png rename to docs/internals/images/05-skill-loading.png diff --git a/docs/en/agent/images/06-context-passing.png b/docs/internals/images/06-context-passing.png similarity index 100% rename from docs/en/agent/images/06-context-passing.png rename to docs/internals/images/06-context-passing.png diff --git a/docs/memory/images/06-path-resolution.png b/docs/internals/images/06-path-resolution.png similarity index 100% rename from docs/memory/images/06-path-resolution.png rename to docs/internals/images/06-path-resolution.png diff --git a/docs/skills/images/06-skill-injection.png b/docs/internals/images/06-skill-injection.png similarity index 100% rename from docs/skills/images/06-skill-injection.png rename to docs/internals/images/06-skill-injection.png diff --git a/docs/memory/images/07-prompt-injection.png b/docs/internals/images/07-prompt-injection.png similarity index 100% rename from docs/memory/images/07-prompt-injection.png rename to docs/internals/images/07-prompt-injection.png diff --git a/docs/skills/images/07-skill-execution.png b/docs/internals/images/07-skill-execution.png similarity index 100% rename from docs/skills/images/07-skill-execution.png rename to docs/internals/images/07-skill-execution.png diff --git a/docs/en/agent/images/07-tool-pool.png b/docs/internals/images/07-tool-pool.png similarity index 100% rename from docs/en/agent/images/07-tool-pool.png rename to docs/internals/images/07-tool-pool.png diff --git a/docs/memory/images/08-auto-extraction.png b/docs/internals/images/08-auto-extraction.png similarity index 100% rename from docs/memory/images/08-auto-extraction.png rename to docs/internals/images/08-auto-extraction.png diff --git a/docs/en/agent/images/08-background-task.png b/docs/internals/images/08-background-task.png similarity index 100% rename from docs/en/agent/images/08-background-task.png rename to docs/internals/images/08-background-task.png diff --git a/docs/skills/images/08-conditional-activation.png b/docs/internals/images/08-conditional-activation.png similarity index 100% rename from docs/skills/images/08-conditional-activation.png rename to docs/internals/images/08-conditional-activation.png diff --git a/docs/memory/images/09-memory-retrieval.png b/docs/internals/images/09-memory-retrieval.png similarity index 100% rename from docs/memory/images/09-memory-retrieval.png rename to docs/internals/images/09-memory-retrieval.png diff --git a/docs/skills/images/09-skill-lifecycle.png b/docs/internals/images/09-skill-lifecycle.png similarity index 100% rename from docs/skills/images/09-skill-lifecycle.png rename to docs/internals/images/09-skill-lifecycle.png diff --git a/docs/en/agent/images/09-teams-mailbox.png b/docs/internals/images/09-teams-mailbox.png similarity index 100% rename from docs/en/agent/images/09-teams-mailbox.png rename to docs/internals/images/09-teams-mailbox.png diff --git a/docs/memory/images/10-agent-memory.png b/docs/internals/images/10-agent-memory.png similarity index 100% rename from docs/memory/images/10-agent-memory.png rename to docs/internals/images/10-agent-memory.png diff --git a/docs/en/agent/images/10-fork-cache.png b/docs/internals/images/10-fork-cache.png similarity index 100% rename from docs/en/agent/images/10-fork-cache.png rename to docs/internals/images/10-fork-cache.png diff --git a/docs/agent/images/11-agent-framework-overview.png b/docs/internals/images/11-agent-framework-overview.png similarity index 100% rename from docs/agent/images/11-agent-framework-overview.png rename to docs/internals/images/11-agent-framework-overview.png diff --git a/docs/memory/images/11-autodream-overview.png b/docs/internals/images/11-autodream-overview.png similarity index 100% rename from docs/memory/images/11-autodream-overview.png rename to docs/internals/images/11-autodream-overview.png diff --git a/docs/agent/images/12-agent-core-loop.png b/docs/internals/images/12-agent-core-loop.png similarity index 100% rename from docs/agent/images/12-agent-core-loop.png rename to docs/internals/images/12-agent-core-loop.png diff --git a/docs/memory/images/12-autodream-trigger.png b/docs/internals/images/12-autodream-trigger.png similarity index 100% rename from docs/memory/images/12-autodream-trigger.png rename to docs/internals/images/12-autodream-trigger.png diff --git a/docs/memory/images/13-autodream-phases.png b/docs/internals/images/13-autodream-phases.png similarity index 100% rename from docs/memory/images/13-autodream-phases.png rename to docs/internals/images/13-autodream-phases.png diff --git a/docs/agent/images/13-system-prompt-pipeline.png b/docs/internals/images/13-system-prompt-pipeline.png similarity index 100% rename from docs/agent/images/13-system-prompt-pipeline.png rename to docs/internals/images/13-system-prompt-pipeline.png diff --git a/docs/agent/images/14-context-compression.png b/docs/internals/images/14-context-compression.png similarity index 100% rename from docs/agent/images/14-context-compression.png rename to docs/internals/images/14-context-compression.png diff --git a/docs/internals/index.md b/docs/internals/index.md new file mode 100644 index 00000000..ff516fa0 --- /dev/null +++ b/docs/internals/index.md @@ -0,0 +1,64 @@ +--- +title: 架构总览 +nav_title: 架构总览 +description: 五层代码怎么分工,一条消息怎么流过它们,以及改动某块功能该从哪篇读起。 +order: 0 +--- + +# 架构总览 + +Claude Code Haha 看着是一个桌面应用,实际是五块可以各自独立运行的代码拼起来的。想读源码或者提 PR,先把这五块的边界分清楚,后面每一篇都会落在其中一块里。 + +```text +desktop/src/ 前端 —— React + Zustand,只画界面,不碰系统能力 +desktop/electron/ 桌面壳 —— Electron 主进程,管窗口、更新、终端、原生预览 +src/server/ 本地 Server —— Bun.serve 的 REST + WebSocket,桌面端和手机端共用 +src/ CLI 内核 —— Agent 循环、工具系统、权限、记忆、Skills +adapters/ IM 接入 —— 每个平台一个独立 sidecar,桥回同一套会话 +``` + +三条事实值得先记住: + +- **CLI 内核是唯一执行方**。桌面端每开一个会话,Server 就拉起一个 CLI 子进程;前端点的每个按钮最后都变成发给它的一条消息。 +- **本地 Server 是唯一入口**。桌面端、手机 H5、IM adapter 走的是同一套 REST 与 WebSocket,只是鉴权等级不同。 +- **Electron 是当前桌面主路径**,`desktop/src-tauri/` 只保留打包资源和历史代码作回滚,不是运行时。 + +## CLI 内核怎么分层 + +![CLI 内核的整体分层](../images/01-overall-architecture.png) + +入口层做完初始化后分成两侧:左侧是把一次请求跑完的主链路(终端界面 → 查询引擎 → 工具系统 → 子 Agent),右侧是被主链路调用的横切能力(状态管理、Skills 与插件、MCP / OAuth / 记忆等服务)。桌面端接管了终端界面那一格,其余部分原样复用。 + +## 一条消息经过什么 + +![一次请求的生命周期](../images/02-request-lifecycle.png) + +用户输入被解析、组装上下文后发给模型,流式返回里一旦出现工具调用,就先过权限校验再执行,结果回填进上下文继续下一轮,直到模型不再请求工具。桌面端看到的「工具调用卡」「权限询问卡」,就是这条链路上两个节点的可视化。 + +## 工具和权限的关系 + +![工具系统与权限门](../images/03-tool-system.png) + +所有工具在同一个注册中心登记,按能力分成文件、命令、系统、子 Agent、外部集成和通信几类。真正决定安全边界的是下面那条固定管线:任何一次调用都要先过参数校验和权限门,再进沙箱执行。加新工具时,接进注册中心不难,难的是想清楚它落在权限门的哪一侧。 + +## 我想改 X,该看哪篇 + +| 你想动的东西 | 从这篇开始 | +|---|---| +| 窗口、托盘、自动更新、内嵌终端、原生预览 | [桌面端架构](./desktop.md) | +| REST 接口、WebSocket 事件、鉴权、Provider 代理 | [本地 Server 与 API](./server.md) | +| 不知道某个功能的代码在哪个目录 | [项目结构](./structure.md) | +| 子 Agent 的行为、内置 Agent、Agent Teams | [多 Agent 使用指南](./agent.md) | +| 子 Agent 的生成路径、工具池过滤、后台任务引擎 | [多 Agent 实现原理](./agent-internals.md) | +| Agent 主循环、系统提示词、上下文压缩 | [Agent 框架深度解析](./agent-framework.md) | +| Skill 的写法、来源优先级、触发方式 | [Skills 使用指南](./skills.md) | +| Skill 的发现、注入、fork 执行 | [Skills 实现原理](./skills-internals.md) | +| 记忆存在哪、什么时候写 | [记忆系统使用指南](./memory.md) | +| 记忆的注入、提取、检索、团队同步 | [记忆系统实现原理](./memory-internals.md) | +| 后台自动整合记忆 | [AutoDream 记忆整合](./autodream.md) | +| 屏幕控制的工具、授权、坐标换算 | [Computer Use 架构](./computer-use.md) | +| IM 消息协议、访问控制、权限中继 | [Channel 系统](./channel.md) | +| 提 PR 前要跑哪些检查、发版流程 | [参与贡献与质量门禁](./contributing.md) | +| 在终端里跑 CLI、写自动化脚本 | [CLI 安装与启动](../cli/index.md) | + +产品功能怎么用不在这个分区,从 [开始使用](../start/index.md) 和 [桌面端功能](../desktop/index.md) 进。 diff --git a/docs/memory/02-implementation.md b/docs/internals/memory-internals.md similarity index 88% rename from docs/memory/02-implementation.md rename to docs/internals/memory-internals.md index 43c2117c..055d5157 100644 --- a/docs/memory/02-implementation.md +++ b/docs/internals/memory-internals.md @@ -1,16 +1,17 @@ -# Claude Code 记忆系统 — 实现原理 +--- +title: 记忆系统实现原理 +nav_title: 记忆系统实现 +description: 路径解析、提示词注入、自动提取、智能检索与团队记忆同步。 +order: 10 +--- -> 从系统提示词注入到后台自动提取,拆解记忆系统的每一个技术细节。 +# 记忆系统实现原理 -

-整体架构 · 路径解析 · 提示词注入 · 自动提取 · 智能检索 · 记忆扫描 · 代理记忆 · 团队同步 · 常量速查 · 数据流总览 -

+从系统提示词注入到后台自动提取,拆解记忆系统的每一个技术细节。 ![实现架构总览](./images/05-architecture-overview.png) ---- - -## 一、整体架构 +## 整体架构 记忆系统由 5 个核心模块协作完成: @@ -20,22 +21,20 @@ | **提示词构建** | `src/memdir/memdir.ts` | 将记忆指令注入系统提示词 | | **记忆扫描** | `src/memdir/memoryScan.ts` | 扫描目录、解析 frontmatter、排序 | | **智能检索** | `src/memdir/findRelevantMemories.ts` | 用 Sonnet 选择与当前查询相关的记忆 | -| **自动提取** | `src/services/extractMemories/` | 后台分叉代理,从对话中提取记忆 | +| **自动提取** | `src/services/extractMemories/` | 后台分叉 Agent,从对话中提取记忆 | 辅助模块: | 模块 | 源码位置 | 职责 | |------|----------|------| -| **AutoDream** | `src/services/autoDream/` | 后台记忆整合("做梦"),详见 [03-autodream.md](./03-autodream.md) | +| **AutoDream** | `src/services/autoDream/` | 后台记忆整合("做梦"),详见 [AutoDream](./autodream.md) | | **类型定义** | `src/memdir/memoryTypes.ts` | 四种记忆类型的分类法和提示词模板 | | **新鲜度** | `src/memdir/memoryAge.ts` | 计算记忆年龄、生成陈旧警告 | | **文件检测** | `src/utils/memoryFileDetection.ts` | 判断路径是否属于记忆系统 | -| **代理记忆** | `src/tools/AgentTool/agentMemory.ts` | 子代理专属的三级记忆目录 | +| **Agent 记忆** | `src/tools/AgentTool/agentMemory.ts` | 子 Agent 专属的三级记忆目录 | | **团队同步** | `src/services/teamMemorySync/` | 记忆的远程上传/下载 | ---- - -## 二、路径解析系统 +## 路径解析系统 ![路径解析流程](./images/06-path-resolution.png) @@ -89,9 +88,7 @@ settings.json autoMemoryEnabled → 跟随设置 默认 → 开启 ``` ---- - -## 三、系统提示词注入 +## 系统提示词注入 ![提示词注入流程](./images/07-prompt-injection.png) @@ -148,9 +145,7 @@ if (bytes > 25,000) → 在最后一个换行符处截断 提示词中明确告知模型目录已存在,避免浪费 turn 执行 `ls` 或 `mkdir`。 ---- - -## 四、自动记忆提取 +## 自动记忆提取 ![自动提取流程](./images/08-auto-extraction.png) @@ -197,7 +192,7 @@ if (bytes > 25,000) → 在最后一个换行符处截断 10. 通知用户:"Memory updated in ..." ``` -### 分叉代理(Forked Agent) +### 分叉 Agent(Forked Agent) 自动提取使用 `runForkedAgent` — 这是对主会话的完美分叉: @@ -209,15 +204,15 @@ if (bytes > 25,000) → 在最后一个换行符处截断 ### 工具权限(`createAutoMemCanUseTool`) ``` -✅ 允许:Read, Grep, Glob(无限制) -✅ 允许:Bash(仅只读命令:ls, find, grep, cat, stat...) -✅ 允许:Edit/Write(仅 auto-memory 目录内) -❌ 拒绝:MCP, Agent, 非只读 Bash, 其他写操作 +允许:Read, Grep, Glob(无限制) +允许:Bash(仅只读命令:ls, find, grep, cat, stat...) +允许:Edit/Write(仅 auto-memory 目录内) +拒绝:MCP, Agent, 非只读 Bash, 其他写操作 ``` ### 互斥机制 -主代理和提取代理是**互斥的**: +主 Agent 和提取 Agent 是**互斥的**: ```typescript function hasMemoryWritesSince(messages, sinceUuid): boolean { @@ -227,7 +222,7 @@ function hasMemoryWritesSince(messages, sinceUuid): boolean { } ``` -这避免了重复保存:主代理已经写了记忆时,后台提取直接跳过。 +这避免了重复保存:主 Agent 已经写了记忆时,后台提取直接跳过。 ### 合并机制 @@ -236,9 +231,7 @@ function hasMemoryWritesSince(messages, sinceUuid): boolean { 2. 旧提取完成后,立即启动一次**尾部提取** 3. 尾部提取只处理两次调用之间新增的消息 ---- - -## 五、智能记忆检索 +## 智能记忆检索 ![记忆检索流程](./images/09-memory-retrieval.png) @@ -293,9 +286,7 @@ function memoryFreshnessText(mtimeMs: number): string { } ``` ---- - -## 六、记忆扫描详解 +## 记忆扫描详解 ### `scanMemoryFiles()` @@ -322,7 +313,7 @@ async function scanMemoryFiles(memoryDir, signal): Promise { ### `formatMemoryManifest()` -生成供 Sonnet 或提取代理消费的清单格式: +生成供 Sonnet 或提取 Agent 消费的清单格式: ``` - [feedback] testing_policy.md (2026-03-15T10:30:00.000Z): 集成测试用真实数据库 @@ -330,13 +321,11 @@ async function scanMemoryFiles(memoryDir, signal): Promise { - [project] freeze.md (2026-03-10T15:00:00.000Z): 3/5 起合并冻结 ``` ---- +## Agent 记忆(Agent Memory) -## 七、代理记忆(Agent Memory) +![Agent 记忆三级作用域](./images/10-agent-memory.png) -![代理记忆三级作用域](./images/10-agent-memory.png) - -子代理(通过 Agent 工具启动的)有独立的三级记忆系统: +子 Agent(通过 Agent 工具启动的)有独立的三级记忆系统: | 作用域 | 路径 | 说明 | |--------|------|------| @@ -344,14 +333,12 @@ async function scanMemoryFiles(memoryDir, signal): Promise { | **project** | `.claude/agent-memory/{agentType}/` | 项目级(提交到 VCS) | | **local** | `.claude/agent-memory-local/{agentType}/` | 本地级(不提交) | -代理记忆与主记忆的差异: +Agent 记忆与主记忆的差异: - 无 MEMORY.md 索引步骤(`skipIndex = true`) - 直接写文件即可,无需两步操作 - 各代理类型隔离(explorer、planner 等各有各的目录) ---- - -## 八、团队记忆同步 +## 团队记忆同步 当 `TEAMMEM` feature flag 开启时: @@ -391,9 +378,7 @@ PUT /api/claude_code/team_memory?repo={owner/repo} ← 推送 | project | 偏向团队 | | reference | 通常团队 | ---- - -## 九、关键常量速查 +## 关键常量速查 ```typescript // 索引文件 @@ -415,9 +400,7 @@ maxTurns = 5 // 分叉代理最多 5 个 turn 最多返回 5 个相关记忆 ``` ---- - -## 十、数据流总览 +## 数据流总览 ``` ┌─────────────────────────────────────────────────────┐ @@ -467,6 +450,6 @@ maxTurns = 5 // 分叉代理最多 5 个 turn │ → 合并重复 / 修正过时 / 压缩索引 │ │ → 通知用户:"Improved N memories" │ │ │ -│ 详见 03-autodream.md │ +│ 详见 autodream.md │ └─────────────────────────────────────────────────────┘ ``` diff --git a/docs/memory/01-usage-guide.md b/docs/internals/memory.md similarity index 88% rename from docs/memory/01-usage-guide.md rename to docs/internals/memory.md index 225f7dbe..4b7aa97e 100644 --- a/docs/memory/01-usage-guide.md +++ b/docs/internals/memory.md @@ -1,16 +1,17 @@ -# Claude Code 记忆系统 — 使用指南 +--- +title: 记忆系统使用指南 +nav_title: 记忆系统使用 +description: 四种记忆类型、触发保存的方式、存储位置与日常管理办法。 +order: 9 +--- -> 让 Claude Code 跨会话记住你是谁、你偏好什么、项目正在发生什么。 +# 记忆系统使用指南 -

-记忆系统 · 四种记忆类型 · 触发保存 · 存储位置 · 管理记忆 · 生命周期 · 快速参考 -

+让 Claude Code 跨会话记住你是谁、你偏好什么、项目正在发生什么。 ![记忆系统概览](./images/01-memory-overview.png) ---- - -## 一、什么是记忆系统? +## 什么是记忆系统? Claude Code 的记忆系统是一套**基于文件的持久化知识库**,让 Claude 能够跨越多次对话,持续积累对你和你的项目的理解。 @@ -23,9 +24,7 @@ Claude Code 的记忆系统是一套**基于文件的持久化知识库**,让 | 周四后冻结非关键合并 | 已有的 CLAUDE.md 内容 | | Bug 跟踪在 Linear 的 INGEST 项目 | 调试方案(修复已在代码里) | ---- - -## 二、四种记忆类型 +## 四种记忆类型 ![四种记忆类型](./images/02-memory-types.png) @@ -71,9 +70,7 @@ Claude 保存:2026-03-05 起合并冻结,标记该日期后的非关键 PR Claude 保存:grafana.internal/d/api-latency 是 oncall 延迟仪表板 — 编辑请求路径代码时检查 ``` ---- - -## 三、如何触发记忆保存 +## 如何触发记忆保存 ![记忆触发流程](./images/03-memory-trigger.png) @@ -84,8 +81,8 @@ Claude 保存:grafana.internal/d/api-latency 是 oncall 延迟仪表板 — 工作流程: 1. 你和 Claude 正常对话 2. Claude 完成回复(没有工具调用时) -3. 后台启动一个**记忆提取子代理** -4. 子代理分析最近的对话内容 +3. 后台启动一个**记忆提取子 Agent** +4. 子 Agent 分析最近的对话内容 5. 识别出值得保存的记忆 6. 写入记忆文件 + 更新 MEMORY.md 索引 @@ -121,9 +118,7 @@ Claude:[立即保存为 feedback 类型记忆] - 检测重复、过时和冲突的记忆 - **不会直接修改**,所有变更需要你批准 ---- - -## 四、记忆存储在哪里? +## 记忆存储在哪里? ### 目录结构 @@ -173,9 +168,7 @@ MEMORY.md 是索引而不是内容。它**始终加载到上下文**中,每行 **限制**:最多 200 行或 25KB,超出会被截断。 ---- - -## 五、如何管理记忆 +## 如何管理记忆 ### 让 Claude 遗忘 @@ -215,9 +208,7 @@ Claude:[本次对话中不使用任何记忆内容] 支持 `~/` 展开。出于安全考虑,项目级 `.claude/settings.json` **不允许**设置此项。 ---- - -## 六、记忆的生命周期 +## 记忆的生命周期 ![记忆生命周期](./images/04-memory-lifecycle.png) @@ -245,14 +236,14 @@ Claude:[本次对话中不使用任何记忆内容] ### AutoDream —— "做梦"整理记忆 -Claude Code 有一个隐藏的 **AutoDream** 功能,类比人类睡眠时大脑整理记忆的过程。当满足以下条件时,Claude 会在后台静默启动一个"做梦"子智能体: +Claude Code 有一个隐藏的 **AutoDream** 功能,类比人类睡眠时大脑整理记忆的过程。当满足以下条件时,Claude 会在后台静默启动一个"做梦"子 Agent: - 距上次整合 **>= 24 小时** - 期间积累了 **>= 5 个会话** 做梦过程分四阶段:定向 → 收集 → 整合 → 修剪。底部状态栏会显示 **"dreaming"**,你可以按 `Shift+Down` 查看进度,按 `x` 终止。 -详细技术分析见 [AutoDream 记忆整合](./03-autodream.md)。 +详细技术分析见 [AutoDream 记忆整合](./autodream.md)。 ### 新鲜度管理 @@ -260,9 +251,7 @@ Claude Code 有一个隐藏的 **AutoDream** 功能,类比人类睡眠时大 - **超过 1 天的记忆**:附带陈旧警告,提醒 Claude 验证后再引用 - **引用文件路径/函数名的记忆**:使用前先 grep 确认还存在 ---- - -## 七、快速参考 +## 快速参考 | 操作 | 方法 | |------|------| diff --git a/docs/reference/local-server.md b/docs/internals/server.md similarity index 96% rename from docs/reference/local-server.md rename to docs/internals/server.md index 031b1520..81c9408b 100644 --- a/docs/reference/local-server.md +++ b/docs/internals/server.md @@ -1,4 +1,11 @@ -# 本地 Server +--- +title: 本地 Server 与 API +nav_title: 本地 Server +description: 本地 Server 的启动参数、访问控制、REST 与 WebSocket 接口范围。 +order: 2 +--- + +# 本地 Server 与 API 本地 Server 是桌面端、H5 页面和 Claude CLI 之间的运行时边界。它提供 REST API、聊天 WebSocket、Provider 协议代理和桌面 Web 静态资源。打包后的桌面应用会自动管理它;只有源码开发、无界面部署或自定义客户端才需要手工启动。 @@ -62,7 +69,7 @@ CLAUDE_H5_DIST_DIR=/absolute/path/to/desktop/dist \ | `CLAUDE_H5_PUBLIC_BASE_URL` | 固定的公开服务地址 | | `CLAUDE_H5_AUTO_PUBLIC_URL=1` | 在启用 H5 时尝试生成局域网公开地址 | -完整的 Token、允许来源、手机访问和 Nginx 配置见 [H5 访问](../desktop/06-h5-access.md)。 +完整的 Token、允许来源、手机访问和 Nginx 配置见 [H5 访问](../desktop/remote.md)。 ## 访问控制 diff --git a/docs/skills/02-implementation.md b/docs/internals/skills-internals.md similarity index 96% rename from docs/skills/02-implementation.md rename to docs/internals/skills-internals.md index 29ad9e85..4ae7a329 100644 --- a/docs/skills/02-implementation.md +++ b/docs/internals/skills-internals.md @@ -1,16 +1,17 @@ -# Claude Code Skills 系统 — 实现原理 +--- +title: Skills 实现原理 +nav_title: Skills 实现 +description: Skill 如何被发现、解析、注入对话、在 fork 子 Agent 中执行。 +order: 8 +--- -> 深度剖析 Skills 如何被发现、加载、注入、执行和管理。 +# Skills 实现原理 -

-整体架构 · 发现与加载 · Frontmatter 解析 · 注入对话 · 执行引擎 · Fork 执行 · 条件激活 · Hook 集成 · 权限系统 · 完整生命周期 · 源码索引 -

+深度剖析 Skills 如何被发现、加载、注入、执行和管理。 ![Skills 架构概览](./images/04-skills-architecture.png) ---- - -## 一、整体架构 +## 整体架构 Skills 系统由 5 个核心模块协同工作: @@ -40,9 +41,7 @@ Skills 系统由 5 个核心模块协同工作: | Activation | `loadSkillsDir.ts` (后半段) | 条件激活和动态发现 | | Context | `forkedAgent.ts` | 上下文准备和修改 | ---- - -## 二、Skill 发现与加载 +## Skill 发现与加载 ### 加载入口 @@ -225,9 +224,7 @@ async function fetchCommandsForClient(client) { **特性门控:** `feature('MCP_SKILLS')` 控制 MCP Skills 是否可用。 ---- - -## 三、Frontmatter 解析 +## Frontmatter 解析 ### 解析流程 @@ -309,9 +306,7 @@ async getPromptForCommand(args, toolUseContext) { } ``` ---- - -## 四、Skill 注入到对话 +## Skill 注入到对话 ### 注入流程 @@ -394,9 +389,7 @@ Important: }) ``` ---- - -## 五、SkillTool 执行引擎 +## SkillTool 执行引擎 ### 工具定义 @@ -498,9 +491,7 @@ async function getAllCommands(context: ToolUseContext): Promise { } ``` ---- - -## 六、Fork 子代理执行 +## Fork 子 Agent 执行 ### 执行流程 @@ -603,9 +594,7 @@ export async function prepareForkedCommandContext( } ``` ---- - -## 七、条件激活与动态发现 +## 条件激活与动态发现 ### 条件 Skills @@ -679,9 +668,7 @@ clearCommandMemoizationCaches() 下一轮对话会加载新的 Skill 列表 ``` ---- - -## 八、Hook 集成 +## Hook 集成 ### Hook 注册 @@ -732,9 +719,7 @@ processPromptSlashCommand() └─ once: true 的 Hook 执行一次后自动移除 ``` ---- - -## 九、权限系统 +## 权限系统 ### 检查流程 @@ -775,9 +760,7 @@ SAFE_SKILL_PROPERTIES = { **核心逻辑:** 新增的 frontmatter 字段默认需要权限,除非显式加入白名单。 ---- - -## 十、完整生命周期 +## 完整生命周期 ### 数据流总览 @@ -841,9 +824,7 @@ addInvokedSkill(name, path, content, agentId) 按 agentId 作用域恢复(防止跨代理泄漏) ``` ---- - -## 十一、源码索引 +## 源码索引 ### 核心文件 diff --git a/docs/skills/01-usage-guide.md b/docs/internals/skills.md similarity index 92% rename from docs/skills/01-usage-guide.md rename to docs/internals/skills.md index 79b5adc6..e668ce0f 100644 --- a/docs/skills/01-usage-guide.md +++ b/docs/internals/skills.md @@ -1,16 +1,17 @@ -# Claude Code Skills 系统 — 使用指南 +--- +title: Skills 使用指南 +nav_title: Skills 使用 +description: Skill 的六种来源、定义格式、调用方式、执行上下文与权限控制。 +order: 7 +--- -> Skills 是 Claude Code 的扩展能力引擎,让你用 Markdown 文件定义专属的自动化工作流。 +# Skills 使用指南 -

-Skills 是什么 · 六种来源 · 定义格式 · 调用方式 · 执行上下文 · 条件激活 · 权限控制 · 快速参考 -

+Skills 是 Claude Code 的扩展能力引擎,让你用 Markdown 文件定义专属的自动化工作流。 ![Skills 系统概览](./images/01-skills-overview.png) ---- - -## 一、什么是 Skills? +## 什么是 Skills? Skills 是 Claude Code 的**可扩展能力插件系统**。每个 Skill 是一个 Markdown 文件(含 YAML frontmatter),定义了一段专门的提示词和行为配置,让 Claude 在特定场景下执行专业化的工作流。 @@ -21,13 +22,11 @@ Skills 是 Claude Code 的**可扩展能力插件系统**。每个 Skill 是一 | 专业化工作流 | 定义代码审查、TDD、调试等标准流程 | | 工具权限控制 | 限制 Skill 只能使用指定的工具 | | 模型切换 | 为不同 Skill 指定不同的模型 | -| 执行隔离 | Fork 模式在子代理中独立运行 | +| 执行隔离 | Fork 模式在子 Agent 中独立运行 | | 条件激活 | 只在操作特定文件时才激活 | | Hook 注入 | Skill 调用时自动注册生命周期钩子 | ---- - -## 二、六种 Skill 来源 +## 六种 Skill 来源 ![Skill 来源类型](./images/02-skill-sources.png) @@ -124,9 +123,7 @@ OpenAI Codex、Cursor、Gemini CLI、opencode 等工具都会扫描它。放在 **安全限制**:MCP Skills 为远程不受信来源,**禁止执行** `!`...`` 内联 shell 命令。 ---- - -## 三、Skill 定义格式 +## Skill 定义格式 ### 目录结构 @@ -202,9 +199,7 @@ hooks: | `argument-hint` | string | — | 参数提示文本 | | `version` | string | — | 版本号 | ---- - -## 四、调用方式 +## 调用方式 ![Skill 调用流程](./images/03-skill-invocation.png) @@ -255,9 +250,7 @@ Claude:[通过 SkillTool 调用 superpowers:code-reviewer] 7. Built-in Commands(内建命令) ← 最低优先级 ``` ---- - -## 五、执行上下文 +## 执行上下文 ### Inline 模式(默认) @@ -273,9 +266,9 @@ context: inline # 默认值,可省略 - `allowedTools` 限制当前轮次可用工具 - `model` 覆盖当前轮次使用的模型 -### Fork 模式(子代理) +### Fork 模式(子 Agent) -Skill 在**隔离的子代理**中运行,拥有独立的 token 预算和上下文。 +Skill 在**隔离的子 Agent**中运行,拥有独立的 token 预算和上下文。 ```yaml context: fork @@ -299,9 +292,7 @@ agent: general-purpose # 可选,指定代理类型 | 适用场景 | 简短指导、扩展上下文 | 长任务、独立运算 | | 工具限制 | contextModifier 修改 | modifiedGetAppState | ---- - -## 六、条件激活 +## 条件激活 Skill 可以通过 `paths` frontmatter 实现**按需激活**,只在操作匹配文件时才对模型可见。 @@ -338,9 +329,7 @@ paths: "src/**/*.ts, test/**/*.ts" 5. 发现新目录 → addSkillDirectories() → 加载并注册 ``` ---- - -## 七、权限控制 +## 权限控制 ### 自动允许 @@ -366,9 +355,7 @@ Allow? (y)es / (n)o / (a)lways allow / (d)eny **处理顺序:** deny 规则 → allow 规则 → 安全属性检查 → 询问用户 ---- - -## 八、快速参考 +## 快速参考 ### 创建一个 Skill diff --git a/docs/reference/project-structure.md b/docs/internals/structure.md similarity index 96% rename from docs/reference/project-structure.md rename to docs/internals/structure.md index 5393f3cf..c8dc61df 100644 --- a/docs/reference/project-structure.md +++ b/docs/internals/structure.md @@ -1,3 +1,10 @@ +--- +title: 项目结构 +nav_title: 项目结构 +description: 仓库的目录职责边界:CLI、Server、桌面端、Adapter 与文档站。 +order: 3 +--- + # 项目结构 本项目包含 CLI/TUI、本地 Server、Electron 桌面端、IM Adapter 和文档站。下面只列稳定的职责边界;具体文件以当前源码为准。 diff --git a/docs/memory/index.md b/docs/memory/index.md deleted file mode 100644 index 7709ef35..00000000 --- a/docs/memory/index.md +++ /dev/null @@ -1,121 +0,0 @@ -# Claude Code 记忆系统文档 - -> 完整的记忆系统使用指南和实现原理文档 - ---- - -## 📚 文档目录 - -### [01-usage-guide.md](./01-usage-guide.md) — 使用指南 - -面向用户的完整使用手册,涵盖: - -- **四种记忆类型**:User(用户画像)、Feedback(行为反馈)、Project(项目动态)、Reference(外部引用) -- **四种触发方式**:自动提取、显式请求、`/memory` 命令、`/remember` 命令 -- **存储格式**:YAML frontmatter + Markdown 内容 -- **管理操作**:遗忘、忽略、手动编辑、禁用、自定义目录 -- **生命周期**:从学习到注入,新鲜度管理 - -**适合人群**:所有 Claude Code 用户 - ---- - -### [02-implementation.md](./02-implementation.md) — 实现原理 - -面向开发者的技术深度解析,涵盖: - -- **5 大核心模块**:路径解析、提示词构建、记忆扫描、智能检索、自动提取 -- **路径解析系统**:优先级链、安全校验、启用条件 -- **系统提示词注入**:`loadMemoryPrompt()` → `buildMemoryLines()`,MEMORY.md 截断策略 -- **自动记忆提取**:分叉代理、互斥机制、工具权限、合并机制 -- **智能检索**:`scanMemoryFiles()` → Sonnet 选择 → 新鲜度警告 -- **代理记忆**:三级作用域(user/project/local) -- **团队记忆同步**:Pull/Push API、合并语义 -- **完整数据流**:从会话启动到上下文注入 - -**适合人群**:贡献者、架构师、想深入了解实现的开发者 - ---- - -### [03-autodream.md](./03-autodream.md) — AutoDream 记忆整合 - -Claude 的"做梦"机制——后台静默整合记忆的深度解析,涵盖: - -- **核心概念**:像人类睡眠整理记忆一样,定期回顾多个会话整合知识 -- **五重门控**:功能开关 → 时间门控(24h) → 扫描节流(10min) → 会话门控(5个) → 锁门控 -- **四阶段流程**:Orient(定向)→ Gather(收集)→ Consolidate(整合)→ Prune(修剪) -- **安全限制**:Bash 只读、写操作仅限记忆目录、PID 锁文件互斥 -- **UI 展示**:底部 "dreaming" 标签、Shift+Down 详情对话框、完成通知 -- **配置控制**:settings.json 本地开关 + GrowthBook 远程 feature flag -- **与 extractMemories 对比**:白天记笔记 vs 睡觉整理笔记本 - -**适合人群**:贡献者、架构师、对 Claude 自动化记忆管理感兴趣的开发者 - ---- - -## 🖼️ 配图说明 - -所有配图采用深色背景(#1a1a2e)+ Claude Code Haha 橙蓝品牌色(#FF7A00)风格,与 Claude Code 官方文档一致。 - -| 图片 | 说明 | 尺寸 | -|------|------|------| -| `01-memory-overview.png` | 记忆系统概览 — 四层架构(触发/类型/存储/检索) | 632 KB | -| `02-memory-types.png` | 四种记忆类型 — 2x2 网格展示 User/Feedback/Project/Reference | 507 KB | -| `03-memory-trigger.png` | 记忆触发流程 — 从对话到存储的四种路径 | 474 KB | -| `04-memory-lifecycle.png` | 记忆生命周期 — 完整循环流程 + 新鲜度判断 | 1.0 MB | -| `05-architecture-overview.png` | 实现架构总览 — 5 个核心模块 + 辅助模块 | 3.5 MB | -| `06-path-resolution.png` | 路径解析流程 — 三层优先级 + 安全校验 | 1.0 MB | -| `07-prompt-injection.png` | 提示词注入流程 — loadMemoryPrompt 分发逻辑 | 1.1 MB | -| `08-auto-extraction.png` | 自动提取流程 — 分叉代理完整流程 | 1.2 MB | -| `09-memory-retrieval.png` | 智能检索流程 — Sonnet 选择 + 新鲜度管理 | 816 KB | -| `10-agent-memory.png` | 代理记忆作用域 — 三级嵌套结构 | 523 KB | -| `11-autodream-overview.png` | AutoDream 概览 — 做梦机制的核心架构与人类睡眠类比 | 777 KB | -| `12-autodream-trigger.png` | AutoDream 触发流程 — 五重门控检查链 | 493 KB | -| `13-autodream-phases.png` | AutoDream 四阶段 — Orient/Gather/Consolidate/Prune | 602 KB | - ---- - -## 🚀 快速开始 - -### 用户 - -1. 阅读 [使用指南](./01-usage-guide.md) -2. 了解四种记忆类型和触发方式 -3. 尝试 `/memory` 和 `/remember` 命令 - -### 开发者 - -1. 阅读 [实现原理](./02-implementation.md) -2. 查看源码位置: - - `src/memdir/paths.ts` — 路径解析 - - `src/memdir/memdir.ts` — 提示词构建 - - `src/memdir/memoryScan.ts` — 记忆扫描 - - `src/memdir/findRelevantMemories.ts` — 智能检索 - - `src/services/extractMemories/` — 自动提取 -3. 理解数据流和模块交互 - ---- - -## 📝 核心概念速查 - -| 概念 | 说明 | -|------|------| -| **MEMORY.md** | 索引文件,始终加载到上下文(最多 200 行 / 25KB) | -| **主题文件** | `*.md` 文件,包含 frontmatter + 内容 | -| **自动提取** | 每次回复后后台运行,分叉代理分析对话 | -| **AutoDream** | 每 24h + 5 个会话后,后台整合/去重/修剪全部记忆 | -| **智能检索** | Sonnet 模型从所有记忆中选择最多 5 个相关的 | -| **新鲜度** | ≤1 天无警告,>1 天附带陈旧警告 | -| **分叉代理** | 共享提示词缓存,限制工具权限,最多 5 turns | -| **三级作用域** | 代理记忆:user(全局)> project(项目)> local(本地) | - ---- - -## 🔗 相关资源 - -- [Claude Code Haha 主页](/) -- [记忆系统源码](https://github.com/NanmiCoder/cc-haha/tree/main/src/memdir/) -- [自动提取服务](https://github.com/NanmiCoder/cc-haha/tree/main/src/services/extractMemories/) -- [AutoDream 服务](https://github.com/NanmiCoder/cc-haha/tree/main/src/services/autoDream/) -- [DreamTask 任务](https://github.com/NanmiCoder/cc-haha/tree/main/src/tasks/DreamTask/) -- [GitHub Issues](https://github.com/NanmiCoder/cc-haha/issues) diff --git a/docs/public/desktop/electron-migration-qa-checklist.html b/docs/public/desktop/electron-migration-qa-checklist.html deleted file mode 100644 index 8470647f..00000000 --- a/docs/public/desktop/electron-migration-qa-checklist.html +++ /dev/null @@ -1,563 +0,0 @@ - - - - - - Electron 迁移交互式验收清单 - - - -
-

Electron 迁移交互式验收清单

-

覆盖 Tauri 2 到 Electron 迁移后的桌面能力、构建发布、实机 smoke 和当前 release gate blocker。进度只保存在本机浏览器 localStorage。

-
-
- - - -
-
-
-
-
- - -
- - -
- - -
-
-
- - - - diff --git a/docs/reference/fixes.md b/docs/reference/fixes.md deleted file mode 100644 index 1c7acc55..00000000 --- a/docs/reference/fixes.md +++ /dev/null @@ -1,13 +0,0 @@ -# 相对于原始泄露源码的修复 - - -泄露的源码无法直接运行,主要修复了以下问题: - -| 问题 | 根因 | 修复 | -|------|------|------| -| TUI 不启动 | 入口脚本把无参数启动路由到了 recovery CLI | 恢复走 `cli.tsx` 完整入口 | -| 启动卡死 | `verify` skill 导入缺失的 `.md` 文件,Bun text loader 无限挂起 | 创建 stub `.md` 文件 | -| `--print` 卡死 | `filePersistence/types.ts` 缺失 | 创建类型桩文件 | -| `--print` 卡死 | `ultraplan/prompt.txt` 缺失 | 创建资源桩文件 | -| **Enter 键无响应** | `modifiers-napi` native 包缺失,`isModifierPressed()` 抛异常导致 `handleEnter` 中断,`onSubmit` 永远不执行 | 加 try-catch 容错 | -| setup 被跳过 | `preload.ts` 自动设置 `LOCAL_RECOVERY=1` 跳过全部初始化 | 移除默认设置 | diff --git a/docs/start/first-session.md b/docs/start/first-session.md new file mode 100644 index 00000000..e66cf763 --- /dev/null +++ b/docs/start/first-session.md @@ -0,0 +1,103 @@ +--- +title: 跑通第一条会话 +nav_title: 第一条会话 +description: 选目录、定权限、说目标、看它改文件、逐行审阅——一轮完整流程。 +order: 3 +--- + +# 跑通第一条会话 + +模型接好了,现在让它真的动一次手。找一个你不心疼的项目——最好是有 Git 的,改坏了能一键还原。 + +## 1. 新建会话,选好项目目录 + +点侧边栏的「新建会话」,或按 `Cmd/Ctrl + N`。 + +![空会话首屏,底部输入框里有权限模式、运行位置和模型三颗按钮](../images/app/session-new.webp) + +看输入框底部这一排:`+` 是附件,往右依次是**权限模式**、**运行位置**、**模型 + 推理强度**,最右边是「运行」。 + +先点中间那颗**运行位置** pill(截图里显示的是 `task-board / main`),选一个项目文件夹。这一步定的是 Agent 的活动边界:它读文件、搜索、跑命令、看 Git 状态,全都只在这个目录里进行。 + +如果这个目录是 Git 仓库,pill 里还能选分支,以及要不要开「独立工作树」。第一次先别开——直接在当前目录里跑,改了什么最直观。 + +## 2. 权限模式:从「询问权限」开始 + +点权限模式按钮,五个档位摊开在你面前。 + +![五档权限模式菜单](../images/app/permission-modes.webp) + +| 模式 | App 里的说明 | +|---|---| +| **询问权限** | CLI 请求时确认文件编辑和高风险命令 | +| **自动接受编辑** | Claude 无需询问即可写入磁盘 | +| **自动模式**(需开启一次) | Claude 会审查工具调用,并执行其认为安全的操作 | +| **计划模式** | 仅架构和推理,不操作文件 | +| **跳过权限**(高风险) | 对 Shell 和文件系统的完整工具访问 | + +**第一次请保持「询问权限」。** 每一次写文件、每一条有风险的命令,它都会停下来问你,你能亲眼看清它想干什么。等你摸清了它的行为模式,再往上放权也不迟。 + +后面四档的分寸: + +- **自动接受编辑** 只放开文件写入,命令仍然要问。适合"改动范围我已经想清楚了"的场景。 +- **自动模式** 需要手动启用一次,之后由 Claude 自己审查每次工具调用,执行它认为安全的、拦下它认为有风险的。**它会减少询问,但不保证安全**——只在隔离环境里用。 +- **计划模式** 完全不碰文件,只出方案。做只读调研、让它先讲清楚打算怎么改,用这个。 +- **跳过权限** 关掉全部检查,Shell 和文件系统完全放开。它能删掉任何文件、跑任何命令,而且不会问你。**不要在有生产密钥、个人资料,或者没有版本控制的目录里开它。** + +:::danger +「跳过权限」的风险跟「自动模式」不在一个量级上。自动模式至少还有一层 Claude 自己的审查,跳过权限连这层都没有。 +::: + +会话跑到一半时权限模式是锁住的——界面上的选择和实际生效的权限必须一致。要改就先等这一轮结束,或者按 `Cmd/Ctrl + .` 停下来。 + +## 3. 说出你想要什么 + +用大白话就行,不用装成技术需求文档。关键是**目标可验证**——说完你自己知道怎么检查它做没做到。 + +比如: + +```text +读一下这个项目,然后给任务列表加一个「按截止日期排序」的开关: +放在标题右侧,点一下就在「手动顺序」和「按截止日期」之间切换, +没有日期的排在最后。改完告诉我动了哪几个文件。 +``` + +拿不准就先只读地问一句:「读一下这个项目,告诉我它是干什么的、怎么启动,不要改任何文件。」 + +按 `Enter` 发送(`Shift + Enter` 换行)。 + +## 4. 看它干活 + +![完整一轮:工具调用折叠卡、思考、权限询问卡、内联 diff](../images/app/session-main.webp) + +它会先摸清情况再动手,你能看到几种不同的卡片: + +- **工具调用卡**(「查找到文件, 执行了一条命令, 读取了 4 个文件」)— 折叠着的,点开看它到底读了什么、跑了什么。 +- **思考块** — 它对下一步的判断。 +- **权限询问卡**(「允许 Claude Edit index.html?」)— 停在这里等你。卡片里直接带一份变更预览,绿色是新增行,红色是删除行。**先把这段 diff 看完再决定**。 + +三个按钮: + +- **允许** — 只放行这一次。 +- **本次会话允许** — 同类操作在这条会话里不再问。改动范围已经清楚了就用它,能省掉一连串重复点击。 +- **拒绝** — 打回去。它会换个思路,或者反过来问你要更多信息。 + +想中途叫停,点输入框右下角的停止按钮或按 `Cmd/Ctrl + .`。 + +## 5. 逐个文件审阅 + +每次编辑落盘后,会话里会直接展示一份**内联 diff**:文件路径、`+8 / -3` 的增删统计、左右对照的行号,语法高亮照旧。看会话流里的 diff 适合跟进单次改动。 + +一轮跑完,改动多起来了,就打开右侧**工作区**(标签栏右上角的「显示工作区」按钮)。那里把这一轮改过的文件列成一张清单,能逐个点开做完整评审,还能对着某一行写评论、把评论连同代码位置一起带回输入框继续让它改。 + +工作区的完整用法见 [工作区](../desktop/workspace.md)。 + +:::warning +被你拒绝掉的编辑不会落到磁盘上,但最终事实源永远是磁盘和 `git diff`。交付之前自己跑一遍 `git status` 和 `git diff`,别只信界面。 +::: + +## 接下来 + +- 想看看还有哪些能力 — [桌面端功能地图](../desktop/index.md) +- 出门了想在手机上接着聊 — [手机与 IM 接力](../desktop/remote.md) +- 哪一步卡住了 — [装不上 / 打不开 / 连不上](./troubleshooting.md) diff --git a/docs/start/index.md b/docs/start/index.md new file mode 100644 index 00000000..53a4e9c6 --- /dev/null +++ b/docs/start/index.md @@ -0,0 +1,56 @@ +--- +title: Claude Code Haha 是什么 +nav_title: 这是什么 +description: 跑在你自己电脑上的 AI 编程工作台:模型自己接,改动你审阅。 +order: 0 +--- + +# Claude Code Haha 是什么 + +它是一个装在你电脑上的应用。你把一个项目文件夹交给它,用大白话说出你想要什么,它去读代码、改文件、跑命令——每一处改动都摆在你眼前,等你点头才算数。 + +![一条完整会话:提问、工具调用、编辑文件、内联 diff](../images/app/session-main.webp) + +上图是一条真实会话:左边是项目和历史,中间是对话,Claude 读完文件后直接改,改了什么就在下面逐行标出来。 + +## 和 Claude Code CLI 是什么关系 + +Claude Code 是 Anthropic 出的命令行编程 Agent,跑在终端里。Claude Code Haha 的内核就是从 Claude Code 源码修复而来的一份 CLI(仓库里叫 `claude-haha`),桌面端是包在它外面的图形界面。 + +对你来说,这意味着两件事: + +- **不用先装 Claude Code。** CLI 内核已经打进安装包,装完应用就能用,不需要 Node.js、npm 或任何全局命令。 +- **终端里能干的,这里都能干。** 权限审批、子 Agent、Skills、MCP、记忆,都是同一套东西,只是从滚动的终端输出变成了看得懂的界面。 + +想直接用终端的人也没被落下,源码里的 CLI 一直在,见 [命令行](../cli/index.md)。 + +## 它能帮你做什么 + +**写代码。** 描述一个目标——加个功能、修个 bug、换套配色——它自己找文件、读上下文、动手改,改完告诉你动了哪几个文件。 + +**审改动。** 每次编辑都带一份内联 diff,右侧工作区还会把这一轮改过的文件列成清单,能逐个点开左右对照。不满意就打回去重改。 + +**派 Agent。** 大任务可以拆给子 Agent 并行跑,你在活动面板里看它们各自的进度。也可以在设置里给不同 Agent 配不同的模型、工具和系统提示词。 + +**定时跑。** 每天早上整理一次日志、每周跑一遍依赖检查——把任务设成定时的,到点它自己开工,跑完留一份记录。 + +**手机上继续。** 开一次 H5 访问,扫码就能在手机上接着刚才那条会话;也可以把飞书、Telegram、微信这些聊天软件接上,在对话框里指挥它干活。 + +## 三条底线 + +**本地优先。** 会话、设置、记忆、技能都存在你自己的机器上(默认在 `~/.claude` 下)。没有账号体系,没有云端同步,不上传你的代码。唯一的对外流量是你自己配的模型服务。 + +**模型自选。** 不绑定任何一家。Claude、ChatGPT、Grok 的官方账号可以直接登录;DeepSeek、Kimi、智谱 GLM 这些第三方 API 有现成预设;LM Studio、Ollama 跑的本地模型也接得上,一分钱不花。 + +**改动你说了算。** 默认是「询问权限」模式:写文件、跑命令之前都会停下来问你一句。你可以放宽到自动接受编辑,也可以收紧到只让它做规划不许碰文件——五档权限,随时切换。 + +## 从这里开始 + +按顺序走,大约二十分钟能跑通第一条会话: + +1. [下载与安装](./install.md) — 三个平台的安装包,以及系统拦下来时怎么办。 +2. [连接模型服务](./models.md) — 官方账号、第三方 API、本地模型,三种接法挑一种。 +3. [跑通第一条会话](./first-session.md) — 选目录、选权限、说目标、看它干活、审改动。 +4. [桌面端功能地图](../desktop/index.md) — 跑通之后,看看还有哪些能力可以用上。 + +中途卡住了,去 [装不上 / 打不开 / 连不上](./troubleshooting.md) 按症状找。 diff --git a/docs/start/install.md b/docs/start/install.md new file mode 100644 index 00000000..4cf0f59e --- /dev/null +++ b/docs/start/install.md @@ -0,0 +1,121 @@ +--- +title: 下载与安装 +nav_title: 下载与安装 +description: macOS、Windows、Linux 三个平台的安装包,以及系统拦截时的处理办法。 +order: 1 +--- + +# 下载与安装 + +装完就能用,不需要另外安装 Node.js、Python 或 Claude Code——CLI 内核和文件搜索用的 ripgrep 都已经打进安装包里了。 + +## 挑对安装包 + +所有安装包都在 [GitHub Releases](https://github.com/NanmiCoder/cc-haha/releases/latest),按系统和 CPU 架构选一个: + +| 你的系统 | 下载 | +|---|---| +| macOS,M 系列芯片 | `Claude-Code-Haha-<版本>-mac-arm64.dmg` | +| macOS,Intel 芯片 | `Claude-Code-Haha-<版本>-mac-x64.dmg` | +| Windows x64 | `Claude-Code-Haha-<版本>-win-x64.exe` | +| Windows ARM64 | `Claude-Code-Haha-<版本>-win-arm64.exe` | +| Linux x64 | `Claude-Code-Haha-<版本>-linux-x86_64.AppImage` 或 `-linux-amd64.deb` | +| Linux ARM64 | `Claude-Code-Haha-<版本>-linux-arm64.AppImage` 或 `-linux-arm64.deb` | + +不确定自己是哪种架构:macOS 看「关于本机」里的芯片型号,Windows 看「设置 → 系统 → 系统信息」里的系统类型。别只凭机器牌子猜。 + +`.blockmap` 和 `latest*.yml` 是应用自动更新用的,不用手动下载。 + +## macOS + +1. 双击 DMG。 +2. 把 Claude Code Haha 拖进「应用程序」。 +3. 从「应用程序」打开。 + +### 如果提示「已损坏,无法打开」 + +这不是文件坏了。macOS 会给从网上下载的安装包打一个隔离标记(quarantine),遇到没有 Apple 签名的应用就直接拒绝启动,报错文案偏偏写成「已损坏」。经过签名和公证的正式版本不会有这一步;如果你拿到的是未签名构建,用下面两种办法之一放行。 + +**办法一:用官方脚本(推荐)** + +从同一个 Release 里把 `install-macos-unsigned.sh` 下载到和 DMG **同一个文件夹**(比如「下载」),然后在终端里跑: + +```bash +cd ~/Downloads +bash install-macos-unsigned.sh +``` + +脚本会自动挑对架构的 DMG、挂载、把应用装进 `/Applications`、清掉隔离标记再打开。已有的旧版本会先移到废纸篓,不会直接覆盖。 + +**办法二:手动清隔离标记** + +已经把应用拖进「应用程序」了,就直接执行: + +```bash +xattr -dr com.apple.quarantine "/Applications/Claude Code Haha.app" +``` + +只对你确认来自本仓库 Release 的安装包这么做。来路不明的应用不要绕过 Gatekeeper。 + +## Windows + +1. 先把正在运行的旧版本完全退出,包括系统托盘里的图标。 +2. 双击 `.exe` 安装。 +3. **不要**右键选「以管理员身份运行」——安装器是给当前用户装的,用管理员身份反而会让数据目录对不上。 + +未签名的安装包会触发 SmartScreen 蓝屏提示。确认文件确实来自本仓库 Release 后,点「更多信息」→「仍要运行」。 + +覆盖升级时安装器会检查旧安装目录里的用户数据。如果它提示「程序仍在运行」,退出主窗口和托盘图标,等几秒让后台的 sidecar、终端和 IM adapter 退干净,再重新运行安装器。别先手动删掉旧安装目录。 + +## Linux + +**AppImage**(免安装,下载即用): + +```bash +chmod +x Claude-Code-Haha-<版本>-linux-x86_64.AppImage +./Claude-Code-Haha-<版本>-linux-x86_64.AppImage +``` + +启动失败并提示 FUSE 相关错误时,装一下运行库:Ubuntu 22.04 及更早用 `sudo apt install libfuse2`,24.04 及以后用 `libfuse2t64`。 + +**deb**(装进系统菜单): + +```bash +sudo apt install ./Claude-Code-Haha-<版本>-linux-amd64.deb +``` + +ARM64 机器换成对应的 `linux-arm64` 文件。 + +## 从源码跑 + +想改代码、调试内核,或者只想在终端里用 CLI,可以从源码起: + +```bash +git clone https://github.com/NanmiCoder/cc-haha.git +cd cc-haha +bun install +cp .env.example .env +./bin/claude-haha +``` + +需要 [Bun](https://bun.sh) 和 Git。这条路只跑 CLI,桌面端的构建方式和本地服务参数见 [命令行](../cli/index.md)。 + +## 升级 + +**应用内更新(推荐)。** 打开「设置 → 关于 → 应用更新」,点「检查更新」。它会比对当前版本和 GitHub Releases 上的最新版本,有新版就下载,下载完提示「安装并重启」。 + +更新前先把正在跑的会话停掉,未提交的代码存好。 + +下载卡住不动,多半是网络到不了 GitHub。同一个面板里有「高级更新代理」,可以切成系统代理或填一个本地 HTTP 代理地址(例如 `http://127.0.0.1:7890`)。这个代理只管应用自己的更新下载,不影响模型请求。 + +**手动换包。** 从 Releases 下新版安装包,按上面各平台的步骤重装一遍即可。会话、服务商配置、技能、Agent、记忆都存在 `~/.claude` 下,不在应用目录里,覆盖安装不会动它们。 + +:::warning +安装器的数据保护不等于备份。真正重要的东西请自己另存一份。 +::: + +## 装完之后 + +去 [连接模型服务](./models.md)。没接模型之前,应用能打开,但发不出任何一条消息。 + +装不上或者打不开,看 [装不上 / 打不开 / 连不上](./troubleshooting.md)。 diff --git a/docs/start/models.md b/docs/start/models.md new file mode 100644 index 00000000..100e4c42 --- /dev/null +++ b/docs/start/models.md @@ -0,0 +1,124 @@ +--- +title: 连接模型服务 +nav_title: 连接模型 +description: 官方账号、第三方 API、本地模型,三种接法任选一种,十分钟内跑起来。 +order: 2 +--- + +# 连接模型服务 + +Claude Code Haha 自己不带模型,它只是那个替你干活的壳。装完之后第一件事,是给它接一个能思考的大脑。 + +点侧边栏最底部的「设置」,选左边第一栏「服务商」。接下来你有三条路: + +- **有官方账号** — Claude、ChatGPT、Grok 三张内置卡,点一下走浏览器登录,不用填 API 密钥。 +- **有第三方 API 密钥** — DeepSeek、Kimi、智谱 GLM 这些都有现成预设,填个密钥就能用。 +- **想完全免费** — 本机跑 LM Studio 或 Ollama,模型在你自己的显卡上,一分钱不花,断网也能用。 + +三条路可以同时配好,在服务商列表里随时切换。 + +## 官方账号登录 + +「设置 → 服务商」页面顶部有三张卡: + +| 卡片 | 需要什么 | +|---|---| +| **Claude 官方** | 一个 Claude.ai 账号(Pro / Max 订阅,或有额度的 API 账号) | +| **ChatGPT 官方** | 一个 ChatGPT 账号,走 OpenAI OAuth | +| **Grok 官方** | 一个 xAI 账号,走 Grok 官方 OAuth | + +点卡片上的登录按钮(「登录 Claude 账号」/「登录 ChatGPT」/「登录 Grok」),系统浏览器会打开对应的授权页面。用同一个账号完成授权后,浏览器会自动跳回应用,卡片上显示「已登录」。 + +三条注意事项: + +- 授权全程别关掉 Claude Code Haha,回调要落回正在运行的应用。 +- 浏览器没自动打开,就点「复制授权链接」,自己粘到浏览器里。 +- 挂了代理或装了拦截类扩展时,授权页和本机回调都可能被挡住。授权失败先把它们关掉再试。 + +登录之后能用哪些模型,取决于你的账号权限和订阅档位——文档里出现过某个模型名,不代表你的账号一定能调。 + +## 第三方 API 服务商 + +有 API 密钥的话,这条路最省事:点「添加服务商」,在「预设」里选一个,接口地址和默认模型会自动填好,你只需要粘贴密钥。 + +内置预设(按弹窗里的排列): + +- **DeepSeek** · **Zhipu GLM** · **Kimi** · **MiniMax** — 国内主流模型厂商,接口地址是各家的 Anthropic 兼容端点。 +- **接口AI** · **胜算云** · **TeamoRouter** — 中转类服务商,用它们的通道调 Claude 官方模型。 +- **LM Studio** · **Ollama** — 本地模型,见下一节。 +- **Custom** — 上面都没有,自己填。 + +选中预设后,如果这家服务商有申请密钥的页面,密钥输入框下面会出现一颗「获取 API Key」按钮,点开就是注册/取密钥的地址。 + +## 本地模型 + +想彻底不花钱、不联网,就在本机跑一个模型服务,让应用连过去。 + +**LM Studio**:在 LM Studio 里加载好模型并启动本地服务器,然后在「添加服务商」里选 `LM Studio` 预设,接口地址填 `http://localhost:1234`。 + +**Ollama**:`ollama serve` 起来之后,选 `Ollama` 预设,接口地址填 `http://localhost:11434`。 + +两条硬性要求: + +1. **接口地址后面不要加 `/v1`。** 这两家都提供 Anthropic 兼容协议,应用走的是那条路径,多加 `/v1` 会直接 404。 +2. **把上下文窗口调大,建议至少 200K。** Claude Code 的系统提示词、工具定义和 Skills 本身就要吃掉不少上下文,默认的 4K/8K 窗口连开场都放不下。这个设置在 LM Studio 或 Ollama 自己的模型配置里改,不在本应用里。 + +本地模型能不能撑住完整的 Agent 工作流,取决于模型本身的工具调用能力。小参数量模型经常出现"一直说话但不动手"的情况——那是模型的问题,不是配置的问题。 + +## 「添加服务商」弹窗逐个字段 + +![添加服务商弹窗:预设、接口地址、认证变量、API 密钥、模型映射](../images/app/settings-provider-add.webp) + +**名称**(必填)— 显示在服务商列表里的名字。选了预设会自动填,可以改成你认得出的,比如「DeepSeek-工作号」。 + +**备注** — 给自己看的一句话,比如"月底到期"。不影响任何请求。 + +**接口地址**(必填)— 服务商的 API 根地址,不是它的官网地址。预设会自动填对。自己填的时候注意别把接口路径写重复了:如果地址里已经有 `/anthropic`,后面就不要再补 `/v1/messages`。 + +**认证变量** — 决定密钥以什么形式发出去,五个选项: + +| 选项 | 什么时候用 | +|---|---| +| API Key (`ANTHROPIC_API_KEY`) | 直连 Anthropic 官方 API,发 `x-api-key` 头 | +| Bearer Token (`ANTHROPIC_AUTH_TOKEN`) | 绝大多数第三方 Anthropic 兼容服务,发 `Authorization: Bearer` | +| Bearer + 清空 API_KEY | OpenRouter、Ollama 这类,必须避免回退到 Anthropic 密钥 | +| 两个变量写同一个 Token | Hugging Face Router 这类同时检查两个变量的服务 | +| 两个变量写 dummy | 本地 vLLM 这类只需要占位认证值的服务 | + +预设会自动选对。自己填的时候拿不准就先用 Bearer Token,401 了再换 API Key。 + +**启用 Tool Search**(默认开)— 开着的时候,MCP 工具和一部分工具定义按需加载,能省下首轮很大一块 schema token。但它依赖模型支持 `tool_reference`。**如果接的是能力较弱的模型,或者服务商直接拒绝这种请求形态,把它关掉。** + +**关闭实验性 Beta 头** — 给这个服务商设置 `CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1`。有些第三方通道见到 beta 形态的 API 请求会直接报错,勾上它就不发这些头了。**报 400 或者"不支持的参数"时优先试这个。** + +**API 密钥**(必填,本地模型除外)— 从服务商控制台复制过来的密钥。粘贴时注意别带上前后的空格。密钥存在本机,不会上传到任何地方。 + +**模型映射** — 四个槽位,填的是**模型 ID**,不是模型的中文名: + +- **主模型**(必填)— 日常对话用的模型,必须支持工具调用。 +- **Haiku 模型** — 留空则跟随主模型。这个槽位承担标题生成、文件摘要这类轻量活儿,映射到一个便宜的小模型能省不少钱。 +- **Sonnet 模型 / Opus 模型** — 留空则跟随主模型。用得上分档时再填。 + +每个槽位下面有个 `1M` 复选框,只有当这个模型确实支持一百万 token 上下文时才勾。 + +**API 格式** — 这一项只在选了 `Custom` 预设、或者编辑已有服务商时才出现,三个选项:Anthropic Messages(原生)、OpenAI Chat Completions(代理转换)、OpenAI Responses API(代理转换)。后两种会由应用在本机起一个回环代理做协议转换,你不用自己部署任何东西。所有内置预设走的都是 Anthropic 原生,所以看不到这一项。 + +填完点「添加」。 + +## 保存之后 + +回到服务商列表,对着刚加的这一条: + +1. 点「测试」。Anthropic 原生格式只测一步「① 连通」;OpenAI 格式会多测一步「② 代理转换」。两步都绿了才算通。 +2. 点「设为默认」,让新会话默认用它。 +3. 多个服务商可以拖动排序,顺序只影响列表显示。 + +然后新建一条会话,**在输入框右下角的模型选择器里挑具体模型**——那里列出的才是当前服务商真正能用的模型。旁边那颗按钮是推理强度,不确定就保持默认。 + +:::tip +测试通过不等于万事大吉。它只证明接口通、认证对,不代表这个模型能撑住工具调用和长上下文。真正的验收是发一条会真的改文件的任务,看它有没有动手。 +::: + +连不上、401、模型列表是空的?去 [装不上 / 打不开 / 连不上](./troubleshooting.md) 的「模型连不通」一节。 + +模型接好了,就可以 [跑通第一条会话](./first-session.md) 了。 diff --git a/docs/start/troubleshooting.md b/docs/start/troubleshooting.md new file mode 100644 index 00000000..f6f7db24 --- /dev/null +++ b/docs/start/troubleshooting.md @@ -0,0 +1,211 @@ +--- +title: 装不上 / 打不开 / 连不上 +nav_title: 排查问题 +description: 按症状查:安装失败、启动白屏、模型 401、会话卡住、端口冲突、手机连不上。 +order: 4 +--- + +# 装不上 / 打不开 / 连不上 + +按你看到的现象往下找。每条都是「现象 → 为什么 → 怎么办」。 + +先确认一件事:你用的是 [GitHub Releases](https://github.com/NanmiCoder/cc-haha/releases/latest) 上的最新正式版。旧版本上的问题很多已经修掉了。 + +## 装不上 + +### macOS 提示「已损坏,无法打开」 + +**为什么** — 文件没坏。macOS 会给从网上下载的包打一个隔离标记,遇到没有 Apple 签名的应用就拒绝启动,而报错文案写成了「已损坏」,非常有误导性。 + +**怎么办** — 从同一个 Release 下载 `install-macos-unsigned.sh`,放到和 DMG 同一个文件夹,执行 `bash install-macos-unsigned.sh`;或者应用已经在「应用程序」里的话,直接执行 `xattr -dr com.apple.quarantine "/Applications/Claude Code Haha.app"`。完整说明见 [下载与安装](./install.md)。 + +### Windows 弹出 SmartScreen 蓝屏 + +**为什么** — 未签名的安装包会被 SmartScreen 拦一道。 + +**怎么办** — 确认文件确实来自本仓库 Release,点「更多信息」→「仍要运行」。文件名或来源对不上就别绕过。 + +### Windows 安装器说「程序仍在运行」 + +**为什么** — 旧版本的主进程、sidecar、内嵌终端或 IM adapter 还没退干净,安装器不敢覆盖。 + +**怎么办** + +1. 退出主窗口,检查系统托盘里的图标也一并退出。 +2. 等几秒让后台进程结束。 +3. 还不行就去任务管理器里找 Claude Code Haha 相关进程手动结束。 +4. 重新双击安装器。**不要**选「以管理员身份运行」,也**不要**先手动删掉旧安装目录里的数据。 + +### Linux AppImage 双击没反应 + +**为什么** — 多半是没给执行权限,或者系统缺 FUSE。 + +**怎么办** — 先 `chmod +x <文件名>.AppImage`。仍报 FUSE 相关错误的话,Ubuntu 22.04 及更早装 `libfuse2`,24.04 及以后装 `libfuse2t64`。 + +## 打不开 + +### 双击后什么都没发生 + +**为什么** — 最常见的是下错了 CPU 架构:Apple Silicon 的机器装了 `mac-x64`,或者 ARM64 的 Windows 装了 `win-x64`。 + +**怎么办** — 对照 [下载与安装](./install.md) 重新确认架构,下对的那个包重装。旧版进程还在跑的话也会互相打架,先全部退掉。 + +### 窗口打开了,但一片白 + +**为什么** — 界面资源没加载出来,通常是升级过程中断,或者显卡驱动异常导致渲染进程起不来。 + +**怎么办** + +1. 完全退出应用(不是关窗口),重新打开。 +2. 还白就重装同一个版本的安装包覆盖一次。会话和配置存在 `~/.claude` 下,不在应用目录里,覆盖安装不会丢。 +3. 仍然白屏,说明卡在启动阶段。带上系统版本、CPU 架构、安装包文件名去 [GitHub Issues](https://github.com/NanmiCoder/cc-haha/issues) 提一条。 + +:::warning +任何情况下都不要为了排错去删 `~/.claude`。你的会话、服务商配置、技能、Agent、记忆全在那里,删了找不回来。 +::: + +### 升级后会话列表空了 + +**为什么** — 大概率不是数据没了。侧边栏的会话列表走的是本地 SQLite 索引,那只是一份可重建的派生数据,原始会话仍以 JSON / JSONL 文件存着。索引正在重建时列表会暂时是空的。 + +**怎么办** — 打开「设置 → 诊断 → 本地索引」,看它是不是正在构建,等它跑完。有降级来源或错误代码就点「重建本地索引」——这个操作只重建索引,不碰原始对话和设置。 + +## 模型 401 或连不通 + +### 提示 401 / 403 / API Key invalid + +**为什么** — 认证方式和服务商对不上,或者密钥本身有问题。 + +**怎么办** — 打开「设置 → 服务商」,编辑那条配置,按顺序核对: + +1. **接口地址**是不是 API 根地址,而不是官网地址。 +2. **认证变量**选对没有。第三方 Anthropic 兼容服务基本都用 `Bearer Token (ANTHROPIC_AUTH_TOKEN)`,只有直连 Anthropic 官方才用 `API Key (ANTHROPIC_API_KEY)`。拿不准就两个都试一遍。 +3. **API 密钥**前后有没有粘进空格,或者已经过期、额度用完。 +4. 保存后点「测试」,通过再新建一条会话验证。 + +不要为了改这些去手动编辑 `settings.json`,应用里改完会自动同步。 + +### 提示 404 或「模型不存在」 + +**为什么** — 接口路径写重了,或者模型 ID 用了别的平台的别名。 + +**怎么办** — 检查接口地址里的路径有没有重复(地址里已经有 `/anthropic` 就别再补 `/v1/messages`)。模型 ID 用服务商控制台里给的那个原样字符串,别用产品名。 + +本地模型(LM Studio / Ollama)**接口地址后面不要加 `/v1`**,这是最常见的 404 来源。 + +### 报 400,或提示不支持某个参数 + +**为什么** — 第三方通道拒绝了 beta 形态的 API 请求,或者模型不支持 `tool_reference`。 + +**怎么办** — 编辑这个服务商,勾上「关闭实验性 Beta 头」;还不行就把「启用 Tool Search」关掉。这两个开关就是为这类通道准备的。 + +### 登录成功,但模型选择器里没有想要的模型 + +**为什么** — 能看到哪些模型由你的账号权限、地区和套餐决定,不是应用决定的。 + +**怎么办** — 先刷新服务商状态,重新打开模型选择器。仍然没有,以服务商官方控制台显示的可用模型为准。 + +### 一直输出文字,就是不动手改文件 + +**为什么** — 这个模型的工具调用能力撑不住 Agent 工作流。小参数量的本地模型经常这样。 + +**怎么办** — 换一个明确支持 function calling 的模型试试。同时确认当前权限模式不是「计划模式」——计划模式本来就不允许它碰文件。 + +### 官方账号 OAuth 授权卡住 + +**为什么** — 授权回调落不回正在运行的应用。 + +**怎么办** — 授权全程别关掉应用;让系统浏览器打开授权页,用同一个账号完成;把代理和拦截类浏览器扩展先关掉;系统时间不准也会让授权失败。浏览器没自动打开就点「复制授权链接」自己粘过去。 + +## 会话卡住 + +### 一直转圈,不出结果 + +**为什么** — 可能是网络抖动,也可能是上游没有正常结束这次响应。 + +**怎么办** — 按顺序来,别一上来就重启: + +1. 等十几秒,看是不是短暂波动。 +2. 点停止,或按 `Cmd/Ctrl + .`。 +3. 打开活动面板,看后台任务和子 Agent 的真实状态——主对话没动静不代表后台也停了。 +4. 切到别的会话再切回来,排除界面刷新问题。 +5. 以上都不行,完全退出应用重开。 +6. 去「设置 → 诊断」复制错误摘要。 + +### 会话跑着时权限模式选不了 + +**为什么** — 这是故意锁的。跑到一半改权限会让界面显示和实际生效的权限对不上。 + +**怎么办** — 等这一轮结束,或者先停止,再切换。 + +### 拒绝了编辑,但不确定文件到底改没改 + +**为什么** — 被拒绝的写入不会落盘,但界面展示和磁盘状态是两回事。 + +**怎么办** — 交付前自己跑一遍 `git status` 和 `git diff`。磁盘和 Git 才是最终事实源。 + +## 端口冲突 + +**现象** — H5 打不开,或者应用启动后本地服务没起来。 + +**为什么** — 本地服务默认用 `3456` 端口。这个端口被别的程序占了,服务就换端口或者起不来。 + +**怎么办** — 打开「设置 → H5 访问」,看「当前端口」显示的是多少——它可能已经自动换到别的端口了,而你手上的旧二维码还指向老端口。需要一个稳定的地址(比如做书签或配反向代理),在同一页填「固定端口」,范围 1024–65535,改完重启应用生效。 + +## 手机连不上 + +**现象** — 扫码后页面打不开,或者提示未授权。 + +**为什么** — 地址、端口、令牌、网络四件事任何一件对不上都连不上。 + +**怎么办** — 打开「设置 → H5 访问」逐项核对: + +1. H5 访问的开关是不是真的打开了。 +2. 「访问主机 / IP」显示的地址,是不是电脑当前网卡的地址(换了 Wi-Fi 就会变)。 +3. 二维码里的端口,和「当前端口」是不是同一个。 +4. 手机和电脑在不在同一个局域网里。 +5. 系统防火墙有没有放行这个端口。 +6. 令牌有没有被重新生成过——**一旦重新生成,旧二维码立刻失效**。 + +改过固定端口一定要重启应用。完整部署方式和安全边界见 [手机与 IM 接力](../desktop/remote.md)。 + +### 手机锁屏了,正在跑的任务会断吗 + +不会。短暂断连不影响正在执行的任务,它会在后台跑完,重连之后你能看到结果。只有当任务已经空闲、且没有任何客户端连着时,才会按断连保活设置停掉对应的 CLI(默认 30 秒)。 + +但这不等于永远不会断——系统休眠、进程退出、代理故障、服务重启,照样会终止连接。 + +### IM 已经扫码绑定了,联系人还是聊不了 + +扫码只是把平台账号绑上来,不等于授权了所有联系人。对方还需要发一次桌面端生成的一次性配对码,或者被你加进允许列表。两者都为空时默认拒绝。各平台差异见 [IM 接入](../im/index.md)。 + +## Computer Use 没反应 + +**现象** — 让它点屏幕、控制别的应用,它说做不到,或者干脆没动静。 + +**为什么** — Computer Use 有一串前置条件,任何一环没过它都不干活。它只支持 macOS 和 Windows,**Linux 上没有这个能力**。 + +**怎么办** — 打开「设置 → Computer Use」,从上往下看哪一项是红的: + +1. 顶部的启用开关是不是打开了(关着的话新会话根本不会注入这套工具)。 +2. Python 3 检测通过了没有。没装就先装,或者在「Python 解释器路径」里指定一个已有的(conda、pyenv 都行)。 +3. 虚拟环境和依赖包是不是「已就绪 / 已安装」,没有就点「安装环境」。 +4. macOS 上还要「辅助功能权限」和「屏幕录制权限」两项都显示「已授权」——去「系统设置 → 隐私与安全性」里开。 +5. **授权之后必须重启 Claude Code Haha**,系统权限对已经在跑的进程不生效。 +6. 要控制的目标应用有没有在「已授权应用」列表里。 + +完整说明见 [Computer Use](../desktop/computer-use.md)。 + +## 还是没解决 + +去「设置 → 诊断」: + +1. 点「复制 Issue 报告」,先拿到一份结构化的现场信息。 +2. 到 [GitHub Issues](https://github.com/NanmiCoder/cc-haha/issues) 搜一下有没有人报过同样的问题,没有再新建。 +3. 光靠报告定位不了的话,再点「导出诊断包」附上。 + +一并提供这些能大幅提高解决速度:应用版本、操作系统和 CPU 架构、安装包文件名、用的哪类服务商(**不要贴 API 密钥**)、最短复现步骤、完整错误文字、问题出在桌面端还是手机端还是 CLI。 + +:::warning +诊断报告会尽力省略聊天内容、文件正文、完整环境变量和 API 密钥,但仍可能带上本机路径、服务商主机名这类信息。**发出去之前自己看一遍。** +::: diff --git a/scripts/pr/change-policy.test.ts b/scripts/pr/change-policy.test.ts index 8ebfce4a..f2952930 100644 --- a/scripts/pr/change-policy.test.ts +++ b/scripts/pr/change-policy.test.ts @@ -129,7 +129,7 @@ describe('evaluateChangePolicy', () => { const result = evaluateChangePolicy([ '.github/CODEOWNERS', '.github/copilot-instructions.md', - 'docs/guide/contributing.md', + 'docs/internals/contributing.md', ]) expect(result.checks.policy).toBe(true) diff --git a/scripts/pr/change-policy.ts b/scripts/pr/change-policy.ts index 1fed2c1c..2c8a9f12 100644 --- a/scripts/pr/change-policy.ts +++ b/scripts/pr/change-policy.ts @@ -137,8 +137,8 @@ const policyExactPaths = new Set([ '.github/pull_request_template.md', 'AGENTS.md', 'CONTRIBUTING.md', - 'docs/en/guide/contributing.md', - 'docs/guide/contributing.md', + 'docs/en/internals/contributing.md', + 'docs/internals/contributing.md', 'package.json', ]) diff --git a/scripts/pr/quality-contract.test.ts b/scripts/pr/quality-contract.test.ts index 11ccdff5..970dc514 100644 --- a/scripts/pr/quality-contract.test.ts +++ b/scripts/pr/quality-contract.test.ts @@ -80,8 +80,8 @@ describe('feature quality contract', () => { scripts?: Record } const prePushHook = readFileSync('scripts/git-hooks/pre-push', 'utf8') - const contributing = readFileSync('docs/guide/contributing.md', 'utf8') - const englishContributing = readFileSync('docs/en/guide/contributing.md', 'utf8') + const contributing = readFileSync('docs/internals/contributing.md', 'utf8') + const englishContributing = readFileSync('docs/en/internals/contributing.md', 'utf8') const rootContributing = readFileSync('CONTRIBUTING.md', 'utf8') expect(packageJson.scripts?.verify).toBe('bun run quality:pr') diff --git a/site/.gitignore b/site/.gitignore index c22c8be5..f816e0f8 100644 --- a/site/.gitignore +++ b/site/.gitignore @@ -1,3 +1,3 @@ dist/ node_modules/ -src/generated/docs-manifest.js +src/generated/ diff --git a/site/AGENTS.md b/site/AGENTS.md index 803693d4..a2ca441a 100644 --- a/site/AGENTS.md +++ b/site/AGENTS.md @@ -2,9 +2,41 @@ These rules apply to the public landing page and documentation experience under `site/`. +## Contracts that must not break + - Keep the site independently installable with `npm ci` and buildable with `npm run build`. - Keep `npm run check` deterministic, offline, and responsible for site-specific validation beyond compilation. -- Preserve the GitHub Pages custom-domain contract; production assets and routes must work from the root of `claudecode-haha.relakkesyang.org`. +- Preserve the GitHub Pages custom-domain contract; production assets and routes must work from the root of `claudecode-haha.relakkesyang.org`. `scripts/prepare-static-output.mjs` hard-fails when the CNAME drifts. - Treat files under `docs/` as the source of truth for long-form Chinese and English documentation. Keep paired public routes aligned when both languages exist. - Do not copy private user state, credentials, local filesystem paths, or unredacted product screenshots into the site. - Run `bun run check:docs` after site or docs changes and include desktop plus narrow-mobile browser evidence for user-visible layout changes. + +## How content reaches the site + +`scripts/generate-docs-manifest.mjs` scans `docs/`, then emits three things into `src/generated/` (gitignored): + +- `docs-index.js` — the eager index: route, section, title, `nav_title`, description, `order`. Keep it small; it ships on every page. +- `content/.js` — one module per document holding the markdown **body** (frontmatter already stripped). `docsContent` maps a route to a dynamic import, so opening one page never downloads the rest. +- `search-index.js` — lazily imported by the search dialog only. + +Routes come from file paths (`docs/start/install.md` → `/start/install`). Renaming a file renames its URL, so add the old path to both `LEGACY_ROUTES` in `src/content/docs.js` and `legacyRoutes` in `scripts/prepare-static-output.mjs`. + +Sidebar grouping comes from the `sections` array in the generator — register any new top-level `docs/` directory there or it sorts last with a bare directory name. Order inside a group comes from each document's `order` frontmatter. + +## Design system + +`src/styles/base.css` holds every token. The palette is lifted from the desktop app's 「纸·墨·印」 themes — light mirrors 纯白, dark mirrors 墨夜 — so the site and the app read as one product. Rules: + +- Use tokens (`--surface-*`, `--text-*`, `--border*`, `--brand*`, `--sp-*`, `--fs-*`, `--r-*`) rather than literal values. A raw hex in a component is a bug. +- The primary button is ink (`--ink`), turning terracotta (`--brand`) on hover. It is not brand-coloured at rest. +- Depth comes from 1px borders and surface layering, not heavy shadows. +- Both themes must work. `data-theme` on `` is set by the bootstrap script in `index.html` before first paint. +- Breakpoints are 1180 / 900 / 620 across both stylesheets. Do not introduce a fourth. + +## Mermaid + +````mermaid fences render as diagrams. Nothing under `docs/` currently uses one — the two pages that did were deleted in the July 2026 restructure — so the dependency and its `.doc-mermaid` styles sit unused, lazily loaded and costing readers nothing. Keep them: `internals/` is exactly where a diagram would earn its place, and the mermaid theme is already wired to the site tokens. + +## Fonts + +Self-hosted in `public/fonts/`, copied from `desktop/public/fonts/`. **Never add a Google Fonts `@import` or ``** — it is unreachable from mainland China and would leave every heading in a fallback serif. Only the latin subsets are hosted; Chinese glyphs fall through to the platform font on purpose, exactly as the desktop app does. diff --git a/site/index.html b/site/index.html index 46af57a0..0e0ae691 100644 --- a/site/index.html +++ b/site/index.html @@ -3,22 +3,33 @@ - - - - + + + + + - - + + + + Claude Code Haha — Claude Code 的本地优先桌面客户端 + - Claude Code Haha — 会工作的桌面 AI 搭档
diff --git a/site/package-lock.json b/site/package-lock.json index 9b8c3fec..be331495 100644 --- a/site/package-lock.json +++ b/site/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "dompurify": "^3.3.0", + "highlight.js": "^11.11.1", "marked": "^17.0.5", "mermaid": "^11.14.0", "react": "^19.2.4", @@ -1283,6 +1284,15 @@ "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", "license": "MIT" }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", diff --git a/site/package.json b/site/package.json index 76170148..2fbd96a4 100644 --- a/site/package.json +++ b/site/package.json @@ -15,6 +15,7 @@ }, "dependencies": { "dompurify": "^3.3.0", + "highlight.js": "^11.11.1", "marked": "^17.0.5", "mermaid": "^11.14.0", "react": "^19.2.4", diff --git a/site/public/fonts/inter-latin-ext.woff2 b/site/public/fonts/inter-latin-ext.woff2 new file mode 100644 index 00000000..57da6f8d Binary files /dev/null and b/site/public/fonts/inter-latin-ext.woff2 differ diff --git a/site/public/fonts/inter-latin.woff2 b/site/public/fonts/inter-latin.woff2 new file mode 100644 index 00000000..91dc3e85 Binary files /dev/null and b/site/public/fonts/inter-latin.woff2 differ diff --git a/site/public/fonts/jetbrains-mono-latin-ext.woff2 b/site/public/fonts/jetbrains-mono-latin-ext.woff2 new file mode 100644 index 00000000..11da8b45 Binary files /dev/null and b/site/public/fonts/jetbrains-mono-latin-ext.woff2 differ diff --git a/site/public/fonts/jetbrains-mono-latin.woff2 b/site/public/fonts/jetbrains-mono-latin.woff2 new file mode 100644 index 00000000..5b9b02f0 Binary files /dev/null and b/site/public/fonts/jetbrains-mono-latin.woff2 differ diff --git a/site/public/fonts/noto-serif-sc-latin-ext.woff2 b/site/public/fonts/noto-serif-sc-latin-ext.woff2 new file mode 100644 index 00000000..df48bf7e Binary files /dev/null and b/site/public/fonts/noto-serif-sc-latin-ext.woff2 differ diff --git a/site/public/fonts/noto-serif-sc-latin.woff2 b/site/public/fonts/noto-serif-sc-latin.woff2 new file mode 100644 index 00000000..d5bd8acc Binary files /dev/null and b/site/public/fonts/noto-serif-sc-latin.woff2 differ diff --git a/site/scripts/generate-docs-manifest.mjs b/site/scripts/generate-docs-manifest.mjs index eeba1c51..1abce288 100644 --- a/site/scripts/generate-docs-manifest.mjs +++ b/site/scripts/generate-docs-manifest.mjs @@ -1,60 +1,38 @@ import { promises as fs } from 'node:fs' import path from 'node:path' -import { fileURLToPath, pathToFileURL } from 'node:url' +import { fileURLToPath } from 'node:url' + +import { readImageSize } from './image-size.mjs' const scriptDir = path.dirname(fileURLToPath(import.meta.url)) const siteDir = path.resolve(scriptDir, '..') const repoDir = path.resolve(siteDir, '..') const docsDir = path.join(repoDir, 'docs') -const outputPath = path.join(siteDir, 'src', 'generated', 'docs-manifest.js') +const generatedDir = path.join(siteDir, 'src', 'generated') +const contentDir = path.join(generatedDir, 'content') export const paths = { + contentDir, docsDir, - outputPath, + generatedDir, repoDir, siteDir } -const excludedDirectoryNames = new Set(['superpowers', 'ui-clone']) -const excludedFiles = new Set(['index.md', 'en/index.md', 'AGENTS.md']) +const excludedDirectoryNames = new Set(['superpowers', 'ui-clone', 'public', 'images']) +const excludedFiles = new Set(['AGENTS.md']) -const sectionLabels = { - zh: { - guide: '开始使用', - desktop: '桌面应用', - features: '核心能力', - im: '消息接入', - agent: '多 Agent', - skills: '技能系统', - memory: '记忆系统', - channel: 'Channel 研究', - reference: '参考资料' - }, - en: { - guide: 'Get started', - desktop: 'Desktop app', - features: 'Core capabilities', - im: 'Messaging', - agent: 'Multi-agent', - skills: 'Skills', - memory: 'Memory', - channel: 'Channel research', - reference: 'Reference' - } -} - -const sectionOrder = [ - 'guide', - 'desktop', - 'features', - 'im', - 'agent', - 'skills', - 'memory', - 'channel', - 'reference' +/** 分区顺序与标题。新增一个 docs/ 顶层目录时在这里登记,否则会排到最后并显示裸目录名。 */ +export const sections = [ + { id: 'start', zh: '开始使用', en: 'Get started' }, + { id: 'desktop', zh: '桌面端功能', en: 'Desktop app' }, + { id: 'im', zh: 'IM 接入', en: 'Messaging' }, + { id: 'cli', zh: '命令行', en: 'Command line' }, + { id: 'internals', zh: '深入原理', en: 'Internals' } ] +const sectionIndex = new Map(sections.map((section, index) => [section.id, index])) + function toPosix(value) { return value.split(path.sep).join('/') } @@ -64,7 +42,7 @@ function shouldInclude(relativePath) { const parts = normalized.split('/') return normalized.endsWith('.md') - && !excludedFiles.has(normalized) + && !excludedFiles.has(path.posix.basename(normalized)) && !parts.some((part) => excludedDirectoryNames.has(part)) } @@ -129,22 +107,16 @@ function plainText(value) { } function extractTitle(body, frontmatter, fallback) { - if (frontmatter.title) { - return plainText(frontmatter.title) - } - + if (frontmatter.title) return plainText(frontmatter.title) const heading = body.match(/^#\s+(.+)$/m) return heading ? plainText(heading[1]) : fallback } function extractDescription(body, frontmatter) { - if (frontmatter.description) { - return plainText(frontmatter.description).slice(0, 180) - } + if (frontmatter.description) return plainText(frontmatter.description).slice(0, 180) const withoutFences = body.replace(/```[\s\S]*?```/g, '') - const paragraphs = withoutFences.split(/\n\s*\n/) - const paragraph = paragraphs.find((candidate) => { + const paragraph = withoutFences.split(/\n\s*\n/).find((candidate) => { const value = candidate.trim() return value && !value.startsWith('#') @@ -157,19 +129,19 @@ function extractDescription(body, frontmatter) { return plainText(paragraph || '').slice(0, 180) } +/** 搜索用的正文摘要:去掉代码块和标记,保留可读文字。 */ +function searchBody(body) { + return plainText(body.replace(/```[\s\S]*?```/g, ' ')).slice(0, 1400) +} + function routeFromRelativePath(relativePath) { const withoutExtension = relativePath.replace(/\.md$/i, '') const withoutIndex = withoutExtension.replace(/(^|\/)index$/i, '$1') return `/${withoutIndex}`.replace(/\/+/g, '/').replace(/\/$/, '') || '/' } -function sortKey(relativePath) { - const basename = path.posix.basename(relativePath, '.md') - if (basename === 'index') { - return '0000-index' - } - - return basename.replace(/^(\d+)-/, (_, value) => value.padStart(4, '0')) +function moduleIdFor(relativePath) { + return relativePath.replace(/\.md$/i, '').replace(/[^A-Za-z0-9]+/g, '-').replace(/^-|-$/g, '') } function buildRecord(relativePath, markdown) { @@ -184,121 +156,160 @@ function buildRecord(relativePath, markdown) { ? section : basename.replace(/^\d+-/, '').replaceAll('-', ' ') + const title = extractTitle(body, attributes, fallbackTitle) + const explicitOrder = Number.parseInt(attributes.order ?? '', 10) + return { - slug: route.replace(/^\//, ''), - path: route, - locale, - section, - title: extractTitle(body, attributes, fallbackTitle), + body, + content: markdown, description: extractDescription(body, attributes), + locale, + moduleId: moduleIdFor(relativePath), + navTitle: attributes.nav_title ? plainText(attributes.nav_title) : title, + order: Number.isFinite(explicitOrder) ? explicitOrder : (basename === 'index' ? 0 : 900), + path: route, + search: searchBody(body), + section, + sectionRank: sectionIndex.has(section) ? sectionIndex.get(section) : 999, + slug: route.replace(/^\//, ''), sourcePath: `docs/${relativePath}`, - sortKey: sortKey(relativePath), - content: markdown + title } } -function buildNavigation(records, locale) { - const localeRecords = records.filter((record) => record.locale === locale) - const sections = [...new Set(localeRecords.map((record) => record.section))] - .sort((left, right) => { - const leftIndex = sectionOrder.indexOf(left) - const rightIndex = sectionOrder.indexOf(right) - return (leftIndex === -1 ? Number.MAX_SAFE_INTEGER : leftIndex) - - (rightIndex === -1 ? Number.MAX_SAFE_INTEGER : rightIndex) - || left.localeCompare(right) - }) - - return sections.map((section) => ({ - key: section, - title: sectionLabels[locale][section] || section, - items: localeRecords - .filter((record) => record.section === section) - .sort((left, right) => left.sortKey.localeCompare(right.sortKey)) - .map(({ content, sortKey: _sortKey, section: _section, ...item }) => item) - })) +function sortRecords(records) { + return [...records].sort((left, right) => { + if (left.locale !== right.locale) return left.locale.localeCompare(right.locale) + if (left.sectionRank !== right.sectionRank) return left.sectionRank - right.sectionRank + if (left.section !== right.section) return left.section.localeCompare(right.section) + if (left.order !== right.order) return left.order - right.order + return left.title.localeCompare(right.title) + }) } -function serialize(value) { - return JSON.stringify(value, null, 2) - .replaceAll('\u2028', '\\u2028') - .replaceAll('\u2029', '\\u2029') +async function emptyDirectory(directory) { + await fs.rm(directory, { force: true, recursive: true }) + await fs.mkdir(directory, { recursive: true }) +} + +const IMAGE_EXTENSIONS = new Set(['.png', '.webp', '.jpg', '.jpeg', '.gif']) + +/** + * 站点资源路径 → 像素尺寸。渲染时写进 , + * 浏览器就能在图片下载完之前留出正确的位置,懒加载不再让正文跳动。 + */ +async function collectImageSizes(directory, prefix = '') { + const sizes = {} + let entries + + try { + entries = await fs.readdir(directory, { withFileTypes: true }) + } catch { + return sizes + } + + for (const entry of entries) { + const absolutePath = path.join(directory, entry.name) + const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name + + if (entry.isDirectory()) { + Object.assign(sizes, await collectImageSizes(absolutePath, relativePath)) + continue + } + + if (!IMAGE_EXTENSIONS.has(path.extname(entry.name).toLowerCase())) continue + + const size = await readImageSize(absolutePath) + if (size?.width && size?.height) sizes[`/${relativePath}`] = [size.width, size.height] + } + + return sizes } export async function generateDocsManifest() { const relativePaths = await listMarkdownFiles(docsDir) - const records = await Promise.all(relativePaths.map(async (relativePath) => { + const records = [] + + for (const relativePath of relativePaths) { const markdown = await fs.readFile(path.join(docsDir, relativePath), 'utf8') - return buildRecord(relativePath, markdown) + records.push(buildRecord(relativePath, markdown)) + } + + const sorted = sortRecords(records) + + await fs.mkdir(generatedDir, { recursive: true }) + await emptyDirectory(contentDir) + + // 每篇正文单独成模块,路由表里用动态 import 拿 —— 打开一篇文档不再下载全部 80 篇。 + // 写的是去掉 frontmatter 之后的 body,渲染层拿到就能直接解析。 + for (const record of sorted) { + await fs.writeFile( + path.join(contentDir, `${record.moduleId}.js`), + `export default ${JSON.stringify(record.body)}\n`, + 'utf8' + ) + } + + const index = sorted.map((record) => ({ + description: record.description, + locale: record.locale, + navTitle: record.navTitle, + order: record.order, + path: record.path, + section: record.section, + slug: record.slug, + sourcePath: record.sourcePath, + title: record.title })) - records.sort((left, right) => left.path.localeCompare(right.path)) - const navigation = { - zh: buildNavigation(records, 'zh'), - en: buildNavigation(records, 'en') - } + const loaders = sorted + .map((record) => ` ${JSON.stringify(record.path)}: () => import('./content/${record.moduleId}.js'),`) + .join('\n') - const moduleSource = `// Generated by site/scripts/generate-docs-manifest.mjs. Do not edit. -export const docsManifest = ${serialize(records)} + const imageSizes = await collectImageSizes(path.join(docsDir, 'images'), 'images') -export const docsNavigation = ${serialize(navigation)} + await fs.writeFile( + path.join(generatedDir, 'docs-index.js'), + [ + '// 由 scripts/generate-docs-manifest.mjs 生成,请勿手工编辑。', + `export const sections = ${JSON.stringify(sections, null, 2)}`, + '', + `export const docsIndex = ${JSON.stringify(index, null, 2)}`, + '', + `export const imageSizes = ${JSON.stringify(imageSizes)}`, + '', + 'export const docsContent = {', + loaders, + '}', + '' + ].join('\n'), + 'utf8' + ) -export const docsBySlug = Object.fromEntries( - docsManifest.flatMap((document) => [ - [document.slug, document], - [document.path, document], - [\`\${document.path}/\`, document] - ]) -) + await fs.writeFile( + path.join(generatedDir, 'search-index.js'), + [ + '// 由 scripts/generate-docs-manifest.mjs 生成,请勿手工编辑。', + `export default ${JSON.stringify(sorted.map((record) => ({ + d: record.description, + l: record.locale, + p: record.path, + s: record.section, + t: record.title, + x: record.search + })))}`, + '' + ].join('\n'), + 'utf8' + ) -export function normalizeDocsPath(value) { - const pathname = String(value || '/').split(/[?#]/, 1)[0] - const decoded = decodeURIComponent(pathname) - const withoutHtml = decoded.replace(/(?:\\/index)?\\.html$/i, '') - const withoutMarkdown = withoutHtml.replace(/\\.md$/i, '') - const normalized = \`/\${withoutMarkdown}\`.replace(/\\/+/g, '/').replace(/\\/$/, '') - return normalized || '/' + return { records: sorted, sections } } -export function resolveDocsPath(value) { - const normalized = normalizeDocsPath(value) - return docsBySlug[normalized] - || docsBySlug[normalized.replace(/^\\//, '')] - || null -} +const invokedDirectly = process.argv[1] + && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url)) -export function resolveDocsHref(href, sourcePath) { - if (!href || /^(?:[a-z]+:|\\/\\/|#)/i.test(href)) { - return href - } - - const suffixIndex = href.search(/[?#]/) - const pathname = suffixIndex === -1 ? href : href.slice(0, suffixIndex) - const suffix = suffixIndex === -1 ? '' : href.slice(suffixIndex) - if (pathname.startsWith('/')) { - return \`\${normalizeDocsPath(pathname)}\${suffix}\` - } - - const sourceRelative = sourcePath.replace(/^docs\\//, '') - const resolved = new URL(pathname, \`https://docs.local/\${sourceRelative}\`).pathname - const isMarkdown = /\\.md$/i.test(resolved) - const target = isMarkdown ? normalizeDocsPath(resolved) : resolved - return \`\${target}\${suffix}\` -} -` - - await fs.mkdir(path.dirname(outputPath), { recursive: true }) - const current = await fs.readFile(outputPath, 'utf8').catch(() => '') - if (current !== moduleSource) { - await fs.writeFile(outputPath, moduleSource) - } - - return { records, navigation, outputPath } -} - -const isDirectRun = process.argv[1] - && pathToFileURL(path.resolve(process.argv[1])).href === import.meta.url - -if (isDirectRun) { +if (invokedDirectly) { const { records } = await generateDocsManifest() - console.log(`Generated ${records.length} documentation routes.`) + console.log(`Generated docs index for ${records.length} pages.`) } diff --git a/site/scripts/image-size.mjs b/site/scripts/image-size.mjs new file mode 100644 index 00000000..5af715ef --- /dev/null +++ b/site/scripts/image-size.mjs @@ -0,0 +1,82 @@ +import { promises as fs } from 'node:fs' + +/** + * 从文件头读出图片的像素尺寸,只认站点用到的四种格式。 + * 不引第三方依赖 —— 站点的构建链路是刻意保持零外部图像库的。 + * 读不出来就返回 null,调用方跳过即可(顶多是那张图没有 width/height)。 + */ +export async function readImageSize(filePath) { + let handle + try { + handle = await fs.open(filePath, 'r') + const { buffer, bytesRead } = await handle.read(Buffer.alloc(64), 0, 64, 0) + if (bytesRead < 16) return null + + // PNG: 8 字节签名 + IHDR,宽高是 16..24 的两个大端 uint32 + if (buffer.readUInt32BE(0) === 0x89504e47) { + return { height: buffer.readUInt32BE(20), width: buffer.readUInt32BE(16) } + } + + // GIF: "GIF8" + 逻辑屏幕宽高(小端 uint16) + if (buffer.toString('ascii', 0, 4) === 'GIF8') { + return { height: buffer.readUInt16LE(8), width: buffer.readUInt16LE(6) } + } + + // WebP: RIFF....WEBP,三种子格式的宽高位置各不相同 + if (buffer.toString('ascii', 0, 4) === 'RIFF' && buffer.toString('ascii', 8, 12) === 'WEBP') { + const format = buffer.toString('ascii', 12, 16) + + if (format === 'VP8 ') { + return { height: buffer.readUInt16LE(28) & 0x3fff, width: buffer.readUInt16LE(26) & 0x3fff } + } + if (format === 'VP8L') { + const bits = buffer.readUInt32LE(21) + return { height: ((bits >> 14) & 0x3fff) + 1, width: (bits & 0x3fff) + 1 } + } + if (format === 'VP8X') { + const read24 = (offset) => + buffer[offset] | (buffer[offset + 1] << 8) | (buffer[offset + 2] << 16) + return { height: read24(27) + 1, width: read24(24) + 1 } + } + return null + } + + // JPEG: 逐个 marker 往下走,直到碰上 SOFn + if (buffer.readUInt16BE(0) === 0xffd8) { + return await readJpegSize(handle) + } + + return null + } catch { + return null + } finally { + await handle?.close() + } +} + +async function readJpegSize(handle) { + const head = Buffer.alloc(2) + const size = Buffer.alloc(2) + const frame = Buffer.alloc(5) + let offset = 2 + + // 帧头最多往后找 2000 个 marker,避免坏文件把构建卡死 + for (let guard = 0; guard < 2000; guard += 1) { + const marker = await handle.read(head, 0, 2, offset) + if (marker.bytesRead < 2 || head[0] !== 0xff) return null + + const code = head[1] + // SOF0..SOF15,跳过 DHT(c4) / JPG(c8) / DAC(cc) 这三个不是帧头的 + if (code >= 0xc0 && code <= 0xcf && code !== 0xc4 && code !== 0xc8 && code !== 0xcc) { + const read = await handle.read(frame, 0, 5, offset + 4) + if (read.bytesRead < 5) return null + return { height: frame.readUInt16BE(1), width: frame.readUInt16BE(3) } + } + + const length = await handle.read(size, 0, 2, offset + 2) + if (length.bytesRead < 2) return null + offset += 2 + size.readUInt16BE(0) + } + + return null +} diff --git a/site/scripts/prepare-static-output.mjs b/site/scripts/prepare-static-output.mjs index 7a59e319..7668aef8 100644 --- a/site/scripts/prepare-static-output.mjs +++ b/site/scripts/prepare-static-output.mjs @@ -77,9 +77,15 @@ async function siteSourceFiles() { const srcDir = path.join(paths.siteDir, 'src') const entries = await fs.readdir(srcDir, { recursive: true, withFileTypes: true }) for (const entry of entries) { - if (entry.isFile() && /\.(?:jsx?|css)$/.test(entry.name)) { - files.push(path.join(entry.parentPath || entry.path, entry.name)) - } + if (!entry.isFile() || !/\.(?:jsx?|css)$/.test(entry.name)) continue + + const directory = entry.parentPath || entry.path + // src/generated/ 里有一张「路径 → 图片尺寸」表,覆盖 docs/images 下的每张图。 + // 那是给渲染器查尺寸用的,不代表页面引用了它们 —— 扫进来会把没人用的图 + // (赞助、打赏之类)全拷进产物。 + if (path.relative(srcDir, directory).split(path.sep)[0] === 'generated') continue + + files.push(path.join(directory, entry.name)) } return files } @@ -110,7 +116,54 @@ async function copySiteReferencedImages() { console.log(`Copied ${sources.size} site-referenced images.`) } -async function createRouteEntry(route, shell) { +function escapeHtml(value) { + return String(value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') +} + +/** + * 每条路由的 HTML 骨架都写进自己的 title / description / canonical / hreflang。 + * 不执行 JS 的爬虫看到的就不再是 78 个一模一样的页面。 + */ +function shellForRoute(shell, meta) { + if (!meta) return shell + + const origin = `https://${expectedCustomDomain}` + const isEnglish = meta.path === '/en' || meta.path.startsWith('/en/') + const canonical = `${origin}${meta.path}` + const alternate = meta.alternate ? `${origin}${meta.alternate}` : null + + const head = [ + ``, + ``, + alternate + ? `` + : '', + `` + ].filter(Boolean).join('\n ') + + return shell + .replace(/[\s\S]*?<\/title>/, `${escapeHtml(meta.title)}`) + .replace( + //, + `` + ) + .replace( + //, + `` + ) + .replace( + //, + `` + ) + .replace('', ` ${head}\n `) +} + +async function createRouteEntry(route, shell, meta) { const relativeRoute = route.replace(/^\/+|\/+$/g, '') if (!relativeRoute) { return @@ -118,9 +171,49 @@ async function createRouteEntry(route, shell) { const routeDirectory = path.join(distDir, ...relativeRoute.split('/')) await fs.mkdir(routeDirectory, { recursive: true }) - await fs.writeFile(path.join(routeDirectory, 'index.html'), shell) + await fs.writeFile(path.join(routeDirectory, 'index.html'), shellForRoute(shell, meta)) } +function alternateFor(record, records) { + const source = record.sourcePath.replace(/^docs\//, '') + const target = record.locale === 'en' ? source.replace(/^en\//, '') : `en/${source}` + return records.find((candidate) => candidate.sourcePath === `docs/${target}`)?.path || null +} + +async function writeSitemap(records) { + const origin = `https://${expectedCustomDomain}` + const urls = ['/', '/en', ...records.map((record) => record.path)] + const body = urls + .map((url) => ` ${origin}${url}weekly`) + .join('\n') + + await fs.writeFile( + path.join(distDir, 'sitemap.xml'), + `\n\n${body}\n\n` + ) + + await fs.writeFile( + path.join(distDir, 'robots.txt'), + `User-agent: *\nAllow: /\n\nSitemap: ${origin}/sitemap.xml\n` + ) +} + +/** 2026-07 信息架构重组前的老路径,发一份骨架出去让前端接管重定向,别在 CDN 层就 404。 */ +const legacyRoutes = [ + '/agent', '/agent/01-usage-guide', '/agent/02-implementation', '/agent/03-agent-framework', + '/channel', '/channel/01-channel-system', '/channel/02-im-gateway-proposal', + '/desktop/01-quick-start', '/desktop/02-architecture', '/desktop/03-features', + '/desktop/04-installation', '/desktop/05-FAQ', '/desktop/06-h5-access', + '/desktop/07-electron-migration-research', '/desktop/08-electron-migration-tasks', + '/desktop/09-electron-migration-validation-checklist', '/desktop/10-release-auto-update', + '/docs', '/features/computer-use', '/features/computer-use-architecture', + '/guide/cli-reference', '/guide/contributing', '/guide/env-vars', '/guide/faq', + '/guide/global-usage', '/guide/quick-start', '/guide/third-party-models', + '/memory', '/memory/01-usage-guide', '/memory/02-implementation', '/memory/03-autodream', + '/reference/fixes', '/reference/local-server', '/reference/project-structure', + '/skills', '/skills/01-usage-guide', '/skills/02-implementation' +] + async function main() { const { records } = await generateDocsManifest() const shellPath = path.join(distDir, 'index.html') @@ -135,17 +228,30 @@ async function main() { throw new Error(`Expected CNAME to contain ${expectedCustomDomain}, received ${customDomain || 'an empty value'}.`) } - const routes = new Set(records.map((record) => record.path)) - routes.add('/en') - routes.add('/docs') - routes.add('/en/docs') - - for (const route of routes) { - await createRouteEntry(route, shell) + for (const record of records) { + await createRouteEntry(record.path, shell, { + alternate: alternateFor(record, records), + description: record.description, + path: record.path, + title: `${record.title} · Claude Code Haha` + }) } + await createRouteEntry('/en', shell, { + alternate: '/', + description: 'A local-first desktop client for Claude Code. Sessions, diffs, agents and scheduled runs all sit in the open.', + path: '/en', + title: 'Claude Code Haha — a local-first desktop client for Claude Code' + }) + + for (const legacy of legacyRoutes) { + await createRouteEntry(legacy, shell, null) + await createRouteEntry(`/en${legacy}`, shell, null) + } + + await writeSitemap(records) await fs.writeFile(path.join(distDir, '404.html'), shell) - console.log(`Prepared static output for ${routes.size} client-side routes.`) + console.log(`Prepared static output for ${records.length} documentation routes and ${legacyRoutes.length * 2} redirects.`) } await main() diff --git a/site/src/App.jsx b/site/src/App.jsx index 6de45a49..88a6b066 100644 --- a/site/src/App.jsx +++ b/site/src/App.jsx @@ -1,5 +1,6 @@ import { lazy, Suspense, useEffect, useState } from 'react' import HomePage from './pages/home/HomePage' +import { resolveLegacyRoute, toSiteHref } from './content/docs' const DocPage = lazy(() => import('./components/DocPage')) @@ -7,11 +8,35 @@ function currentPath() { return window.location.pathname.replace(/\/+$/, '') || '/' } +function NotFound({ pathname }) { + const isEnglish = pathname.startsWith('/en') + return ( +
+

404

+

{isEnglish ? 'This page moved or never existed.' : '这个页面搬走了,或者从来没存在过。'}

+

+ {isEnglish + ? 'The documentation was reorganised in July 2026. Try the two entry points below.' + : '文档在 2026 年 7 月重新分区过。从下面两个入口找找看。'} +

+ +
+ ) +} + export default function App() { const [path, setPath] = useState(currentPath) useEffect(() => { const onPopState = () => setPath(currentPath()) + const onClick = (event) => { const anchor = event.target.closest('a') if ( @@ -30,12 +55,7 @@ export default function App() { const target = new URL(anchor.href, window.location.href) if (target.origin !== window.location.origin) return if (/\.html$/i.test(target.pathname)) return - if ( - target.pathname === window.location.pathname - && target.search === window.location.search - ) { - return - } + if (target.pathname === window.location.pathname && target.search === window.location.search) return event.preventDefault() window.history.pushState({}, '', `${target.pathname}${target.search}${target.hash}`) @@ -50,23 +70,21 @@ export default function App() { } }, []) + // 老路由一律换到新地址,别让重组把外部链接打死在 404 上。 + useEffect(() => { + const legacy = resolveLegacyRoute(path) + if (!legacy) return + window.history.replaceState({}, '', toSiteHref(legacy) + window.location.hash) + setPath(currentPath()) + }, [path]) + if (path === '/' || path === '/en') { return } return ( - Opening the field manual…}> - ( -
- 404 · OFF THE MAP -

这里还没有留下脚印。

-

The crew could not find this page.

- 回到工作室 → -
- )} - /> + }> + } /> ) } diff --git a/site/src/components/DocPage.jsx b/site/src/components/DocPage.jsx index d0723287..796f4f05 100644 --- a/site/src/components/DocPage.jsx +++ b/site/src/components/DocPage.jsx @@ -1,29 +1,37 @@ -import React, { useEffect, useMemo } from 'react' -import { - findDoc, - getAdjacentDocs, - renderMarkdown, - toSiteHref, -} from '../content/docs' +import { useEffect, useMemo, useRef, useState } from 'react' import { alternateLocaleRoute, + ensureHighlighter, + findDoc, + getAdjacentDocs, getDocNavigation, -} from '../content/navigation' + getHighlighter, + getSections, + loadDocContent, + renderMarkdown, + toSiteHref +} from '../content/docs' import { DocPager } from './DocPager' import { DocSidebar } from './DocSidebar' import { DocToc } from './DocToc' +import Icon from './icons' +import SiteHeader from './SiteHeader' +import { setPageMeta } from '../lib/meta' import '../docs/doc.css' +const copy = { + zh: { home: '首页', menu: '目录', reading: '正在打开…', sidebar: '文档目录' }, + en: { home: 'Home', menu: 'Contents', reading: 'Opening…', sidebar: 'Documentation' } +} + function defaultNavigate(href) { window.history.pushState({}, '', toSiteHref(href)) window.dispatchEvent(new PopStateEvent('popstate')) } async function scrollToDocHeading(id) { - const images = [...document.querySelectorAll('.doc-content img')] - images.forEach((image) => { - image.loading = 'eager' - }) + const images = [...document.querySelectorAll('.prose img')] + images.forEach((image) => { image.loading = 'eager' }) await Promise.all(images.map((image) => { if (image.complete && image.naturalWidth > 0) return Promise.resolve() return image.decode?.().catch(() => undefined) || Promise.resolve() @@ -33,64 +41,108 @@ async function scrollToDocHeading(id) { document.getElementById(id)?.scrollIntoView() } -export function DocPage({ - onNavigate = defaultNavigate, - onNotFound, - path, - pathname = path || window.location.pathname, -}) { +export function DocPage({ onNavigate = defaultNavigate, onNotFound, path, pathname = path || window.location.pathname }) { const doc = useMemo(() => findDoc(pathname), [pathname]) - const navigation = useMemo( - () => getDocNavigation(doc?.locale || 'zh'), - [doc?.locale], - ) + const [markdown, setMarkdown] = useState(null) + const [highlightReady, setHighlightReady] = useState(() => Boolean(getHighlighter())) + const [navOpen, setNavOpen] = useState(false) + const mainRef = useRef(null) + const firstRender = useRef(true) + + const locale = doc?.locale || 'zh' + const c = copy[locale] || copy.zh + const navigation = useMemo(() => getDocNavigation(locale), [locale]) + + useEffect(() => { + let cancelled = false + setMarkdown(null) + + if (!doc) return undefined + + Promise.all([loadDocContent(doc.path), ensureHighlighter().catch(() => null)]) + .then(([content]) => { + if (cancelled) return + setHighlightReady(true) + setMarkdown(content ?? '') + }) + + return () => { cancelled = true } + }, [doc]) + const rendered = useMemo( - () => doc ? renderMarkdown(doc) : null, - [doc], - ) - const adjacent = useMemo( - () => doc ? getAdjacentDocs(doc, navigation) : {}, - [doc, navigation], + () => (doc && markdown !== null ? renderMarkdown(doc, markdown) : null), + // highlightReady 参与依赖:高亮器就绪后要用带高亮的结果重渲染一次 + [doc, markdown, highlightReady] ) useEffect(() => { if (!doc) return - document.documentElement.lang = doc.locale === 'en' ? 'en' : 'zh-CN' - document.title = `${doc.title} · Claude Code Haha` - - const hash = window.location.hash - if (hash) scrollToDocHeading(decodeURIComponent(hash.slice(1))) - else requestAnimationFrame(() => window.scrollTo({ top: 0 })) + const alternate = alternateLocaleRoute(doc) + setPageMeta({ + canonical: doc.path, + description: doc.description, + lang: doc.locale === 'en' ? 'en' : 'zh-CN', + alternate: alternate === doc.path ? null : alternate, + title: `${doc.title} · Claude Code Haha` + }) }, [doc]) useEffect(() => { + if (!rendered) return + const hash = window.location.hash + if (hash) scrollToDocHeading(decodeURIComponent(hash.slice(1))) + else requestAnimationFrame(() => window.scrollTo({ top: 0 })) + }, [rendered]) + + // 换页是客户端跳转,浏览器不会重置焦点。不把焦点搬进正文,读屏用户 + // 停在旧页面的某个链接上,也听不到新页面的标题。 + useEffect(() => { + if (firstRender.current) { + firstRender.current = false + return + } + if (!doc) return + mainRef.current?.focus({ preventScroll: true }) + }, [doc]) + + // 抽屉是覆盖在正文上的,Esc 得能关掉它。 + useEffect(() => { + if (!navOpen) return undefined + const onKeyDown = (event) => { if (event.key === 'Escape') setNavOpen(false) } + window.addEventListener('keydown', onKeyDown) + return () => window.removeEventListener('keydown', onKeyDown) + }, [navOpen]) + + useEffect(() => { + if (!rendered) return undefined const diagrams = [...document.querySelectorAll('.doc-mermaid[data-mermaid-pending="true"]')] if (diagrams.length === 0) return undefined let cancelled = false - async function renderDiagrams() { - const { default: mermaid } = await import('mermaid') + const styles = getComputedStyle(document.documentElement) + const token = (name, fallback) => styles.getPropertyValue(name).trim() || fallback + + import('mermaid').then(async ({ default: mermaid }) => { mermaid.initialize({ securityLevel: 'strict', startOnLoad: false, theme: 'base', themeVariables: { - fontFamily: 'Inter, PingFang SC, sans-serif', - lineColor: '#1a1610', - primaryColor: '#f3efe4', - primaryBorderColor: '#1a1610', - primaryTextColor: '#1a1610', - secondaryColor: '#f6e6de', - tertiaryColor: '#faf7ee', - }, + fontFamily: token('--font-sans', 'Inter, sans-serif'), + lineColor: token('--text-2', '#5d5850'), + primaryBorderColor: token('--border-strong', '#d8d5cc'), + primaryColor: token('--surface-muted', '#f6f6f3'), + primaryTextColor: token('--text', '#1f1c17'), + secondaryColor: token('--brand-soft', '#f6ece5'), + tertiaryColor: token('--surface', '#ffffff') + } }) for (const [index, diagram] of diagrams.entries()) { if (cancelled) return const source = diagram.querySelector('pre')?.textContent || '' try { - const id = `doc-mermaid-${Date.now()}-${index}` - const { svg } = await mermaid.render(id, source) + const { svg } = await mermaid.render(`doc-mermaid-${index}-${Math.random().toString(36).slice(2)}`, source) if (!cancelled) { diagram.innerHTML = svg diagram.dataset.mermaidPending = 'false' @@ -99,16 +151,16 @@ export function DocPage({ diagram.dataset.mermaidPending = 'error' } } - } + }) - renderDiagrams() - return () => { - cancelled = true - } - }, [doc, rendered]) + return () => { cancelled = true } + }, [rendered]) if (!doc) return onNotFound ? onNotFound(pathname) : null + const adjacent = getAdjacentDocs(doc, navigation) + const sectionLabel = getSections().find((section) => section.id === doc.section)?.[locale] || doc.section + function handleArticleClick(event) { const hashAnchor = event.target.closest('a[href^="#"]') if (hashAnchor && !event.defaultPrevented) { @@ -127,85 +179,82 @@ export function DocPage({ onNavigate(anchor.dataset.docRoute) } - const alternateRoute = alternateLocaleRoute(doc) - const localeLabel = doc.locale === 'en' ? '阅读中文' : 'Read in English' - const homeRoute = doc.locale === 'en' ? '/en' : '/' - const handleTocNavigate = (event, id) => { - event.preventDefault() - window.history.pushState({}, '', `#${encodeURIComponent(id)}`) - scrollToDocHeading(id) - } - return ( <> -
- { - event.preventDefault() - onNavigate(homeRoute) - }} - > - - Claude Code Haha - Docs - - - -
+ {locale === 'en' ? 'Skip to content' : '跳到正文'} + -
+
+ + {sectionLabel} · {doc.navTitle || doc.title} +
+ +
setNavOpen(false)} /> + +
setNavOpen(false)} + open={navOpen} /> -
-
- {doc.locale === 'en' ? 'DOCUMENTATION' : '使用手册'} - { - event.preventDefault() - onNavigate(alternateRoute) - }}> - {localeLabel} + {/* tabIndex -1 让「跳到正文」和换页真的把焦点搬进来,而不是停在 body 上 */} +
+ -
+ {rendered + ? ( +
+ ) + :

{c.reading}

}
- + {rendered && ( + { + event.preventDefault() + window.history.pushState({}, '', `#${encodeURIComponent(id)}`) + scrollToDocHeading(id) + }} + /> + )}
) diff --git a/site/src/components/DocPager.jsx b/site/src/components/DocPager.jsx index 5d5581f3..37d0f066 100644 --- a/site/src/components/DocPager.jsx +++ b/site/src/components/DocPager.jsx @@ -1,29 +1,36 @@ -import React from 'react' import { toSiteHref } from '../content/docs' -export function DocPager({ next, onNavigate, previous }) { - function handleClick(event, route) { - if (!onNavigate) return - event.preventDefault() - onNavigate(route) - } +const copy = { + zh: { label: '上一篇 / 下一篇', next: '下一篇', previous: '上一篇' }, + en: { label: 'Previous and next page', next: 'Next', previous: 'Previous' } +} +export function DocPager({ locale = 'zh', next, onNavigate, previous }) { if (!previous && !next) return null + const c = copy[locale] || copy.zh + + const link = (doc, direction) => ( + { + if (event.metaKey || event.ctrlKey || event.shiftKey) return + event.preventDefault() + onNavigate(doc.route) + }} + > + {direction === 'next' ? c.next : c.previous} + {doc.label} + {doc.section && {doc.section}} + + ) return ( - ) } + +export default DocToc diff --git a/site/src/components/ErrorBoundary.jsx b/site/src/components/ErrorBoundary.jsx new file mode 100644 index 00000000..13dd6bf1 --- /dev/null +++ b/site/src/components/ErrorBoundary.jsx @@ -0,0 +1,58 @@ +import { Component } from 'react' + +const copy = { + zh: { + body: '这一页没能渲染出来。刷新一般能解决;如果一直这样,麻烦去 GitHub 提个 issue。', + reload: '刷新页面', + title: '出了点问题' + }, + en: { + body: 'This page failed to render. A reload usually fixes it — if it keeps happening, please open an issue on GitHub.', + reload: 'Reload', + title: 'Something broke' + } +} + +/** + * 没有它的话,任意一个组件在渲染或 effect 里抛错都会把整棵树卸载掉, + * 用户看到的是纯白页面而不是错误。文档站不该因为一处小故障就整个消失。 + */ +export default class ErrorBoundary extends Component { + state = { failed: false } + + static getDerivedStateFromError() { + return { failed: true } + } + + componentDidCatch(error, info) { + console.error('[site] 渲染失败', error, info?.componentStack) + } + + render() { + if (!this.state.failed) return this.props.children + + const locale = document.documentElement.lang === 'en' ? 'en' : 'zh' + const c = copy[locale] + + return ( +
+

ERROR

+

{c.title}

+

{c.body}

+
+ + + GitHub + +
+
+ ) + } +} diff --git a/site/src/components/Icon.jsx b/site/src/components/Icon.jsx deleted file mode 100644 index d582fb54..00000000 --- a/site/src/components/Icon.jsx +++ /dev/null @@ -1,28 +0,0 @@ -const paths = { - arrow: <>, - check: , - chevron: , - copy: <>, - download: <>, - github: , - menu: <>, - play: , - spark: <>, -} - -export default function Icon({ name, size = 20, className = '' }) { - return ( - - ) -} diff --git a/site/src/components/SearchDialog.jsx b/site/src/components/SearchDialog.jsx new file mode 100644 index 00000000..f1757516 --- /dev/null +++ b/site/src/components/SearchDialog.jsx @@ -0,0 +1,239 @@ +import { useEffect, useMemo, useRef, useState } from 'react' +import Icon from './icons' +import { getSections, toSiteHref } from '../content/docs' + +const copy = { + zh: { + close: '关闭搜索', + empty: '没有匹配的文档', + hint: '搜标题、摘要和正文', + label: '搜索文档', + placeholder: '搜索文档…', + results: '搜索结果', + tips: ['↑↓ 选择', '↵ 打开', 'Esc 关闭'] + }, + en: { + close: 'Close search', + empty: 'Nothing matched', + hint: 'Searches titles, summaries and body text', + label: 'Search docs', + placeholder: 'Search docs…', + results: 'Search results', + tips: ['↑↓ to move', '↵ to open', 'Esc to close'] + } +} + +const FOCUSABLE = 'a[href], button:not([disabled]), input, [tabindex]' + +function score(entry, terms) { + const title = entry.t.toLowerCase() + const description = (entry.d || '').toLowerCase() + const body = (entry.x || '').toLowerCase() + let total = 0 + + for (const term of terms) { + if (title.includes(term)) total += title.startsWith(term) ? 12 : 8 + else if (description.includes(term)) total += 4 + else if (body.includes(term)) total += 2 + else return 0 + } + + return total +} + +/** 取正文里第一处命中的上下文,让结果能自我解释。 */ +function excerpt(entry, terms) { + const body = entry.x || '' + if (!body) return entry.d || '' + + const lower = body.toLowerCase() + const at = terms.map((term) => lower.indexOf(term)).filter((index) => index >= 0).sort((a, b) => a - b)[0] + if (at === undefined) return entry.d || body.slice(0, 110) + + const start = Math.max(0, at - 40) + return `${start > 0 ? '…' : ''}${body.slice(start, start + 120).trim()}…` +} + +export default function SearchDialog({ locale = 'zh', onClose }) { + const [query, setQuery] = useState('') + const [index, setIndex] = useState(null) + const [active, setActive] = useState(0) + const inputRef = useRef(null) + const listRef = useRef(null) + const dialogRef = useRef(null) + const c = copy[locale] || copy.zh + + const sectionLabels = useMemo(() => { + const map = new Map() + for (const section of getSections()) map.set(section.id, section[locale] || section.id) + return map + }, [locale]) + + useEffect(() => { + let cancelled = false + import('../generated/search-index').then((module) => { + if (!cancelled) setIndex(module.default) + }) + inputRef.current?.focus() + return () => { + cancelled = true + } + }, []) + + useEffect(() => { + const previous = document.body.style.overflow + document.body.style.overflow = 'hidden' + return () => { + document.body.style.overflow = previous + } + }, []) + + // 关掉对话框要把焦点还给打开它的那个控件,否则键盘用户会被扔回文档开头。 + // 触发者要在首次渲染时就记下来 —— 等 effect 跑完,焦点已经被搬进输入框了。 + const openerRef = useRef(undefined) + if (openerRef.current === undefined) openerRef.current = document.activeElement + + useEffect(() => () => { + const opener = openerRef.current + if (opener instanceof HTMLElement && document.contains(opener)) opener.focus() + }, []) + + const results = useMemo(() => { + const terms = query.trim().toLowerCase().split(/\s+/).filter(Boolean) + if (!index || terms.length === 0) return [] + + return index + .filter((entry) => entry.l === locale) + .map((entry) => ({ entry, value: score(entry, terms) })) + .filter((item) => item.value > 0) + .sort((left, right) => right.value - left.value) + .slice(0, 12) + .map(({ entry }) => ({ + excerpt: excerpt(entry, terms), + path: entry.p, + section: sectionLabels.get(entry.s) || entry.s, + title: entry.t + })) + }, [index, locale, query, sectionLabels]) + + useEffect(() => setActive(0), [query]) + + function open(path) { + window.history.pushState({}, '', toSiteHref(path)) + window.dispatchEvent(new PopStateEvent('popstate')) + onClose() + } + + /** 挂在对话框根节点上:Esc 和方向键在关闭按钮、结果项上一样要管用。 */ + function onKeyDown(event) { + if (event.key === 'Escape') { + event.preventDefault() + onClose() + return + } + + // 模态对话框必须困住 Tab,否则焦点会溜到背后被遮住的页面上。 + if (event.key === 'Tab') { + const stops = [...(dialogRef.current?.querySelectorAll(FOCUSABLE) || [])] + .filter((node) => node.tabIndex >= 0 && node.offsetParent !== null) + if (stops.length === 0) return + + const first = stops[0] + const last = stops[stops.length - 1] + const current = document.activeElement + + if (event.shiftKey && (current === first || !dialogRef.current?.contains(current))) { + event.preventDefault() + last.focus() + } else if (!event.shiftKey && (current === last || !dialogRef.current?.contains(current))) { + event.preventDefault() + first.focus() + } + return + } + + if (results.length === 0) return + + if (event.key === 'ArrowDown') { + event.preventDefault() + setActive((value) => (value + 1) % results.length) + } else if (event.key === 'ArrowUp') { + event.preventDefault() + setActive((value) => (value - 1 + results.length) % results.length) + } else if (event.key === 'Enter') { + event.preventDefault() + open(results[active].path) + } + } + + useEffect(() => { + listRef.current?.querySelector('[data-active="true"]')?.scrollIntoView({ block: 'nearest' }) + }, [active, results]) + + const hasResults = results.length > 0 + + return ( +
event.target === event.currentTarget && onClose()}> +
+
+ + setQuery(event.target.value)} + placeholder={c.placeholder} + ref={inputRef} + role="combobox" + type="search" + value={query} + /> + +
+ +
+ {query.trim() === '' &&

{c.hint}

} + {query.trim() !== '' && !hasResults && ( +

{c.empty}

+ )} + {/* 选中项靠 aria-activedescendant 汇报,所以结果本身不进 Tab 序列。 */} +
+ {results.map((result, position) => ( + + ))} +
+
+ +
+ {c.tips.map((tip) => {tip})} +
+
+
+ ) +} diff --git a/site/src/components/SiteHeader.jsx b/site/src/components/SiteHeader.jsx new file mode 100644 index 00000000..a0c15aa3 --- /dev/null +++ b/site/src/components/SiteHeader.jsx @@ -0,0 +1,120 @@ +import { useEffect, useState } from 'react' +import Icon from './icons' +import { toSiteHref } from '../content/docs' +import { useTheme } from '../lib/theme' +import SearchDialog from './SearchDialog' + +export const GITHUB_URL = 'https://github.com/NanmiCoder/cc-haha' +export const DOWNLOAD_URL = 'https://github.com/NanmiCoder/cc-haha/releases/latest' + +const copy = { + zh: { + docs: '文档', + download: '下载', + entries: [ + ['/start', '开始使用'], + ['/desktop', '桌面端功能'], + ['/internals', '深入原理'] + ], + menu: '打开导航', + // 页脚也有一栏叫「文档」,主导航得换个名字,否则地标列表里两个 nav 同名。 + nav: '主导航', + search: '搜索文档', + theme: '切换深浅色', + toEnglish: 'English' + }, + en: { + docs: 'Docs', + download: 'Download', + entries: [ + ['/en/start', 'Get started'], + ['/en/desktop', 'Desktop app'], + ['/en/internals', 'Internals'] + ], + menu: 'Open navigation', + nav: 'Main', + search: 'Search docs', + theme: 'Toggle theme', + toEnglish: '中文' + } +} + +export default function SiteHeader({ activeSection, locale = 'zh', localeHref }) { + const [open, setOpen] = useState(false) + const [searchOpen, setSearchOpen] = useState(false) + const { theme, toggle } = useTheme() + const c = copy[locale] || copy.zh + + useEffect(() => { + function onKeyDown(event) { + if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') { + event.preventDefault() + setSearchOpen(true) + return + } + // 展开的移动端菜单要能用 Esc 收起来,光靠点别处对键盘用户没用。 + if (event.key === 'Escape') setOpen(false) + } + + window.addEventListener('keydown', onKeyDown) + return () => window.removeEventListener('keydown', onKeyDown) + }, []) + + const home = locale === 'en' ? '/en' : '/' + const switchHref = localeHref || (locale === 'en' ? '/' : '/en') + + return ( + <> +
+
+ + + Claude Code Haha + + + + +
+ + + + + + + + {c.download} + + +
+
+
+ + {searchOpen && setSearchOpen(false)} />} + + ) +} diff --git a/site/src/components/icons.jsx b/site/src/components/icons.jsx new file mode 100644 index 00000000..bae14dc4 --- /dev/null +++ b/site/src/components/icons.jsx @@ -0,0 +1,38 @@ +const paths = { + arrow: 'M4 10h11M11 5l5 5-5 5', + book: 'M4 4.5A1.5 1.5 0 0 1 5.5 3H16v14H5.5A1.5 1.5 0 0 0 4 18.5V4.5ZM16 3v14', + check: 'M4 10.5 8 14.5 16 5.5', + close: 'M5 5l10 10M15 5 5 15', + copy: 'M7 7V4.5A1.5 1.5 0 0 1 8.5 3h7A1.5 1.5 0 0 1 17 4.5v7A1.5 1.5 0 0 1 15.5 13H13M3 8.5A1.5 1.5 0 0 1 4.5 7h7A1.5 1.5 0 0 1 13 8.5v7A1.5 1.5 0 0 1 11.5 17h-7A1.5 1.5 0 0 1 3 15.5v-7Z', + download: 'M10 3v10m0 0 4-4m-4 4-4-4M3.5 16.5h13', + github: 'M10 1.7a8.3 8.3 0 0 0-2.6 16.2c.4.1.6-.2.6-.5v-1.6c-2.3.5-2.8-1.1-2.8-1.1-.4-1-.9-1.2-.9-1.2-.8-.5 0-.5 0-.5.8.1 1.2.8 1.2.8.7 1.3 1.9.9 2.4.7.1-.5.3-.9.5-1.1-1.8-.2-3.8-.9-3.8-4.1 0-.9.3-1.7.8-2.3-.1-.2-.4-1 .1-2.1 0 0 .7-.2 2.3.9a7.7 7.7 0 0 1 4.2 0c1.6-1.1 2.3-.9 2.3-.9.5 1.1.2 1.9.1 2.1.5.6.8 1.4.8 2.3 0 3.2-2 3.9-3.8 4.1.3.3.6.8.6 1.6v2.4c0 .3.2.6.6.5A8.3 8.3 0 0 0 10 1.7Z', + menu: 'M3 6h14M3 10h14M3 14h14', + moon: 'M16 11.5A6.5 6.5 0 0 1 8.5 4a6.5 6.5 0 1 0 7.5 7.5Z', + search: 'M12.5 12.5 17 17M14 9a5 5 0 1 1-10 0 5 5 0 0 1 10 0Z', + sidebar: 'M3.5 4.5h13v11h-13v-11ZM8 4.5v11', + sun: 'M10 3.5v1.5M10 15v1.5M3.5 10H5m10 0h1.5M5.4 5.4l1 1m7.2 7.2 1 1m0-9.2-1 1m-7.2 7.2-1 1M13 10a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z', + sun_: '', + translate: 'M3 5h7M6.5 5v1.5c0 3-1.5 5-3.5 6M5 9.5c.7 1.7 2.2 3 4 3.5M10.5 17l3.2-8 3.3 8M11.9 14.2h4.3' +} + +export default function Icon({ name, size = 18, ...rest }) { + const d = paths[name] + if (!d) return null + + return ( + + ) +} diff --git a/site/src/content/docs.js b/site/src/content/docs.js index 94c2a04f..1f0deafe 100644 --- a/site/src/content/docs.js +++ b/site/src/content/docs.js @@ -1,12 +1,14 @@ import { Marked, Renderer } from 'marked' import DOMPurify from 'dompurify' -import { docsManifest } from '../generated/docs-manifest' +import { docsContent, docsIndex, imageSizes, sections } from '../generated/docs-index' const EXTERNAL_PROTOCOL = /^(?:[a-z][a-z\d+.-]*:|\/\/)/i const SITE_BASE = `/${String(import.meta.env.BASE_URL || '/').replace(/^\/+|\/+$/g, '')}` .replace(/\/{2,}/g, '/') .replace(/\/$/, '') || '/' +/* ── 路由归一化 ─────────────────────────────────────────────── */ + function cleanRoute(route) { const withoutQuery = route.split(/[?#]/, 1)[0] let normalized = decodeURIComponent(withoutQuery || '/') @@ -19,17 +21,21 @@ function cleanRoute(route) { return normalized } +function splitHref(href) { + const match = href.match(/^([^?#]*)([?#].*)?$/) + return { path: match?.[1] || '', suffix: match?.[2] || '' } +} + export function toSiteHref(href) { if (!href || href.startsWith('#') || EXTERNAL_PROTOCOL.test(href)) return href - const { path, suffix } = splitHref(href) - const route = /\.html$/i.test(path) - ? `/${decodeURIComponent(path).replace(/^\/+/, '').replace(/\/{2,}/g, '/')}` - : cleanRoute(path) + const { path: rawPath, suffix } = splitHref(href) + const route = /\.html$/i.test(rawPath) + ? `/${decodeURIComponent(rawPath).replace(/^\/+/, '').replace(/\/{2,}/g, '/')}` + : cleanRoute(rawPath) + if (SITE_BASE === '/') return `${route}${suffix}` - if (route === SITE_BASE || route.startsWith(`${SITE_BASE}/`)) { - return `${route}${suffix}` - } + if (route === SITE_BASE || route.startsWith(`${SITE_BASE}/`)) return `${route}${suffix}` return `${SITE_BASE}${route}${suffix}` } @@ -41,122 +47,141 @@ function withoutSiteBase(pathname) { return route } -function routeFromSourcePath(sourcePath) { - const withoutExtension = sourcePath.replace(/\.md$/i, '') +/* ── 索引 ───────────────────────────────────────────────────── */ - if (withoutExtension === 'index') return '/docs' - if (withoutExtension === 'en/index') return '/en/docs' - if (withoutExtension.endsWith('/index')) { - return cleanRoute(`/${withoutExtension.slice(0, -'/index'.length)}`) - } +const docsByRoute = new Map(docsIndex.map((doc) => [doc.path.toLowerCase(), doc])) +const docsBySource = new Map( + docsIndex.map((doc) => [doc.sourcePath.replace(/^docs\//, '').toLowerCase(), doc]) +) - return cleanRoute(`/${withoutExtension}`) +/** + * 2026-07 的信息架构重组把大部分文档挪了位置。老链接(README、外站、App 内) + * 不能死在 404 上,这里做一次性重定向。 + */ +const LEGACY_ROUTES = new Map(Object.entries({ + '/agent': '/internals/agent', + '/agent/01-usage-guide': '/internals/agent', + '/agent/02-implementation': '/internals/agent-internals', + '/agent/03-agent-framework': '/internals/agent-framework', + '/channel': '/internals/channel', + '/channel/01-channel-system': '/internals/channel', + '/channel/02-im-gateway-proposal': '/internals', + '/desktop/01-quick-start': '/start/first-session', + '/desktop/02-architecture': '/internals/desktop', + '/desktop/03-features': '/desktop', + '/desktop/04-installation': '/start/install', + '/desktop/05-faq': '/start/troubleshooting', + '/desktop/06-h5-access': '/desktop/remote', + '/desktop/07-electron-migration-research': '/internals/desktop', + '/desktop/08-electron-migration-tasks': '/internals/desktop', + '/desktop/09-electron-migration-validation-checklist': '/internals/desktop', + '/desktop/10-release-auto-update': '/internals/contributing', + '/docs': '/start', + '/features/computer-use': '/desktop/computer-use', + '/features/computer-use-architecture': '/internals/computer-use', + '/guide/cli-reference': '/cli/reference', + '/guide/contributing': '/internals/contributing', + '/guide/env-vars': '/cli/env', + '/guide/faq': '/start/troubleshooting', + '/guide/global-usage': '/cli', + '/guide/quick-start': '/cli', + '/guide/third-party-models': '/start/models', + '/memory': '/internals/memory', + '/memory/01-usage-guide': '/internals/memory', + '/memory/02-implementation': '/internals/memory-internals', + '/memory/03-autodream': '/internals/autodream', + '/reference/fixes': '/internals', + '/reference/local-server': '/internals/server', + '/reference/project-structure': '/internals/structure', + '/skills': '/internals/skills', + '/skills/01-usage-guide': '/internals/skills', + '/skills/02-implementation': '/internals/skills-internals' +})) + +export function resolveLegacyRoute(pathname) { + const route = withoutSiteBase(pathname) + const isEnglish = route === '/en' || route.startsWith('/en/') + const bare = isEnglish ? route.slice(3) || '/' : route + const target = LEGACY_ROUTES.get(bare.toLowerCase()) + + if (!target) return null + const next = isEnglish ? `/en${target}` : target + return docsByRoute.has(next.toLowerCase()) ? next : null } -function parseFrontmatter(source) { - if (!source.startsWith('---\n')) return { attributes: {}, body: source } +export function findDoc(pathname) { + if (!pathname) return null + return docsByRoute.get(withoutSiteBase(pathname).toLowerCase()) || null +} - const closingIndex = source.indexOf('\n---\n', 4) - if (closingIndex === -1) return { attributes: {}, body: source } +export function getAllDocs(locale) { + return docsIndex.filter((doc) => !locale || doc.locale === locale) +} - const attributes = {} - const block = source.slice(4, closingIndex) - for (const line of block.split('\n')) { - const separator = line.indexOf(':') - if (separator === -1) continue - const key = line.slice(0, separator).trim() - let value = line.slice(separator + 1).trim() - if (!key || !value) continue - value = value.replace(/^(['"])(.*)\1$/, '$2') - attributes[key] = value +export function getSections() { + return sections +} + +/** 侧边栏:按登记好的分区顺序分组,组内按 frontmatter 的 order。 */ +export function getDocNavigation(locale = 'zh') { + const grouped = new Map() + + for (const doc of getAllDocs(locale)) { + if (!grouped.has(doc.section)) grouped.set(doc.section, []) + grouped.get(doc.section).push(doc) } + const known = sections + .filter((section) => grouped.has(section.id)) + .map((section) => ({ id: section.id, label: section[locale] || section.id })) + const extras = [...grouped.keys()] + .filter((id) => !sections.some((section) => section.id === id)) + .sort() + .map((id) => ({ id, label: id })) + + return [...known, ...extras].map(({ id, label }) => ({ + id, + items: grouped.get(id).map((doc) => ({ + label: doc.navTitle || doc.title, + route: doc.path, + title: doc.title + })), + label + })) +} + +export function alternateLocaleRoute(doc) { + if (!doc) return '/' + + const source = doc.sourcePath.replace(/^docs\//, '') + const target = doc.locale === 'en' ? source.replace(/^en\//, '') : `en/${source}` + const alternate = docsBySource.get(target.toLowerCase()) + + if (alternate) return alternate.path + return doc.locale === 'en' ? '/start' : '/en/start' +} + +export function getAdjacentDocs(doc, navigation) { + const flat = navigation.flatMap((group) => + group.items.map((item) => ({ ...item, section: group.label })) + ) + const index = flat.findIndex((item) => item.route === doc.path) + return { - attributes, - body: source.slice(closingIndex + 5), + next: index >= 0 && index + 1 < flat.length ? flat[index + 1] : null, + previous: index > 0 ? flat[index - 1] : null } } -function plainText(value) { - return value - .replace(/<[^>]*>/g, '') - .replace(/!\[([^\]]*)\]\([^)]*\)/g, '$1') - .replace(/\[([^\]]+)\]\([^)]*\)/g, '$1') - .replace(/[*_`~]/g, '') - .trim() +/** 按需拉取一篇文档的正文,Vite 会把每篇切成独立 chunk。 */ +export async function loadDocContent(route) { + const load = docsContent[route] + if (!load) return null + const module = await load() + return module.default } -function titleFromBody(body, sourcePath) { - const match = body.match(/^#\s+(.+)$/m) - if (match) return plainText(match[1]) - - const filename = sourcePath.split('/').pop().replace(/\.md$/i, '') - return filename - .replace(/^\d+[-_.]?/, '') - .replace(/[-_]+/g, ' ') - .replace(/\b\w/g, (letter) => letter.toUpperCase()) -} - -function descriptionFromBody(body) { - const content = body - .replace(/^#.*$/gm, '') - .replace(/^>.*$/gm, '') - .replace(/```[\s\S]*?```/g, '') - .split(/\n\s*\n/) - .map(plainText) - .find((paragraph) => paragraph.length > 30) - - return content ? content.slice(0, 180) : '' -} - -function orderFromSourcePath(sourcePath) { - const filename = sourcePath.split('/').pop() - const match = filename.match(/^(\d+)/) - return match ? Number(match[1]) : filename === 'index.md' ? 0 : 999 -} - -function sectionFromSourcePath(sourcePath) { - const parts = sourcePath.split('/') - const offset = parts[0] === 'en' ? 1 : 0 - return parts[offset] === 'index.md' ? 'guide' : parts[offset] -} - -function preprocessDirectives(markdown) { - return markdown.replace( - /^(:{3,})\s*(info|tip|warning|danger)?\s*([^\n]*)\n([\s\S]*?)^\1\s*$/gm, - (_, _fence, kind = 'info', label, content) => { - const safeKind = kind || 'info' - const heading = label.trim() || { - danger: 'Danger', - info: 'Info', - tip: 'Tip', - warning: 'Warning', - }[safeKind] - return `\n\n` - }, - ) -} - -const docs = docsManifest - .map((record) => { - const sourcePath = record.sourcePath.replace(/^docs\//, '') - const { attributes, body } = parseFrontmatter(String(record.content)) - return { - attributes, - body, - description: record.description || attributes.description || descriptionFromBody(body), - locale: record.locale, - order: orderFromSourcePath(sourcePath), - route: record.path, - section: record.section || sectionFromSourcePath(sourcePath), - sourcePath, - title: record.title || attributes.title || titleFromBody(body, sourcePath), - } - }) - .sort((left, right) => left.sourcePath.localeCompare(right.sourcePath)) - -const docsByRoute = new Map(docs.map((doc) => [doc.route.toLowerCase(), doc])) -const docsBySource = new Map(docs.map((doc) => [doc.sourcePath.toLowerCase(), doc])) +/* ── Markdown 渲染 ──────────────────────────────────────────── */ function normalizeRelativePath(basePath, targetPath) { const segments = basePath.split('/') @@ -171,21 +196,14 @@ function normalizeRelativePath(basePath, targetPath) { return segments.join('/') } -function splitHref(href) { - const match = href.match(/^([^?#]*)([?#].*)?$/) - return { - path: match?.[1] || '', - suffix: match?.[2] || '', - } -} - function resolveSourceReference(doc, href) { - const { path, suffix } = splitHref(href) - if (!path || path.startsWith('#') || EXTERNAL_PROTOCOL.test(path)) return href + const { path: rawPath, suffix } = splitHref(href) + if (!rawPath || rawPath.startsWith('#') || EXTERNAL_PROTOCOL.test(rawPath)) return href - const sourcePath = path.startsWith('/') - ? path.replace(/^\/+/, '') - : normalizeRelativePath(doc.sourcePath, path) + const source = doc.sourcePath.replace(/^docs\//, '') + const sourcePath = rawPath.startsWith('/') + ? rawPath.replace(/^\/+/, '') + : normalizeRelativePath(source, rawPath) return { sourcePath, suffix } } @@ -195,18 +213,14 @@ export function resolveDocHref(doc, href) { if (typeof reference === 'string') return reference const { sourcePath, suffix } = reference - if (/\.html$/i.test(sourcePath)) { - return `/${sourcePath.replace(/^\/+/, '')}${suffix}` - } + if (/\.html$/i.test(sourcePath)) return `/${sourcePath.replace(/^\/+/, '')}${suffix}` - const markdownPath = sourcePath.endsWith('.md') - ? sourcePath - : `${sourcePath.replace(/\/+$/, '')}.md` + const markdownPath = sourcePath.endsWith('.md') ? sourcePath : `${sourcePath.replace(/\/+$/, '')}.md` const indexPath = `${sourcePath.replace(/\/+$/, '')}/index.md` const target = docsBySource.get(markdownPath.toLowerCase()) || docsBySource.get(indexPath.toLowerCase()) - if (target) return `${target.route}${suffix}` + if (target) return `${target.path}${suffix}` return `${cleanRoute(`/${sourcePath}`)}${suffix}` } @@ -218,6 +232,15 @@ export function resolveDocAsset(doc, href) { return `${toSiteHref(`/${reference.sourcePath}`)}${reference.suffix}` } +function plainText(value) { + return value + .replace(/<[^>]*>/g, '') + .replace(/!\[([^\]]*)\]\([^)]*\)/g, '$1') + .replace(/\[([^\]]+)\]\([^)]*\)/g, '$1') + .replace(/[*_`~]/g, '') + .trim() +} + function textFromTokens(tokens = []) { return tokens.map((token) => { if (typeof token.text === 'string') return plainText(token.text) @@ -242,10 +265,79 @@ function escapeAttribute(value) { .replace(/>/g, '>') } -export function renderMarkdown(doc) { +function preprocessDirectives(markdown) { + return markdown.replace( + /^(:{3,})\s*(info|tip|warning|danger)?\s*([^\n]*)\n([\s\S]*?)^\1\s*$/gm, + (_, _fence, kind = 'info', label, content) => { + const safeKind = kind || 'info' + const heading = label.trim() || { + danger: '注意', + info: '说明', + tip: '提示', + warning: '当心' + }[safeKind] + return `\n\n` + } + ) +} + +/** 语法高亮按需加载:首屏不为它买单,加载好之后重渲染一次。 */ +let highlighter = null +let highlighterPromise = null + +export function getHighlighter() { + return highlighter +} + +export function ensureHighlighter() { + if (highlighter) return Promise.resolve(highlighter) + if (highlighterPromise) return highlighterPromise + + highlighterPromise = (async () => { + const [{ default: hljs }, ...languages] = await Promise.all([ + import('highlight.js/lib/core'), + import('highlight.js/lib/languages/bash'), + import('highlight.js/lib/languages/typescript'), + import('highlight.js/lib/languages/javascript'), + import('highlight.js/lib/languages/json'), + import('highlight.js/lib/languages/yaml'), + import('highlight.js/lib/languages/xml'), + import('highlight.js/lib/languages/markdown'), + import('highlight.js/lib/languages/powershell'), + import('highlight.js/lib/languages/nginx'), + import('highlight.js/lib/languages/css'), + import('highlight.js/lib/languages/ini'), + import('highlight.js/lib/languages/python'), + import('highlight.js/lib/languages/diff') + ]) + + const names = [ + 'bash', 'typescript', 'javascript', 'json', 'yaml', 'xml', 'markdown', + 'powershell', 'nginx', 'css', 'ini', 'python', 'diff' + ] + names.forEach((name, index) => hljs.registerLanguage(name, languages[index].default)) + hljs.registerAliases(['sh', 'shell', 'zsh', 'console'], { languageName: 'bash' }) + hljs.registerAliases(['ts', 'tsx'], { languageName: 'typescript' }) + hljs.registerAliases(['js', 'jsx', 'mjs'], { languageName: 'javascript' }) + hljs.registerAliases(['yml'], { languageName: 'yaml' }) + hljs.registerAliases(['html', 'svg'], { languageName: 'xml' }) + hljs.registerAliases(['md'], { languageName: 'markdown' }) + hljs.registerAliases(['toml', 'conf', 'env', 'dotenv'], { languageName: 'ini' }) + hljs.registerAliases(['py'], { languageName: 'python' }) + + hljs.configure({ classPrefix: 'hljs-' }) + highlighter = hljs + return hljs + })() + + return highlighterPromise +} + +export function renderMarkdown(doc, markdown) { const renderer = new Renderer() const headingCounts = new Map() const tableOfContents = [] + const hljs = highlighter renderer.heading = function heading({ tokens, depth }) { const content = this.parser.parseInline(tokens) @@ -255,11 +347,13 @@ export function renderMarkdown(doc) { const id = count === 0 ? baseSlug : `${baseSlug}-${count}` headingCounts.set(baseSlug, count + 1) - if (depth >= 2 && depth <= 3) { - tableOfContents.push({ depth, id, text }) - } + if (depth >= 2 && depth <= 3) tableOfContents.push({ depth, id, text }) - return `${content}#` + const anchor = depth >= 2 && depth <= 3 + ? `#` + : '' + + return `${content}${anchor}` } renderer.link = function link({ href, title, tokens }) { @@ -267,89 +361,84 @@ export function renderMarkdown(doc) { const isExternal = EXTERNAL_PROTOCOL.test(resolvedHref) const isStaticHtml = /\.html(?:[?#]|$)/i.test(resolvedHref) const titleAttribute = title ? ` title="${escapeAttribute(title)}"` : '' - const externalAttributes = isExternal + const extra = isExternal ? ' target="_blank" rel="noreferrer noopener"' : isStaticHtml ? '' : ` data-doc-link data-doc-route="${escapeAttribute(resolvedHref)}"` const publicHref = isExternal ? resolvedHref : toSiteHref(resolvedHref) - return `${this.parser.parseInline(tokens)}` + return `${this.parser.parseInline(tokens)}` } renderer.image = function image({ href, title, text }) { const resolvedHref = resolveDocAsset(doc, href) const titleAttribute = title ? ` title="${escapeAttribute(title)}"` : '' - return `${escapeAttribute(text || '')}` + // 带上真实像素尺寸,浏览器才能在图片下载完之前按比例留位,正文不会跳。 + const size = imageSizes[resolvedHref.split(/[?#]/, 1)[0]] + const sizeAttributes = size ? ` width="${size[0]}" height="${size[1]}"` : '' + // 竖图(手机截图、聊天软件的扫码页)在正文列宽下能铺满两屏,得单独限高。 + // 构建期就知道比例,所以这里标出来,CSS 不用去猜文件名。 + const tall = size ? ` data-tall="${size[1] / size[0] > 1.1}"` : '' + return `${escapeAttribute(text || '')}` } - renderer.code = function code({ text, lang, escaped }) { + // 宽表格在窄屏上必须自己横向滚动,不能把页面撑出横向滚动条。 + renderer.table = function table(token) { + const header = `${token.header.map((cell, index) => + `${this.parser.parseInline(cell.tokens)}` + ).join('')}` + + const body = token.rows.map((row) => + `${row.map((cell, index) => + `${this.parser.parseInline(cell.tokens)}` + ).join('')}` + ).join('') + + return `
${header}${body}
` + } + + renderer.code = function code({ text, lang }) { const language = (lang || '').trim().split(/\s+/)[0] if (language === 'mermaid') { return `
${escapeAttribute(text)}
` } + let markup = escapeAttribute(text) + if (hljs && language && hljs.getLanguage(language)) { + try { + markup = hljs.highlight(text, { ignoreIllegals: true, language }).value + } catch { + markup = escapeAttribute(text) + } + } + const className = language ? ` class="language-${escapeAttribute(language)}"` : '' - const codeText = escaped ? text : escapeAttribute(text) - return `
${codeText}
` + return `
${markup}
` } - const parser = new Marked({ - gfm: true, - renderer, - }) - const html = DOMPurify.sanitize(parser.parse(preprocessDirectives(doc.body)), { + const parser = new Marked({ gfm: true, renderer }) + const html = DOMPurify.sanitize(parser.parse(preprocessDirectives(markdown)), { ADD_ATTR: [ 'aria-label', 'data-doc-link', 'data-doc-route', 'data-language', 'data-mermaid-pending', + 'data-tall', 'decoding', + 'height', 'loading', 'target', + 'width' ], - FORBID_TAGS: ['embed', 'form', 'iframe', 'input', 'object', 'script', 'style', 'textarea'], + FORBID_TAGS: ['embed', 'form', 'iframe', 'input', 'object', 'script', 'style', 'textarea'] }) - return { - html, - tableOfContents, - } -} - -function normalizeRequestedRoute(pathname) { - let route = withoutSiteBase(pathname) - - if (route === '/docs') return '/desktop' - if (route === '/en/docs') return '/en/desktop' - if (route.startsWith('/docs/')) route = route.slice('/docs'.length) - if (route.startsWith('/en/docs/')) route = `/en${route.slice('/en/docs'.length)}` - return route -} - -export function findDoc(pathname) { - if (!pathname) return null - const route = normalizeRequestedRoute(pathname) - return docsByRoute.get(route.toLowerCase()) || null -} - -export function getAllDocs(locale) { - return docs.filter((doc) => !locale || doc.locale === locale) -} - -export function getAdjacentDocs(doc, navigation) { - const routes = navigation.flatMap((group) => group.items.map((item) => item.route)) - const index = routes.indexOf(doc.route) - return { - next: index >= 0 && index + 1 < routes.length ? findDoc(routes[index + 1]) : null, - previous: index > 0 ? findDoc(routes[index - 1]) : null, - } + return { html, tableOfContents } } export function isDocRoute(pathname) { - return Boolean(findDoc(pathname)) - || cleanRoute(pathname) === '/docs' - || cleanRoute(pathname) === '/en/docs' + return Boolean(findDoc(pathname)) || Boolean(resolveLegacyRoute(pathname)) } -export { docs } +export { docsIndex } diff --git a/site/src/content/navigation.js b/site/src/content/navigation.js deleted file mode 100644 index 759e881e..00000000 --- a/site/src/content/navigation.js +++ /dev/null @@ -1,138 +0,0 @@ -import { findDoc, getAllDocs } from './docs' - -const sectionOrder = [ - 'guide', - 'desktop', - 'features', - 'agent', - 'skills', - 'memory', - 'im', - 'channel', - 'reference', -] - -const sectionLabels = { - zh: { - agent: '多 Agent', - channel: 'Channel 研究', - desktop: '桌面工作台', - features: '核心能力', - guide: '开始使用', - im: '远程连接', - memory: '记忆系统', - reference: '开发者参考', - skills: 'Skills 系统', - }, - en: { - agent: 'Multi-Agent', - channel: 'Channel research', - desktop: 'Desktop workspace', - features: 'Core capabilities', - guide: 'Get started', - im: 'Remote access', - memory: 'Memory', - reference: 'Developer reference', - skills: 'Skills', - }, -} - -const preferredLabels = new Map([ - ['/desktop', '桌面端概览'], - ['/desktop/01-quick-start', '三分钟快速上手'], - ['/desktop/02-architecture', '桌面端架构'], - ['/desktop/03-features', '完整功能地图'], - ['/desktop/04-installation', '安装与更新'], - ['/desktop/05-FAQ', '桌面端排障'], - ['/desktop/06-h5-access', '手机与浏览器访问'], - ['/desktop/pets', '桌面宠物'], - ['/features/computer-use', 'Computer Use'], - ['/features/computer-use-architecture', 'Computer Use 架构'], - ['/guide/quick-start', 'CLI 安装与启动'], - ['/guide/cli-reference', 'CLI 参考'], - ['/guide/global-usage', '全局使用(任意目录启动)'], - ['/guide/faq', '常见问题与求助'], - ['/guide/env-vars', '环境变量'], - ['/guide/contributing', '贡献指南与本地质量门禁'], - ['/guide/third-party-models', '连接模型服务'], - ['/agent', '多 Agent 系统概览'], - ['/agent/01-usage-guide', '使用指南'], - ['/agent/02-implementation', '实现原理'], - ['/agent/03-agent-framework', 'Agent 框架深度解析'], - ['/skills/01-usage-guide', '使用指南'], - ['/skills/02-implementation', '实现原理'], - ['/memory', '记忆系统概览'], - ['/memory/01-usage-guide', '使用指南'], - ['/memory/02-implementation', '实现原理'], - ['/memory/03-autodream', 'AutoDream 记忆整合'], - ['/im', 'IM 接入总览'], - ['/channel', 'Channel 源码研究'], - ['/channel/01-channel-system', 'Channel 系统架构解析'], - ['/channel/02-im-gateway-proposal', 'IM Gateway 方案设计(历史设计稿)'], - ['/reference/local-server', '本地服务与 API'], - ['/reference/fixes', '泄露源码修复说明'], - ['/reference/project-structure', '项目结构'], - ['/en/desktop', 'Desktop overview'], - ['/en/desktop/01-quick-start', 'Quick start'], - ['/en/desktop/03-features', 'Feature map'], - ['/en/desktop/04-installation', 'Install & update'], - ['/en/desktop/05-FAQ', 'Desktop troubleshooting'], - ['/en/desktop/06-h5-access', 'Browser & mobile access'], - ['/en/desktop/pets', 'Desktop pets'], - ['/en/features/computer-use', 'Computer Use'], - ['/en/guide/quick-start', 'CLI quick start'], - ['/en/guide/third-party-models', 'Connect a model'], - ['/en/agent', 'Multi-agent overview'], - ['/en/agent/01-usage-guide', 'Usage guide'], - ['/en/agent/02-implementation', 'Implementation'], - ['/en/agent/03-agent-framework', 'Agent framework deep dive'], - ['/en/skills/01-usage-guide', 'Usage guide'], - ['/en/skills/02-implementation', 'Implementation'], - ['/en/memory', 'Memory system overview'], - ['/en/memory/01-usage-guide', 'Usage guide'], - ['/en/memory/02-implementation', 'Implementation'], - ['/en/memory/03-autodream', 'AutoDream memory consolidation'], - ['/en/im', 'IM overview'], - ['/en/reference/local-server', 'Local server & API'], - ['/en/reference/fixes', 'Fixes vs. leaked source'], -]) - -export function getDocNavigation(locale = 'zh') { - const grouped = new Map() - for (const doc of getAllDocs(locale)) { - const sectionDocs = grouped.get(doc.section) || [] - sectionDocs.push(doc) - grouped.set(doc.section, sectionDocs) - } - - return [...grouped.entries()] - .sort(([left], [right]) => { - const leftIndex = sectionOrder.indexOf(left) - const rightIndex = sectionOrder.indexOf(right) - return (leftIndex === -1 ? Number.MAX_SAFE_INTEGER : leftIndex) - - (rightIndex === -1 ? Number.MAX_SAFE_INTEGER : rightIndex) - || left.localeCompare(right) - }) - .map(([section, sectionDocs]) => ({ - label: sectionLabels[locale]?.[section] || section, - items: sectionDocs - .sort((left, right) => left.order - right.order || left.title.localeCompare(right.title)) - .map((doc) => ({ - label: preferredLabels.get(doc.route) || doc.title, - route: doc.route, - title: doc.title, - })), - })) -} - -export function alternateLocaleRoute(doc) { - if (!doc) return '/' - - const sourcePath = doc.locale === 'en' - ? doc.sourcePath.replace(/^en\//, '') - : `en/${doc.sourcePath}` - const alternate = findDoc(`/${sourcePath.replace(/(?:\/index)?\.md$/i, '')}`) - - if (alternate) return alternate.route - return doc.locale === 'en' ? '/docs' : '/en/docs' -} diff --git a/site/src/docs/doc.css b/site/src/docs/doc.css index c28b4343..34cd6cef 100644 --- a/site/src/docs/doc.css +++ b/site/src/docs/doc.css @@ -1,669 +1,597 @@ -.doc-site-header { - position: sticky; - top: 0; - z-index: 40; +/* ============================================================================ + 文档阅读体验:侧边栏 · 正文 · 目录 + ========================================================================== */ + +.doc-layout { display: grid; - height: 64px; - padding: 0 clamp(18px, 2.6vw, 32px); - grid-template-columns: 1fr auto 1fr; - align-items: center; - border-bottom: 1px solid var(--line); - background: color-mix(in srgb, var(--paper) 88%, transparent); - backdrop-filter: blur(16px); -} - -.doc-site-header__brand, -.doc-site-header__actions { - display: flex; - align-items: center; - gap: 12px; -} - -.doc-site-header__brand { - width: fit-content; - font-size: 15px; - font-weight: 600; - letter-spacing: -0.01em; -} - -.doc-site-header__brand img { - width: 28px; - height: 28px; - object-fit: contain; -} - -.doc-site-header__brand small { - padding: 3px 7px; - color: var(--accent); - border: 1px solid currentColor; - font-family: var(--mono); - font-size: 9px; - font-weight: 500; - letter-spacing: 0.14em; - text-transform: uppercase; -} - -.doc-site-header__marker { - display: flex; - align-items: center; - gap: 9px; - justify-self: center; - color: var(--ink-soft); - font-family: var(--mono); - font-size: 10px; - letter-spacing: 0.2em; - text-transform: uppercase; -} - -.doc-site-header__marker::before, -.doc-site-header__marker::after { - width: 22px; - height: 1px; - background: var(--line-strong); - content: ''; -} - -.doc-site-header__actions { - justify-self: end; - gap: 22px; - font-family: var(--mono); - font-size: 11px; - letter-spacing: 0.04em; -} - -.doc-site-header__actions a { - color: var(--ink-soft); - transition: color 160ms ease; -} - -.doc-site-header__actions a:hover { - color: var(--accent); -} - -.doc-shell { - display: grid; - max-width: 1440px; - min-height: calc(100vh - 64px); + grid-template-columns: var(--w-sidebar) minmax(0, 1fr) var(--w-toc); + gap: var(--sp-10); + align-items: start; + max-width: calc(var(--w-page) + 240px); margin: 0 auto; - grid-template-columns: 264px minmax(0, 800px) 216px; - justify-content: center; + padding: 0 var(--sp-6) var(--sp-24); } -.doc-sidebar { +/* ── 侧边栏 ─────────────────────────────────────────────────── */ + +.doc-nav { position: sticky; - top: 64px; - align-self: start; - height: calc(100vh - 64px); + top: var(--h-header); + max-height: calc(100vh - var(--h-header)); + padding: var(--sp-8) var(--sp-2) var(--sp-10) 0; overflow-y: auto; - padding: 34px 26px 64px 0; - border-right: 1px solid var(--line); + overscroll-behavior: contain; scrollbar-width: thin; } -.doc-sidebar__eyebrow { - display: flex; - align-items: center; - gap: 9px; - margin-bottom: 26px; - color: var(--ink-soft); - font-family: var(--mono); - font-size: 10px; - font-weight: 500; - letter-spacing: 0.2em; - text-transform: uppercase; +.doc-nav__group + .doc-nav__group { + margin-top: var(--sp-6); } -.doc-sidebar__eyebrow::before { - width: 6px; - height: 6px; - background: var(--accent); - content: ''; -} - -.doc-sidebar__group { - padding: 13px 0; - border-top: 1px solid var(--line); -} - -.doc-sidebar__group:last-child { - border-bottom: 1px solid var(--line); -} - -.doc-sidebar__group summary { - display: flex; - align-items: baseline; - gap: 10px; - color: var(--ink); - cursor: pointer; - font-size: 13px; +.doc-nav__title { + display: block; + padding: 0 var(--sp-3) var(--sp-2); + color: var(--text-3); + font-size: var(--fs-xs); font-weight: 600; - list-style: none; - transition: color 160ms ease; + letter-spacing: 0.02em; } -.doc-sidebar__group summary:hover { - color: var(--accent); -} - -.doc-sidebar__group summary::-webkit-details-marker { - display: none; -} - -.doc-sidebar__group summary .doc-sidebar__index { - color: var(--accent); - font-family: var(--mono); - font-size: 10px; - font-weight: 500; -} - -.doc-sidebar__group summary::after { - margin-left: auto; - color: var(--ink-soft); - content: '+'; - font-family: var(--mono); - font-size: 12px; -} - -.doc-sidebar__group[open] summary::after { - content: '−'; -} - -.doc-sidebar__group ul { - display: grid; +.doc-nav__list { + display: flex; + flex-direction: column; gap: 1px; - margin: 9px 0 2px; + margin: 0; padding: 0; list-style: none; } -.doc-sidebar__group li { - margin: 0; -} - -.doc-sidebar__group a { - position: relative; +.doc-nav__link { display: block; - padding: 6px 8px 6px 22px; - color: var(--ink-soft); - font-size: 12.5px; + padding: 6px var(--sp-3); + border-radius: var(--r-md); + color: var(--text-2); + font-size: var(--fs-base); line-height: 1.5; - transition: color 140ms ease, background 140ms ease; + transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease); } -.doc-sidebar__group a::before { - position: absolute; - top: 0; - bottom: 0; - left: 8px; - width: 2px; - background: transparent; - content: ''; - transition: background 140ms ease; +.doc-nav__link:hover { + background: var(--surface-hover); + color: var(--text); } -.doc-sidebar__group a:hover { - color: var(--ink); - background: var(--paper-bright); +.doc-nav__link[aria-current='page'] { + background: var(--brand-soft); + color: var(--on-brand-soft); + font-weight: 500; } -.doc-sidebar__group a[aria-current='page'] { - color: var(--accent); - font-weight: 600; - background: var(--accent-soft); -} - -.doc-sidebar__group a[aria-current='page']::before { - background: var(--accent); -} +/* ── 正文列 ─────────────────────────────────────────────────── */ .doc-main { min-width: 0; - padding: 58px clamp(32px, 5.5vw, 84px) 110px; + max-width: var(--w-prose); + padding-top: var(--sp-10); } -.doc-main__meta { +.doc-breadcrumb { display: flex; - margin-bottom: 40px; align-items: center; - justify-content: space-between; - color: var(--ink-soft); - font-family: var(--mono); - font-size: 10px; - letter-spacing: 0.16em; - text-transform: uppercase; + gap: var(--sp-2); + margin-bottom: var(--sp-4); + color: var(--text-3); + font-size: var(--fs-sm); } -.doc-main__meta span { - display: inline-flex; - align-items: center; - gap: 9px; +.doc-breadcrumb a:hover { + color: var(--brand); } -.doc-main__meta span::before { - width: 6px; - height: 6px; - background: var(--accent); - content: ''; +.doc-breadcrumb span[aria-hidden] { + color: var(--border-control); } -.doc-main__meta a { - padding-bottom: 4px; - border-bottom: 1px solid var(--line-strong); - color: var(--ink-soft); - text-transform: none; - transition: color 160ms ease, border-color 160ms ease; +/* ── Markdown 正文 ──────────────────────────────────────────── */ + +.prose { + color: var(--text); + font-size: var(--fs-lg); + line-height: 1.78; } -.doc-main__meta a:hover { - border-color: var(--accent); - color: var(--accent); -} - -/* ---------- content ---------- */ - -.doc-content { - color: #2b2620; - font-size: 15.5px; - line-height: 1.9; -} - -.doc-content > :first-child { +.prose > *:first-child { margin-top: 0; } -.doc-content h1 { - max-width: 720px; - margin: 0 0 34px; - font-family: var(--serif); - font-size: clamp(34px, 3.6vw, 52px); - font-weight: 900; - letter-spacing: 0; - line-height: 1.22; - text-wrap: balance; -} - -.doc-content h1::after { - display: block; - width: 44px; - height: 3px; - margin-top: 30px; - background: var(--accent); - content: ''; -} - -.doc-content h2 { - margin: 68px 0 18px; - padding-top: 26px; - border-top: 1px solid var(--line-strong); - font-family: var(--serif); - font-size: clamp(24px, 2.3vw, 30px); +.prose h1 { + margin: 0 0 var(--sp-4); + font-size: var(--fs-5xl); font-weight: 700; - letter-spacing: 0; - line-height: 1.3; - text-wrap: balance; + letter-spacing: -0.015em; } -.doc-content h3 { - margin: 44px 0 12px; - font-size: 18px; - font-weight: 700; - letter-spacing: 0; - line-height: 1.4; +.prose h2 { + margin: var(--sp-16) 0 var(--sp-4); + padding-top: var(--sp-6); + border-top: 1px solid var(--border); + font-size: var(--fs-3xl); } -.doc-content h4 { - margin: 34px 0 10px; - font-size: 15.5px; - font-weight: 700; +.prose h3 { + margin: var(--sp-10) 0 var(--sp-3); + font-size: var(--fs-2xl); } -.doc-heading-anchor { - margin-left: 9px; - color: var(--accent); - font-family: var(--mono); - font-size: 0.6em; +.prose h4 { + margin: var(--sp-8) 0 var(--sp-2); + font-family: var(--font-sans); + font-size: var(--fs-lg); + font-weight: 600; +} + +.prose h1 + p { + color: var(--text-2); + font-size: var(--fs-xl); + line-height: 1.7; +} + +.prose p, +.prose ul, +.prose ol, +.prose blockquote { + margin: var(--sp-5) 0; +} + +.prose ul, +.prose ol { + padding-left: 1.35em; +} + +.prose li { + margin: var(--sp-2) 0; +} + +.prose li > ul, +.prose li > ol { + margin: var(--sp-2) 0; +} + +.prose li::marker { + color: var(--text-3); +} + +.prose a { + color: var(--brand); + text-decoration: underline; + text-decoration-color: var(--brand-border); + text-underline-offset: 3px; + transition: text-decoration-color var(--t-fast) var(--ease); +} + +.prose a:hover { + text-decoration-color: var(--brand); +} + +.prose strong { + font-weight: 600; +} + +.prose hr { + margin: var(--sp-12) 0; + border: 0; + border-top: 1px solid var(--border); +} + +.prose blockquote { + padding: var(--sp-1) 0 var(--sp-1) var(--sp-5); + border-left: 2px solid var(--border-strong); + color: var(--text-2); +} + +.prose blockquote p { + margin: var(--sp-2) 0; +} + +/* 标题锚点 */ +.doc-anchor { + margin-left: var(--sp-2); + color: var(--border-strong); + font-family: var(--font-mono); + font-size: 0.7em; font-weight: 400; + text-decoration: none; opacity: 0; - transition: opacity 140ms ease; + transition: opacity var(--t-fast) var(--ease); } -.doc-content :is(h2, h3, h4):hover .doc-heading-anchor, -.doc-heading-anchor:focus-visible { +.prose h2:hover .doc-anchor, +.prose h3:hover .doc-anchor, +.doc-anchor:focus-visible { opacity: 1; } -.doc-content p, -.doc-content ul, -.doc-content ol { - margin: 17px 0; +/* 行内代码 */ +.prose :not(pre) > code { + padding: 0.15em 0.4em; + border: 1px solid var(--code-border); + border-radius: var(--r-sm); + background: var(--code-bg); + font-family: var(--font-mono); + font-size: 0.87em; + word-break: break-word; } -.doc-content li + li { - margin-top: 5px; -} - -.doc-content li::marker { - color: var(--accent); -} - -.doc-content a { - color: var(--accent-deep); - text-decoration: underline; - text-decoration-color: rgba(165, 45, 17, 0.35); - text-decoration-thickness: 1px; - text-underline-offset: 4px; - transition: color 140ms ease, text-decoration-color 140ms ease; -} - -.doc-content a:hover { - color: var(--accent); - text-decoration-color: currentColor; -} - -.doc-content strong { - font-weight: 700; -} - -.doc-content blockquote { - margin: 30px 0; - padding: 4px 0 4px 22px; - border-left: 2px solid var(--line-strong); - color: var(--ink-soft); - font-family: var(--serif); - font-size: 1.02em; - line-height: 1.85; -} - -.doc-content blockquote > :first-child, -.doc-callout > :first-child { - margin-top: 0; -} - -.doc-content blockquote > :last-child, -.doc-callout > :last-child { - margin-bottom: 0; -} - -.doc-callout { - margin: 30px 0; - padding: 20px 22px; - border: 1px solid var(--line); - border-left: 3px solid var(--accent); - background: var(--paper-bright); -} - -.doc-callout > strong:first-child { - display: block; - margin-bottom: 8px; - color: var(--accent); - font-family: var(--mono); - font-size: 10.5px; - font-weight: 500; - letter-spacing: 0.14em; - text-transform: uppercase; -} - -.doc-callout--warning, -.doc-callout--danger { - border-left-color: var(--accent-deep); -} - -.doc-callout--warning > strong:first-child, -.doc-callout--danger > strong:first-child { - color: var(--accent-deep); -} - -.doc-content code { - padding: 0.14em 0.4em; - color: var(--accent-deep); - border: 1px solid var(--line); - background: var(--paper-bright); - font-family: var(--mono); - font-size: 0.84em; -} - -.doc-content pre { +/* 代码块 */ +.prose pre { position: relative; + margin: var(--sp-6) 0; + padding: var(--sp-5); + border: 1px solid var(--code-border); + border-radius: var(--r-lg); + background: var(--code-bg); overflow-x: auto; - margin: 28px 0; - padding: 46px 24px 22px; - color: #e8e2d2; - border: 1px solid #000; - background: var(--ink-deep); + font-size: var(--fs-sm); line-height: 1.75; + -webkit-overflow-scrolling: touch; } -.doc-content pre::before { - position: absolute; - top: 14px; - left: 22px; - color: #8f8875; - content: attr(data-language); - font-family: var(--mono); - font-size: 9px; - letter-spacing: 0.16em; - text-transform: uppercase; -} - -.doc-content pre::after { - position: absolute; - top: 19px; - right: 20px; - width: 18px; - height: 1px; - background: var(--accent); - content: ''; -} - -.doc-content pre code { - padding: 0; - color: inherit; - border: 0; - background: transparent; - font-size: 12.5px; -} - -.doc-mermaid { - display: grid; - min-height: 220px; - margin: 34px 0; - padding: 30px; - place-items: center; - overflow-x: auto; - border: 1px solid var(--line); - background: - radial-gradient(rgba(26, 22, 16, 0.13) 1px, transparent 1.4px), - var(--paper-bright); - background-size: 22px 22px, auto; -} - -.doc-mermaid > svg { - width: auto; - max-width: 100%; - height: auto; -} - -.doc-mermaid[data-mermaid-pending='true'] pre, -.doc-mermaid[data-mermaid-pending='error'] pre { - width: 100%; - margin: 0; -} - -.doc-mermaid[data-mermaid-pending='error']::before { - align-self: end; - color: var(--accent); - content: 'DIAGRAM SOURCE'; - font-family: var(--mono); - font-size: 9px; - letter-spacing: 0.14em; -} - -.doc-content table { +.prose pre code { display: block; - width: 100%; - margin: 30px 0; - overflow-x: auto; - border-spacing: 0; - border-collapse: collapse; - font-size: 13.5px; + padding: 0; + border: 0; + background: none; + font-family: var(--font-mono); + font-size: inherit; + white-space: pre; } -.doc-content :is(th, td) { - min-width: 120px; - padding: 11px 14px; - border: 1px solid var(--line); +.prose pre[data-language]:not([data-language='text'])::after { + content: attr(data-language); + position: absolute; + top: var(--sp-2); + right: var(--sp-3); + color: var(--text-3); + font-family: var(--font-mono); + font-size: var(--fs-2xs); + letter-spacing: 0.04em; + pointer-events: none; +} + +/* 语法高亮(highlight.js 类名 → 桌面端代码色板)*/ +.hljs-comment, +.hljs-quote { + color: var(--c-comment); + font-style: italic; +} +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-doctag, +.hljs-name { + color: var(--c-keyword); +} +.hljs-string, +.hljs-regexp, +.hljs-addition { + color: var(--c-string); +} +.hljs-title, +.hljs-section, +.hljs-title_function_ { + color: var(--c-function); +} +.hljs-number, +.hljs-symbol, +.hljs-bullet, +.hljs-link { + color: var(--c-number); +} +.hljs-attr, +.hljs-attribute, +.hljs-variable, +.hljs-template-variable { + color: var(--c-property); +} +.hljs-type, +.hljs-built_in, +.hljs-params { + color: var(--c-type); +} +.hljs-punctuation, +.hljs-operator, +.hljs-meta { + color: var(--c-punct); +} +.hljs-emphasis { + font-style: italic; +} +.hljs-strong { + font-weight: 600; +} + +/* 表格 */ +.doc-table-wrap { + margin: var(--sp-6) 0; + border: 1px solid var(--border); + border-radius: var(--r-lg); + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} + +.prose table { + width: 100%; + border-collapse: collapse; + font-size: var(--fs-base); +} + +.prose th, +.prose td { + padding: 10px var(--sp-4); + border-bottom: 1px solid var(--border); text-align: left; vertical-align: top; } -.doc-content th { - color: var(--ink); - background: var(--paper-bright); - font-family: var(--mono); - font-size: 11px; - font-weight: 500; - letter-spacing: 0.08em; +.prose thead th { + background: var(--surface-muted); + color: var(--text-2); + font-weight: 600; + white-space: nowrap; } -.doc-content tr:nth-child(even) td { - background: rgba(250, 247, 238, 0.6); +.prose tbody tr:last-child td { + border-bottom: 0; } -.doc-content img { - display: block; +/* 图片。默认满宽 + height:auto,配合渲染器写进标签的 width/height 属性, + 浏览器在图片下载完之前就能按比例留位,懒加载不再让正文跳动。 + (`width: auto` 会让未加载的图塌成 0,占位就失效了。) */ +.prose img { + width: 100%; + height: auto; + margin: var(--sp-6) 0; + border: 1px solid var(--border); + border-radius: var(--r-lg); + box-shadow: var(--shadow-sm); +} + +/* 竖图满宽会铺满近两屏。哪些算竖图由构建期算好的宽高比决定(渲染器打 + data-tall),不靠文件名匹配 —— wechat02 / dingding02 这类并不叫 h5-*。 */ +.prose img[data-tall='true'] { width: auto; max-width: 100%; - height: auto; - margin: 36px auto; - border: 1px solid var(--line-strong); - background: var(--paper-bright); - box-shadow: 0 24px 50px -30px rgba(26, 22, 16, 0.35); + max-height: 620px; + margin-inline: auto; } -.doc-content hr { - height: 1px; - margin: 54px 0; - border: 0; - background: var(--line); +/* 提示块 */ +.doc-callout { + margin: var(--sp-6) 0; + padding: var(--sp-4) var(--sp-5); + border: 1px solid var(--border); + border-left-width: 3px; + border-radius: var(--r-md); + background: var(--surface-sunken); + font-size: var(--fs-md); } -/* ---------- toc ---------- */ - -.doc-toc { - position: sticky; - top: 64px; - align-self: start; - max-height: calc(100vh - 64px); - overflow-y: auto; - padding: 52px 8px 54px 26px; - border-left: 1px solid var(--line); -} - -.doc-toc > p { - display: flex; - align-items: center; - gap: 9px; - margin: 0 0 18px; - color: var(--ink-soft); - font-family: var(--mono); - font-size: 10px; - font-weight: 500; - letter-spacing: 0.2em; - text-transform: uppercase; -} - -.doc-toc > p::before { - width: 6px; - height: 6px; - background: var(--accent); - content: ''; -} - -.doc-toc ol { - display: grid; - gap: 4px; - margin: 0; - padding: 0; - list-style: none; -} - -.doc-toc a { +.doc-callout > strong { display: block; - padding: 3px 0 3px 12px; - border-left: 2px solid transparent; - color: var(--ink-soft); - font-size: 11.5px; - line-height: 1.5; - transition: color 140ms ease, border-color 140ms ease, transform 180ms var(--ease-out); + margin-bottom: var(--sp-1); + font-size: var(--fs-sm); + font-weight: 600; + letter-spacing: 0.02em; } -.doc-toc a:hover { - color: var(--ink); +.doc-callout > *:last-child { + margin-bottom: 0; } -.doc-toc a.is-active { - border-left-color: var(--accent); - color: var(--accent); - transform: translateX(2px); +.doc-callout p { + margin: var(--sp-2) 0; } -.doc-toc__depth-3 { - padding-left: 12px; +.doc-callout--info { + border-left-color: var(--info); + background: var(--info-soft); +} +.doc-callout--info > strong { + color: var(--info-ink); } -/* ---------- pager ---------- */ +.doc-callout--tip { + border-left-color: var(--ok); + background: var(--ok-soft); +} +.doc-callout--tip > strong { + color: var(--ok-ink); +} + +.doc-callout--warning { + border-left-color: var(--warn); + background: var(--warn-soft); +} +.doc-callout--warning > strong { + color: var(--warn-ink); +} + +.doc-callout--danger { + border-left-color: var(--danger); + background: var(--danger-soft); +} +.doc-callout--danger > strong { + color: var(--danger-ink); +} + +/* mermaid */ +.doc-mermaid { + margin: var(--sp-6) 0; + padding: var(--sp-5); + border: 1px solid var(--border); + border-radius: var(--r-lg); + background: var(--surface-sunken); + overflow-x: auto; + text-align: center; +} + +.doc-mermaid[data-mermaid-pending='true'] pre { + margin: 0; + border: 0; + background: none; + color: var(--text-3); + font-size: var(--fs-sm); + text-align: left; +} + +.doc-mermaid svg { + height: auto; + margin: 0 auto; +} + +/* ── 上一页 / 下一页 ────────────────────────────────────────── */ .doc-pager { display: grid; - margin-top: 90px; - grid-template-columns: 1fr 1fr; - gap: 14px; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: var(--sp-4); + margin-top: var(--sp-16); + padding-top: var(--sp-8); + border-top: 1px solid var(--border); } -.doc-pager > a { - display: grid; - min-height: 108px; - padding: 18px 20px; - align-content: space-between; - border: 1px solid var(--line); - background: var(--paper-bright); - transition: background 180ms ease, border-color 180ms ease, color 180ms ease; +.doc-pager__link { + display: flex; + flex-direction: column; + gap: var(--sp-1); + padding: var(--sp-4) var(--sp-5); + border: 1px solid var(--border); + border-radius: var(--r-lg); + transition: border-color var(--t-base) var(--ease), background var(--t-base) var(--ease); } -.doc-pager > a:last-child { +.doc-pager__link:hover { + border-color: var(--border-strong); + background: var(--surface-sunken); +} + +.doc-pager__link--next { + grid-column: 2; text-align: right; } -.doc-pager > a:hover { - color: var(--paper-bright); - border-color: var(--ink); - background: var(--ink); +.doc-pager__dir { + color: var(--text-3); + font-size: var(--fs-sm); } -.doc-pager > a:hover small { - color: var(--accent); +.doc-pager__title { + color: var(--text); + font-size: var(--fs-md); + font-weight: 500; } -.doc-pager small { - color: var(--ink-soft); - font-family: var(--mono); - font-size: 9.5px; - letter-spacing: 0.14em; - text-transform: uppercase; - transition: color 180ms ease; +.doc-pager__section { + color: var(--text-3); + font-size: var(--fs-xs); } -.doc-pager span { - font-family: var(--serif); - font-size: 17px; - font-weight: 700; +/* ── 右侧目录 ───────────────────────────────────────────────── */ + +.doc-toc { + position: sticky; + top: var(--h-header); + max-height: calc(100vh - var(--h-header)); + padding: var(--sp-10) 0; + overflow-y: auto; + scrollbar-width: thin; } -/* ---------- responsive ---------- */ +.doc-toc__title { + margin-bottom: var(--sp-3); + color: var(--text-3); + font-size: var(--fs-xs); + font-weight: 600; + letter-spacing: 0.02em; +} + +.doc-toc__list { + display: flex; + flex-direction: column; + gap: 1px; + margin: 0; + padding: 0; + border-left: 1px solid var(--border); + list-style: none; +} + +.doc-toc__link { + display: block; + padding: 4px var(--sp-3); + margin-left: -1px; + border-left: 1px solid transparent; + color: var(--text-3); + font-size: var(--fs-sm); + line-height: 1.5; + transition: color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease); +} + +.doc-toc__link:hover { + color: var(--text); +} + +.doc-toc__link[data-depth='3'] { + padding-left: var(--sp-6); +} + +.doc-toc__link[aria-current='true'] { + border-left-color: var(--brand); + color: var(--brand); +} + +/* ── 移动端:抽屉式导航 ────────────────────────────────────── */ + +.doc-mobilebar { + display: none; + position: sticky; + z-index: var(--z-sticky); + top: var(--h-header); + align-items: center; + gap: var(--sp-3); + padding: var(--sp-3) var(--sp-5); + border-bottom: 1px solid var(--border); + background: var(--glass); + backdrop-filter: saturate(180%) blur(12px); +} + +.doc-mobilebar button { + display: inline-flex; + align-items: center; + gap: var(--sp-2); + padding: 6px var(--sp-3); + border: 1px solid var(--border); + border-radius: var(--r-md); + color: var(--text-2); + font-size: var(--fs-sm); +} + +.doc-mobilebar__here { + min-width: 0; + overflow: hidden; + color: var(--text-3); + font-size: var(--fs-sm); + text-overflow: ellipsis; + white-space: nowrap; +} + +.doc-scrim { + display: none; +} @media (max-width: 1180px) { - .doc-shell { - grid-template-columns: 244px minmax(0, 800px); + .doc-layout { + grid-template-columns: var(--w-sidebar) minmax(0, 1fr); + gap: var(--sp-8); } .doc-toc { @@ -671,84 +599,83 @@ } } -@media (max-width: 760px) { - .doc-site-header { - height: 58px; - padding: 0 18px; - grid-template-columns: 1fr auto; - } - - .doc-site-header__brand small, - .doc-site-header__marker, - .doc-site-header__actions a:first-child { - display: none; - } - - .doc-shell { +@media (max-width: 900px) { + .doc-layout { display: block; + padding-inline: var(--sp-5); } - .doc-sidebar { - position: static; - height: auto; - max-height: 44vh; - padding: 22px 20px 26px; - border-right: 0; - border-bottom: 1px solid var(--line-strong); - } - - .doc-sidebar__eyebrow { - margin-bottom: 12px; - } - - .doc-sidebar nav { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 0 22px; + .doc-mobilebar { + display: flex; } .doc-main { - padding: 44px 20px 80px; + max-width: none; + padding-top: var(--sp-8); } - .doc-main__meta { - margin-bottom: 30px; + .doc-nav { + position: fixed; + z-index: var(--z-drawer); + top: 0; + bottom: 0; + left: 0; + width: min(320px, 86vw); + max-height: none; + padding: var(--sp-6) var(--sp-4); + border-right: 1px solid var(--border); + background: var(--bg); + box-shadow: var(--shadow-lg); + transform: translateX(-102%); + /* 收起来的抽屉只是被推出屏幕,仍然可以 Tab 进去 —— 39 个链接会变成 + 39 个看不见的焦点停靠点。visibility 把它整体移出焦点序列和无障碍树, + 延迟到滑出动画结束再生效。 */ + visibility: hidden; + transition: transform var(--t-base) var(--ease), visibility 0s linear var(--t-base); } - .doc-content h1 { - font-size: clamp(30px, 9vw, 44px); + .doc-nav[data-open='true'] { + transform: translateX(0); + visibility: visible; + transition: transform var(--t-base) var(--ease), visibility 0s; } - .doc-content h2 { - margin-top: 56px; + .doc-scrim[data-open='true'] { + display: block; + position: fixed; + z-index: var(--z-scrim); + inset: 0; + background: var(--scrim); } - .doc-content pre { - margin-right: -8px; - margin-left: -8px; + /* 抽屉展开时锁住背景滚动:遮罩挡得住点击,挡不住滑动, + 不锁的话手指划在遮罩上会把底下的正文滚走。 */ + html:has(.doc-nav[data-open='true']) { + overflow: hidden; + } + + .prose { + font-size: var(--fs-md); + } + + .prose h1 { + font-size: var(--fs-4xl); + } + + .prose h2 { + margin-top: var(--sp-12); + font-size: var(--fs-2xl); + } + + .prose h3 { + font-size: var(--fs-xl); } .doc-pager { - grid-template-columns: 1fr; + grid-template-columns: minmax(0, 1fr); } - .doc-pager > a:last-child { - text-align: left; - } -} - -@media (max-width: 480px) { - .doc-sidebar nav { - grid-template-columns: 1fr; - } - - .doc-sidebar { - max-height: 34vh; - } - - .doc-main__meta { - align-items: flex-start; - gap: 14px; - flex-direction: column; + .doc-pager__link--next { + grid-column: 1; } } diff --git a/site/src/lib/meta.js b/site/src/lib/meta.js new file mode 100644 index 00000000..b5ac963f --- /dev/null +++ b/site/src/lib/meta.js @@ -0,0 +1,61 @@ +const SITE_ORIGIN = 'https://claudecode-haha.relakkesyang.org' + +function upsert(selector, create) { + let node = document.head.querySelector(selector) + if (!node) { + node = create() + document.head.append(node) + } + return node +} + +function setMetaContent(attribute, key, value) { + const selector = `meta[${attribute}="${key}"]` + if (!value) { + document.head.querySelector(selector)?.remove() + return + } + const node = upsert(selector, () => { + const meta = document.createElement('meta') + meta.setAttribute(attribute, key) + return meta + }) + node.setAttribute('content', value) +} + +function setLink(rel, href, hreflang) { + const selector = hreflang ? `link[rel="${rel}"][hreflang="${hreflang}"]` : `link[rel="${rel}"]:not([hreflang])` + if (!href) { + document.head.querySelector(selector)?.remove() + return + } + const node = upsert(selector, () => { + const link = document.createElement('link') + link.setAttribute('rel', rel) + if (hreflang) link.setAttribute('hreflang', hreflang) + return link + }) + node.setAttribute('href', href) +} + +/** + * SPA 换页时同步 title / description / canonical / hreflang。 + * 静态构建会把同样的值写进每条路由的 HTML 骨架,所以爬虫拿到的也是对的。 + */ +export function setPageMeta({ alternate, canonical, description, lang, title }) { + document.title = title + document.documentElement.lang = lang + + setMetaContent('name', 'description', description) + setMetaContent('property', 'og:title', title) + setMetaContent('property', 'og:description', description) + setMetaContent('property', 'og:url', canonical ? `${SITE_ORIGIN}${canonical}` : null) + + setLink('canonical', canonical ? `${SITE_ORIGIN}${canonical}` : null) + + if (canonical) { + const isEnglish = canonical === '/en' || canonical.startsWith('/en/') + setLink('alternate', `${SITE_ORIGIN}${canonical}`, isEnglish ? 'en' : 'zh-Hans') + setLink('alternate', alternate ? `${SITE_ORIGIN}${alternate}` : null, isEnglish ? 'zh-Hans' : 'en') + } +} diff --git a/site/src/lib/theme.js b/site/src/lib/theme.js new file mode 100644 index 00000000..b8ffc3ff --- /dev/null +++ b/site/src/lib/theme.js @@ -0,0 +1,60 @@ +import { useCallback, useEffect, useState } from 'react' + +const STORAGE_KEY = 'cch-theme' + +export function readStoredTheme() { + try { + const stored = localStorage.getItem(STORAGE_KEY) + return stored === 'dark' || stored === 'light' ? stored : null + } catch { + return null + } +} + +export function systemTheme() { + return window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' +} + +export function applyTheme(theme) { + document.documentElement.dataset.theme = theme +} + +/** + * 深浅色跟随系统,用户手动切过之后以用户的选择为准。 + * 首帧的主题由 index.html 里的引导脚本设置,避免闪白。 + */ +export function useTheme() { + const [theme, setTheme] = useState(() => + (typeof document === 'undefined' ? 'light' : document.documentElement.dataset.theme || 'light') + ) + + useEffect(() => { + const media = window.matchMedia?.('(prefers-color-scheme: dark)') + if (!media) return undefined + + const onChange = () => { + if (readStoredTheme()) return + const next = media.matches ? 'dark' : 'light' + applyTheme(next) + setTheme(next) + } + + media.addEventListener('change', onChange) + return () => media.removeEventListener('change', onChange) + }, []) + + const toggle = useCallback(() => { + setTheme((current) => { + const next = current === 'dark' ? 'light' : 'dark' + applyTheme(next) + try { + localStorage.setItem(STORAGE_KEY, next) + } catch { + /* 隐私模式下写不进去,忽略即可 */ + } + return next + }) + }, []) + + return { theme, toggle } +} diff --git a/site/src/main.jsx b/site/src/main.jsx index b3010648..a22bc914 100644 --- a/site/src/main.jsx +++ b/site/src/main.jsx @@ -1,10 +1,13 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import App from './App' -import './styles/site.css' +import ErrorBoundary from './components/ErrorBoundary' +import './styles/base.css' createRoot(document.getElementById('root')).render( - + + + , ) diff --git a/site/src/pages/home/HomePage.jsx b/site/src/pages/home/HomePage.jsx index 60cb9f07..b8b5861d 100644 --- a/site/src/pages/home/HomePage.jsx +++ b/site/src/pages/home/HomePage.jsx @@ -1,451 +1,319 @@ -import { useEffect, useRef, useState } from 'react' -import Icon from '../../components/Icon' -import appIcon from '../../../../docs/images/app-icon.png' -import mainSession from '../../../../docs/images/desktop_ui/25_main_session.png' -import petSettings from '../../../../docs/images/desktop_ui/14_pet_settings_overview.png' -import computerUse from '../../../../docs/images/desktop_ui/06_settings_computer_use.png' -import scheduledTask from '../../../../docs/images/desktop_ui/20_scheduled_task.png' -import skillMarketplace from '../../../../docs/images/desktop_ui/21_skill_marketplace.png' -import diffReview from '../../../../docs/images/desktop_ui/23_workspace_diff_review.png' -import browserPreview from '../../../../docs/images/desktop_ui/24_browser_preview.png' -import dada from '../../../../desktop/src/assets/agent-mascots/agent-mascot-code.png' -import huhu from '../../../../desktop/src/assets/agent-mascots/agent-mascot-plan.png' -import bubu from '../../../../desktop/src/assets/agent-mascots/agent-mascot-fix.png' -import huihui from '../../../../desktop/src/assets/agent-mascots/agent-mascot-build.png' +import { useEffect, useState } from 'react' +import Icon from '../../components/icons' +import SiteHeader, { DOWNLOAD_URL, GITHUB_URL } from '../../components/SiteHeader' +import { toSiteHref } from '../../content/docs' +import { setPageMeta } from '../../lib/meta' +import { content, mascotAccents, mascots } from './content' +import './home.css' -const DOWNLOAD_URL = 'https://github.com/NanmiCoder/cc-haha/releases/latest' -const GITHUB_URL = 'https://github.com/NanmiCoder/cc-haha' +const SOURCE_COMMANDS = [ + 'git clone https://github.com/NanmiCoder/cc-haha.git', + 'cd cc-haha && bun install', + './bin/claude-haha' +] -const content = { - zh: { - nav: ['产品旅程', '真实界面', '认识搭档', '文档'], - download: '下载桌面端', - eyebrow: 'CLAUDE CODE · 现在有了一间工作室', - titleA: 'Agent 干活的全过程,', - titleB: '你看得见、审得完、管得住。', - intro: 'Claude Code 的本地优先桌面工作室:跑会话、派 Agent、审改动,离开电脑用手机继续同一条会话。', - primary: '下载 macOS / Windows / Linux', - secondary: '先跑通第一条会话', - local: '本地优先', - providers: '模型自选', - remote: '离开也能续', - fig: 'FIG. 01 — MAIN SESSION · 实拍直出', - figStatus: '127.0.0.1 · 已连接', - heroNote: '真实截图,没修过——连光标都是原装的。', - rail: '桌面工作室 · A STUDIO FOR CLAUDE CODE · 本地优先 ·', - journeyEyebrow: 'ONE SESSION, MANY HANDS', - journeyTitle: '一句话开工,五步见结果。', - journeyIntro: '所有功能挂在同一条会话上:你说目标,它们各司其职,进度随时可查。', - steps: [ - ['01', '说出任务', '在 Main Session 里输入目标,选好项目、模型和权限模式,回车开工。'], - ['02', '拆开派活', '大活交给 Task、SubAgent 或 Agent Team 并行干,后台 Agent 的工具活动也汇总进活动面板。'], - ['03', '审完再落地', '改动按文件逐个看 Diff,试验放进 Worktree 不碰主分支,你点头才落地。'], - ['04', '把手伸出电脑', 'Computer Use 让 Agent 操作真实桌面;出门用手机 H5 或 IM 继续会话,断连不中断。'], - ['05', '到点自动跑', '把重复流程设成定时任务,到点在独立会话执行,每跑一次都留记录可复盘。'], - ], - tourEyebrow: 'REAL PRODUCT, NOT A MOCKUP', - tourTitle: '所有截图,都拍自真实产品。', - tourIntro: '不用概念图充数:全部截取自运行中的桌面端,你看到的功能,装好就是那个样子。', - tours: [ - { id: 'session', label: 'Main Session', kicker: '从对话到代码', title: '描述目标,看它一步步做完。', body: '选好项目、模型和权限后开聊;它产生的每处代码改动都留在上下文里,等你审完再落地。', image: mainSession }, - { id: 'pets', label: '桌面宠物', kicker: '状态一眼可见', title: '宠物在动,Agent 就在干活。', body: '搭搭、弧弧、补补、回回随任务状态变换动作;上传一张角色图,就能养一只自己的。', image: petSettings }, - { id: 'computer', label: 'Computer Use', kicker: '看得见,也动得了', title: '能看屏幕,能点能输能验证。', body: '原生无障碍引擎驱动:Agent 能看屏幕、点鼠标、敲键盘,做完还会自己核对;授权分级,敏感操作等你点头。', image: computerUse }, - { id: 'schedule', label: '定时任务', kicker: '到点自动开工', title: '设好时间,它按时回来交活。', body: '定好频率、模型、目录和通知方式;任务在独立会话执行,每跑一次都有记录可查。', image: scheduledTask }, - { id: 'skills', label: 'Skills & Agents', kicker: '给 Agent 添本事', title: '缺什么手艺,装什么手艺。', body: '看中就装,来源和安全提示摆在明处;再给自己的 Agent 单独配模型、工具和系统提示词。', image: skillMarketplace }, - { id: 'diff', label: '代码审阅', kicker: '先看清楚再决定', title: '改了什么,逐个文件看清楚。', body: '改动按文件列出,Diff 逐行对照;Worktree 把试验隔在主分支外,合不合你说了算。', image: diffReview }, - { id: 'preview', label: '浏览器预览', kicker: '做完当场验证', title: '页面效果,会话里直接验证。', body: '在应用里打开本地或公开网页,看到问题直接带回会话继续改;出门就换 H5 或 IM 接力。', image: browserPreview }, - ], - petEyebrow: 'MEET THE CREW', - petTitle: '认识搭搭、弧弧、补补、回回。', - petIntro: '一个图标代表所有状态,太敷衍了。搭搭、弧弧、补补、回回随任务变换动作——忙不忙,看一眼就知道。', - pets: [ - ['搭搭', 'Dada', '搭建', '把想法一块块变成可运行的东西。', dada, '#2eaa91'], - ['弧弧', 'Huhu', '规划', '复杂任务也能画出一条清楚路线。', huhu, '#3577d4'], - ['补补', 'Bubu', '修复', '找到裂缝,验证之后再补好它。', bubu, '#e56645'], - ['回回', 'Huihui', '构建', '新回复一到,就抱着齿轮继续跑。', huihui, '#7657c8'], - ], - docsEyebrow: 'THE FIELD GUIDE', - docsTitle: '按任务查,不按目录翻。', - docsIntro: '文档按真实任务组织:先跑通第一次会话,再按需深入配置、原理和排障。', - docGroups: [ - ['第一次上手', '装好应用、接上模型账号,跑通你的第一条 Main Session。', '/desktop/01-quick-start', '打开快速上手'], - ['用熟桌面端', '会话、多 Agent 协作、代码审阅、宠物和定时任务,一个个用起来。', '/desktop/03-features', '逛逛桌面功能'], - ['连到电脑外', '配置 Computer Use、手机 H5 和 IM 接入,弄清授权和安全边界。', '/features/computer-use', '配置远程入口'], - ['看懂底层', '本地服务、Agent 机制、记忆、Skills 和项目结构,写给想改代码的人。', '/reference/project-structure', '读开发者参考'], - ], - installEyebrow: 'READY WHEN YOU ARE', - installTitle: '现在下载,马上开工。', - installBody: 'GitHub Releases 有三平台安装包,也可 bun install 从源码跑。打开、选项目、说任务,今天就跑通第一条会话。', - copy: '复制', - copied: '已复制', - footer: '本地优先的 Claude Code 桌面工作室', - }, - en: { - nav: ['Journey', 'Real UI', 'Meet the crew', 'Docs'], - download: 'Download desktop', - eyebrow: 'CLAUDE CODE · NOW HAS A STUDIO', - titleA: 'Every step your agents take,', - titleB: 'you can see, review, and stop.', - intro: 'A local-first studio for Claude Code: run sessions, split work across agents, review diffs, and keep going from your phone.', - primary: 'Download for macOS / Windows / Linux', - secondary: 'Run your first session', - local: 'Local-first', - providers: 'Bring your models', - remote: 'Phone-friendly', - fig: 'FIG. 01 — MAIN SESSION, AS SHIPPED', - figStatus: '127.0.0.1 · CONNECTED', - heroNote: 'A real screenshot, unretouched—cursor included as found.', - rail: 'A STUDIO FOR CLAUDE CODE · LOCAL-FIRST · DESKTOP-NATIVE ·', - journeyEyebrow: 'ONE SESSION, MANY HANDS', - journeyTitle: 'One sentence starts it. Five steps finish it.', - journeyIntro: 'Every capability hangs off the same session, so you never switch tools to get from ask to done.', - steps: [ - ['01', 'Say the task', 'Type the goal into Main Session, pick the project, model, and permission mode, and hit enter.'], - ['02', 'Split the work', 'Break big jobs across tasks, subagents, or an agent team. Background progress still rolls up into the activity panel.'], - ['03', 'Review, then land', 'Open a diff for every changed file, keep experiments in a worktree, and land nothing until you approve it.'], - ['04', 'Reach past the screen', 'Computer Use operates the real desktop. Away from it, continue the same session from H5 or IM—disconnects don\'t interrupt.'], - ['05', 'Put it on a clock', 'Turn repeatable routines into scheduled jobs that run in their own sessions, each run leaving a record to revisit.'], - ], - tourEyebrow: 'REAL PRODUCT, NOT A MOCKUP', - tourTitle: 'Every screenshot is the real app.', - tourIntro: 'No concept art. The feature you see in a frame is the one that opens after install.', - tours: [ - { id: 'session', label: 'Main Session', kicker: 'From prompt to code', title: 'Describe the goal. Watch it get done.', body: 'Pick the project, model, and permissions, then start talking. Every change it makes stays open for your review.', image: mainSession }, - { id: 'pets', label: 'Desktop pets', kicker: 'Status at a glance', title: 'If a pet is moving, an agent is working.', body: 'Dada, Huhu, Bubu, and Huihui move with the task at hand. Upload one character image and raise a pet of your own.', image: petSettings }, - { id: 'computer', label: 'Computer Use', kicker: 'Eyes and hands on the desktop', title: 'Sees the screen. Clicks, types, verifies.', body: 'Computer Use runs on a native accessibility engine with tiered authorization. Sensitive moves still wait for your nod.', image: computerUse }, - { id: 'schedule', label: 'Scheduled work', kicker: 'Back on time', title: 'Set the time. It comes back with results.', body: 'Pick a cadence, model, directory, and notification. Jobs run in their own sessions, and every run leaves a record you can review.', image: scheduledTask }, - { id: 'skills', label: 'Skills & Agents', kicker: 'A studio that can grow', title: 'Missing a trick? Install it.', body: 'Install what looks useful—source and safety notes are shown up front. Then give each agent its own model, tools, and system prompt.', image: skillMarketplace }, - { id: 'diff', label: 'Code review', kicker: 'Look before you land', title: 'Know exactly what changed, file by file.', body: 'Every edit is listed per file with its diff; worktrees keep experiments off your main branch. What lands is your call.', image: diffReview }, - { id: 'preview', label: 'Browser preview', kicker: 'Verify on the spot', title: 'Check the page without leaving the session.', body: 'Open local or public pages inside the app and bring what you see back into the task. Step away and hand off to H5 or IM instead.', image: browserPreview }, - ], - petEyebrow: 'MEET THE CREW', - petTitle: 'Meet Dada, Huhu, Bubu, and Huihui.', - petIntro: 'One icon for every state felt lazy, so we adopted Dada, Huhu, Bubu, and Huihui—running, thinking, waiting, done. One glance tells you.', - pets: [ - ['搭搭', 'Dada', 'Build', 'Turns an idea into something you can run.', dada, '#2eaa91'], - ['弧弧', 'Huhu', 'Plan', 'Finds a clear route through complicated work.', huhu, '#3577d4'], - ['补补', 'Bubu', 'Fix', 'Finds the crack, proves it, then patches it.', bubu, '#e56645'], - ['回回', 'Huihui', 'Ship', 'Grabs the gear and moves when a reply arrives.', huihui, '#7657c8'], - ], - docsEyebrow: 'THE FIELD GUIDE', - docsTitle: 'Find answers by task, not by chapter.', - docsIntro: 'The docs follow real tasks: get your first session running, then go deeper into configuration, internals, and fixes as needed.', - docGroups: [ - ['First launch', 'Install the app, connect a model account, and finish your first Main Session.', '/en/desktop/01-quick-start', 'Open the quick start'], - ['The desktop studio', 'Main Session, the activity panel, code review, pets, and scheduled runs.', '/en/desktop/03-features', 'Tour the desktop app'], - ['Beyond the machine', 'Set up Computer Use, H5, and IM access, and know exactly where the permission boundaries are.', '/en/features/computer-use', 'Configure remote access'], - ['Under the hood', 'Local services, agent mechanics, memory, skills, and project structure—written for people who hack on it.', '/en/reference/project-structure', 'Read the developer reference'], - ], - installEyebrow: 'READY WHEN YOU ARE', - installTitle: 'Download it and get to work.', - installBody: 'Grab the installer on GitHub Releases, or bun install from source. Open the app, pick a project, and say your first task.', - copy: 'Copy', - copied: 'Copied', - footer: 'A local-first desktop studio for Claude Code', - }, -} - -function useReveal() { - const scope = useRef(null) - - useEffect(() => { - const root = scope.current - if (!root) return - - const observer = new IntersectionObserver((entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - entry.target.dataset.visible = 'true' - observer.unobserve(entry.target) - } - }) - }, { threshold: 0.14 }) - - root.querySelectorAll('[data-reveal]').forEach((node) => observer.observe(node)) - return () => observer.disconnect() - }, []) - - return scope -} - -function Header({ locale, c }) { - const [open, setOpen] = useState(false) - const prefix = locale === 'en' ? '/en' : '' - const ids = ['journey', 'tour', 'crew', 'guide'] - const closeMenu = () => setOpen(false) - - return ( -
- - - Claude Code Haha - - - -
- ) -} - -function Hero({ locale, c }) { +function Hero({ c, locale }) { return (
-
-
{c.eyebrow}
-

{c.titleA}{c.titleB}

-

{c.intro}

- -
- {[c.local, c.providers, c.remote].map((item) => {item})} -
-
-
-
-
) } -function Journey({ c }) { +function Capabilities({ c }) { return ( -
-
-
{c.journeyEyebrow}
-

{c.journeyTitle}

-

{c.journeyIntro}

+
+
+
+

{c.capabilities.title}

+

{c.capabilities.lede}

+
+
    + {c.capabilities.items.map(([title, body]) => ( +
  • +

    {title}

    +

    {body}

    +
  • + ))} +
-
    - {c.steps.map(([number, title, body]) => ( -
  1. -
    {number}
    -

    {title}

    {body}

    - -
  2. - ))} -
) } -function ProductTour({ c }) { - const [activeId, setActiveId] = useState(c.tours[0].id) - const active = c.tours.find((tour) => tour.id === activeId) || c.tours[0] +function Tour({ c, locale }) { + const [activeId, setActiveId] = useState(c.tour.tabs[0].id) + useEffect(() => setActiveId(c.tour.tabs[0].id), [c]) - useEffect(() => setActiveId(c.tours[0].id), [c]) + const active = c.tour.tabs.find((tab) => tab.id === activeId) || c.tour.tabs[0] - const selectAdjacentTab = (event, currentIndex) => { + function onKeyDown(event, index) { if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) return - event.preventDefault() - let nextIndex = currentIndex - if (event.key === 'Home') nextIndex = 0 - if (event.key === 'End') nextIndex = c.tours.length - 1 - if (event.key === 'ArrowLeft') nextIndex = (currentIndex - 1 + c.tours.length) % c.tours.length - if (event.key === 'ArrowRight') nextIndex = (currentIndex + 1) % c.tours.length - const nextTour = c.tours[nextIndex] - setActiveId(nextTour.id) - event.currentTarget.parentElement - ?.querySelector(`#tour-tab-${nextTour.id}`) - ?.focus() + const count = c.tour.tabs.length + let next = index + if (event.key === 'Home') next = 0 + if (event.key === 'End') next = count - 1 + if (event.key === 'ArrowLeft') next = (index - 1 + count) % count + if (event.key === 'ArrowRight') next = (index + 1) % count + + setActiveId(c.tour.tabs[next].id) + event.currentTarget.parentElement?.querySelector(`#tour-${c.tour.tabs[next].id}`)?.focus() } return ( -
-
-
-
{c.tourEyebrow}
-

{c.tourTitle}

+
+
+
+

{c.tour.title}

+

{c.tour.lede}

+
+ +
+ {c.tour.tabs.map((tab, index) => ( + + ))}
-

{c.tourIntro}

-
-
- {c.tours.map((tour, index) => ( - - ))} -
-
-
-
{active.kicker}
-

{active.title}

-

{active.body}

-
-
- {`${active.label} + +
+
+

{active.title}

+

{active.body}

+
+
+ {active.title} +
) } -function Crew({ c }) { +function Crew({ c, locale }) { return ( -
-
-
{c.petEyebrow}
-

{c.petTitle}

-

{c.petIntro}

-
-
- {c.pets.map(([name, latin, role, body, image, color], index) => ( -
-
UNIT 0{index + 1}
-
-
{role}
-

{name}{latin}

-

{body}

-
- ))} -
-
- ) -} - -function Guide({ c }) { - return ( -
-
-
{c.docsEyebrow}
-

{c.docsTitle}

-

{c.docsIntro}

-
-
- {c.docGroups.map(([title, body, href, action], index) => ( - - {String(index + 1).padStart(2, '0')} - {title}{body} - {action} +
+
+ +
    + {c.crew.members.map(([name, latin, role, body], index) => ( +
  • + + {role} +

    {name}{name !== latin && {latin}}

    +

    {body}

    +
  • + ))} +
) } -function Install({ locale, c }) { - const [copied, setCopied] = useState(false) - const command = [ - 'git clone https://github.com/NanmiCoder/cc-haha.git', - 'cd cc-haha && bun install', - './bin/claude-haha', - ].join('\n') +function Paths({ c }) { + return ( +
+
+
+

{c.paths.title}

+

{c.paths.lede}

+
+
+ {c.paths.items.map((item) => ( +
+ {item.eyebrow} +

{item.title}

+

{item.body}

+ +
+ ))} +
+
+
+ ) +} - const copyCommand = async () => { - await navigator.clipboard.writeText(command) - setCopied(true) - window.setTimeout(() => setCopied(false), 1600) +function Install({ c, locale }) { + const [copied, setCopied] = useState(false) + + async function copy() { + try { + await navigator.clipboard.writeText(SOURCE_COMMANDS.join('\n')) + setCopied(true) + window.setTimeout(() => setCopied(false), 1600) + } catch { + /* 剪贴板被禁用时静默失败,命令仍然可以手选复制 */ + } } return ( -
-
-
{c.installEyebrow}
-

{c.installTitle}

-

{c.installBody}

-
- {c.primary} - Installation guide +
+
+
+

{c.install.title}

+

{c.install.lede}

+ +
+ +
+
+ {c.install.commandLabel} + +
+
{SOURCE_COMMANDS.map((line) => `$ ${line}`).join('\n')}
-
-
-
CLI · SOURCE CHECKOUT
- - $ git clone https://github.com/NanmiCoder/cc-haha.git
- $ cd cc-haha && bun install
- $ ./bin/claude-haha -
- -

Desktop + CLI · same local runtime

-
) } -function Footer({ locale, c }) { +function Footer({ c, locale }) { return ( -