diff --git "a/docs/04-\351\205\215\347\275\256\350\277\201\347\247\273\345\215\263\344\273\243\347\240\201.md" "b/docs/04-\351\205\215\347\275\256\350\277\201\347\247\273\345\215\263\344\273\243\347\240\201.md" index f75670d..fed31c3 100644 --- "a/docs/04-\351\205\215\347\275\256\350\277\201\347\247\273\345\215\263\344\273\243\347\240\201.md" +++ "b/docs/04-\351\205\215\347\275\256\350\277\201\347\247\273\345\215\263\344\273\243\347\240\201.md" @@ -52,6 +52,30 @@ resetProToOpusDefault.ts --- +## 全景图:11 个迁移函数与 main.tsx 的关系 + +```mermaid +graph TB + Boot["main.tsx 启动"] --> Check{"migrationVersion === 11?"} + Check -- "是" --> Skip["整段跳过"] + Check -- "否" --> Sync["sync 流水线(顺序写死)"] + + Sync --> Field["搬家 3 个
autoUpdates / claudeMd / replBridge→remoteControl"] + Sync --> Model["模型字符串 6 个
fennec→opus, opus→opus1m,
sonnet1m→45→46, legacyOpus→current,
resetProToOpus"] + Sync --> Cond["条件分支 2 个
TRANSCRIPT_CLASSIFIER / 'external'==='ant'"] + + Field --> Bump["saveGlobalConfig: migrationVersion = 11"] + Model --> Bump + Cond --> Bump + + Boot --> Async["migrateChangelogFromConfig
(fire-and-forget,不参与版本号)"] + + style Sync fill:#e1f5fe + style Async fill:#fff3e0 +``` + +--- + ## 一、流水线:一个版本号守护的串行入口 所有 11 个迁移并不是各自找时机各自跑的,它们被集中在 `main.tsx` 的一个内部函数里,由一个叫 `migrationVersion` 的小数字守护。下面这段就是流水线的全貌(`main.tsx:323-352`): diff --git "a/docs/12-\346\226\207\344\273\266-\344\273\243\347\240\201-\344\270\216-LSP-\345\215\217\344\275\234\346\227\217.md" "b/docs/12-\346\226\207\344\273\266-\344\273\243\347\240\201-\344\270\216-LSP-\345\215\217\344\275\234\346\227\217.md" index 5e9cb12..e1624dd 100644 --- "a/docs/12-\346\226\207\344\273\266-\344\273\243\347\240\201-\344\270\216-LSP-\345\215\217\344\275\234\346\227\217.md" +++ "b/docs/12-\346\226\207\344\273\266-\344\273\243\347\240\201-\344\270\216-LSP-\345\215\217\344\275\234\346\227\217.md" @@ -12,6 +12,38 @@ --- +## 全景图:八工具 + LSP/REPL 服务的协作关系 + +```mermaid +graph TB + Model["AI Agent"] --> Read["FileReadTool"] + Model --> Write["FileWriteTool"] + Model --> Edit["FileEditTool"] + Model --> NB["NotebookEditTool"] + Model --> Glob["GlobTool"] + Model --> Grep["GrepTool"] + Model --> LSPTool["LSPTool"] + Model --> REPL["REPLTool"] + + Read --> ReadState[("readFileState
读过没有 / 读完之后改没改")] + Write --> ReadState + Edit --> ReadState + NB --> ReadState + + Glob --> RipGrep[("ripgrep")] + Grep --> RipGrep + + LSPTool --> LSPSvc[("services/lsp/
LSPClient / LSPDiagnosticRegistry /
LSPServerManager")] + + REPL --> Hidden["8 个 deferred 工具
(写文件/REPL/Glob/Grep/LSP)"] + + style ReadState fill:#fff3e0 + style RipGrep fill:#e1f5fe + style LSPSvc fill:#e1f5fe +``` + +--- + ## 一、读:FileReadTool 与"先读后写"这条暗规 `tools/FileReadTool/FileReadTool.ts:1-1183` 是这一族里最长的一个文件 —— 不是因为它要做什么复杂的事,而是因为读文件这件事在 Claude Code 里有太多形态:纯文本、二进制图片、PDF、Jupyter notebook、超长日志,每一种都要在同一个工具里走完"路径解析 → 编码探测 → 截断 → 回执"的全套流程。 diff --git "a/docs/13-\351\200\232\344\277\241\350\260\203\345\272\246\351\227\256\350\257\242\344\270\216\345\220\210\346\210\220\345\267\245\345\205\267.md" "b/docs/13-\351\200\232\344\277\241\350\260\203\345\272\246\351\227\256\350\257\242\344\270\216\345\220\210\346\210\220\345\267\245\345\205\267.md" index 7d9cf36..875a0da 100644 --- "a/docs/13-\351\200\232\344\277\241\350\260\203\345\272\246\351\227\256\350\257\242\344\270\216\345\220\210\346\210\220\345\267\245\345\205\267.md" +++ "b/docs/13-\351\200\232\344\277\241\350\260\203\345\272\246\351\227\256\350\257\242\344\270\216\345\220\210\346\210\220\345\267\245\345\205\267.md" @@ -21,6 +21,58 @@ --- +## 全景图:十把工具按"通道方向"分四组 + +```mermaid +graph LR + Agent["AI Agent"] + + subgraph G1["往外抓事实"] + WebFetch["WebFetchTool"] + WebSearch["WebSearchTool"] + end + + subgraph G2["挂到时间/外触发"] + Cron["ScheduleCronTool"] + RTrig["RemoteTriggerTool"] + Sleep["SleepTool"] + end + + subgraph G3["与人对话(低带宽)"] + Ask["AskUserQuestionTool"] + Brief["BriefTool"] + Config["ConfigTool"] + end + + subgraph G4["与同伴 Agent(高频)"] + Send["SendMessageTool"] + end + + subgraph G5["结构化退出"] + Synth["SyntheticOutputTool"] + end + + Agent --> G1 + Agent --> G2 + Agent --> G3 + Agent --> G4 + Agent --> G5 + + G1 -.-> Net[("外网 / API")] + G2 -.-> Sched[("scheduler / 消息队列")] + G3 -.-> User[("用户 UI")] + G4 -.-> Peer[("teammate 收件箱")] + G5 -.-> Schema[("Ajv schema 校验")] + + style G1 fill:#e1f5fe + style G2 fill:#fff3e0 + style G3 fill:#f3e5f5 + style G4 fill:#e8f5e9 + style G5 fill:#fce4ec +``` + +--- + ## 一、往外抓一段事实:WebFetch 与 WebSearch ### 1.1 WebFetch:URL 背后那条比想象中长的链 diff --git "a/docs/17-Coordinator-Cron-\344\270\216\345\256\232\346\227\266\350\260\203\345\272\246.md" "b/docs/17-Coordinator-Cron-\344\270\216\345\256\232\346\227\266\350\260\203\345\272\246.md" index fdef6a4..8abde3c 100644 --- "a/docs/17-Coordinator-Cron-\344\270\216\345\256\232\346\227\266\350\260\203\345\272\246.md" +++ "b/docs/17-Coordinator-Cron-\344\270\216\345\256\232\346\227\266\350\260\203\345\272\246.md" @@ -11,6 +11,37 @@ --- +## 全景图:Coordinator 与 Cron 两条线汇到同一入口 + +```mermaid +graph TB + subgraph Spatial["空间维度:Coordinator"] + Main["主会话
(项目经理 system prompt)"] + Main --> Worker1["Worker Agent 1"] + Main --> Worker2["Worker Agent 2"] + Worker1 --> WorkerDone["Worker 完成"] + Worker2 --> WorkerDone + end + + subgraph Temporal["时间维度:Cron"] + CronCreate["CronCreateTool"] + CronCreate --> Disk[("cron.json")] + Tick["scheduler: setInterval(1000)"] + Tick --> Disk + Disk --> Trigger["到点触发:读出 prompt"] + end + + WorkerDone --> Enqueue["enqueuePendingNotification()"] + Trigger --> Enqueue + Enqueue --> Queue[("messageQueueManager
'later' 优先级")] + Queue --> QueryLoop["主线程 query loop
继续转"] + + style Enqueue fill:#fff3e0 + style Queue fill:#e1f5fe +``` + +--- + ## 一、为什么放在同一章? > 本节先解释合章动机;具体源码位置见 §二(`coordinator/coordinatorMode.ts`)与 §三、§四(`tools/ScheduleCronTool/`、`utils/cronScheduler.ts`、`hooks/useScheduledTasks.ts`)。 diff --git "a/docs/21-Skill-Plugin-OutputStyle\344\270\211\346\211\251\345\261\225\347\202\271.md" "b/docs/21-Skill-Plugin-OutputStyle\344\270\211\346\211\251\345\261\225\347\202\271.md" index 47e8720..2845c18 100644 --- "a/docs/21-Skill-Plugin-OutputStyle\344\270\211\346\211\251\345\261\225\347\202\271.md" +++ "b/docs/21-Skill-Plugin-OutputStyle\344\270\211\346\211\251\345\261\225\347\202\271.md" @@ -25,6 +25,35 @@ Hook 脚本 → Skill 文件 → Agent 定义 → Plugin 包 > **章内导读**:§一 自定义 Skill → §二 自定义 Agent → §三 Plugin 系统架构 → §四 Hook 脚本 → §五 MCP Skill 桥接 → §六 Output Style 作为第三条扩展路径 → §七 实战示例 → §八 可迁移模式。本章按「扩展点从轻量到重量」组织:Skill → Agent → Plugin → Hook → MCP → Output Style。读完前六节后 §七 是一份可直接照抄的 walkthrough。 +## 全景图:四档扩展机制 + Output Style 第三条路径 + +```mermaid +graph TB + User["扩展开发者"] + + User --> Hook["Hook 脚本
(最轻:shell 命令钩子)"] + User --> Skill["Skill
(一份 markdown:prompt + 行为约束)"] + User --> Agent["Agent
(独立 AI 角色:prompt + 工具集 + 模型)"] + User --> Plugin["Plugin
(最重:目录包,可携带上述三种 + MCP + Output Style)"] + + User --> OS["Output Style
(体验层第三条路径:
system prompt 末尾的可替换 tail)"] + + Hook -.触发于.-> Events["27 个 HOOK_EVENTS"] + Skill -.被模型自主调用.-> SkillTool["SkillTool 桥接"] + Agent -.spawn 时加载.-> AgentRuntime["runAgent()"] + Plugin -.聚合.-> Hook + Plugin -.聚合.-> Skill + Plugin -.聚合.-> Agent + Plugin -.聚合.-> OS + + OS -.注入.-> Prompt[("constants/prompts.ts
getOutputStyleSection()")] + + style Plugin fill:#e1f5fe + style OS fill:#fff3e0 +``` + +--- + ## 一、自定义 Skill 编写 ### 1.1 目录结构与发现机制 diff --git "a/docs/24-Bridge-IPC-\344\270\216\350\277\234\347\250\213\344\274\232\350\257\235.md" "b/docs/24-Bridge-IPC-\344\270\216\350\277\234\347\250\213\344\274\232\350\257\235.md" index 7fb03b2..ff38792 100644 --- "a/docs/24-Bridge-IPC-\344\270\216\350\277\234\347\250\213\344\274\232\350\257\235.md" +++ "b/docs/24-Bridge-IPC-\344\270\216\350\277\234\347\250\213\344\274\232\350\257\235.md" @@ -19,6 +19,42 @@ --- +## 全景图:本地 CLI 与远端控制器之间的两层架构 + +```mermaid +graph TB + subgraph LocalMachine["你的笔记本"] + CLI["本地 Claude Code 进程"] + Bridge["bridge/
environment + session 两层"] + Sub["sessionRunner 子进程"] + CLI -.内部 IPC.-> Bridge + Bridge --> Sub + end + + subgraph Cloud["服务端"] + Reg["register / poll / work secret 端点"] + WS[("SessionsWebSocket")] + end + + subgraph Remote["手机 / Web / Desktop"] + Phone["浏览器 / App"] + end + + Bridge -- "握手 + 心跳" --> Reg + Bridge <-- "对话帧 / 控制帧 / 取消" --> WS + Phone -- "登录会话" --> WS + Sub -- "stdout JSON 流" --> Bridge + + Sub -.activity 事件.-> Phone + Phone -.control_request 权限询问.-> Bridge + Bridge -.control_response.-> Phone + + style Bridge fill:#e1f5fe + style WS fill:#fff3e0 +``` + +--- + ## 一、为什么需要 Bridge? 在拆代码之前先把场景描清楚。手机上点一下「Claude」图标,看到的是一条对话窗,但模型不在手机上跑——本地的 `claude` 进程还得动磁盘、读你的项目、跑测试。中间需要一条管道,把对面的输入翻译成本地 REPL 的「下一条用户消息」,再把本地的回答、工具调用、权限请求一路反向送出去。 diff --git "a/docs/25-DirectConnect-\344\270\216\344\270\212\346\270\270\344\273\243\347\220\206.md" "b/docs/25-DirectConnect-\344\270\216\344\270\212\346\270\270\344\273\243\347\220\206.md" index 6bbff93..515c3cf 100644 --- "a/docs/25-DirectConnect-\344\270\216\344\270\212\346\270\270\344\273\243\347\220\206.md" +++ "b/docs/25-DirectConnect-\344\270\216\344\270\212\346\270\270\344\273\243\347\220\206.md" @@ -13,6 +13,38 @@ 3. **这两条线为什么放在一章?** — 它们在工程范式上是一对镜像 4. **能从中学到哪些可复用模式?** — 握手与长连分离、单 WS 双向 RPC、fail open、贴近 surface 的状态注入 +## 全景图:server/ 与 upstreamproxy/ 两段对称的"直连" + +```mermaid +graph TB + subgraph LocalCLI["本地 Claude Code 进程"] + REPL["REPL / query loop"] + end + + subgraph DirectConnect["server/ — DirectConnect"] + DCM["directConnectManager.ts"] + DCSession["createDirectConnectSession"] + DCM --> DCSession + end + + subgraph UpstreamProxy["upstreamproxy/ — CCR MITM"] + Relay["relay.ts"] + Proxy["upstreamproxy.ts"] + Relay --> Proxy + end + + REPL -- "本地 REPL → 远端 session
(接管者视角)" --> DCM + DCSession -. WS 双向 RPC .-> RemoteSrv[("远端服务端")] + + REPL -- "出网 API 流量" --> Relay + Proxy -. "注入企业头 / 走企业代理" .-> Upstream[("Anthropic API / 企业 LLM")] + + style DCM fill:#e1f5fe + style Relay fill:#fff3e0 +``` + +--- + ## 一、两条「直连」线,一个 CLI 打开 `claude-code-cli` 的目录,你会看到两个名字里都带「直」「连」「代理」气息的目录: diff --git "a/docs/27-\347\273\204\344\273\266\344\270\216\350\256\276\350\256\241\347\263\273\347\273\237.md" "b/docs/27-\347\273\204\344\273\266\344\270\216\350\256\276\350\256\241\347\263\273\347\273\237.md" index bf29780..1bfaf1c 100644 --- "a/docs/27-\347\273\204\344\273\266\344\270\216\350\256\276\350\256\241\347\263\273\347\273\237.md" +++ "b/docs/27-\347\273\204\344\273\266\344\270\216\350\256\276\350\256\241\347\263\273\347\273\237.md" @@ -17,6 +17,28 @@ Claude Code 面对的正是这样的复杂度。它的 UI 不是静态的信息 --- +## 全景图:从 token 到工具 UI 的五层 + +```mermaid +graph TB + Token["主题 token
80+ 语义化颜色"] --> Provider["ThemeProvider
(React Context)"] + Provider --> Base["基础组件
ThemedText / ThemedBox"] + Base --> Layout["布局组件
Pane / Divider / Dialog"] + Base --> Inter["交互组件
Tabs / ProgressBar / FuzzyPicker"] + Layout --> ToolUI["工具 UI 协议
Tool 接口的 10 个 Render/查询方法"] + Inter --> ToolUI + + ToolUI --> Bash[("BashTool UI")] + ToolUI --> Edit[("FileEdit UI")] + ToolUI --> Other[("…其他工具 UI")] + + style Token fill:#fff3e0 + style Provider fill:#e1f5fe + style ToolUI fill:#f3e5f5 +``` + +--- + ## 一、主题系统:80+ 语义化颜色 token ### 1.1 Theme 类型:颜色的语义化契约 diff --git "a/docs/28-Keybindings-Vim\344\270\216Voice\350\276\223\345\205\245.md" "b/docs/28-Keybindings-Vim\344\270\216Voice\350\276\223\345\205\245.md" index 1e98369..db544ca 100644 --- "a/docs/28-Keybindings-Vim\344\270\216Voice\350\276\223\345\205\245.md" +++ "b/docs/28-Keybindings-Vim\344\270\216Voice\350\276\223\345\205\245.md" @@ -26,6 +26,34 @@ voice/, services/voice*, hooks/useVoice* # 按住一个键 → 一段 PCM 流 --- +## 全景图:同一条按键流被三套子系统分别解释 + +```mermaid +graph TB + Ink["Ink useInput
(按下了哪个键)"] + + Ink --> KB["keybindings/
按键 → 动作 ID"] + Ink --> Vim["vim/
按键序列 → 一次 vim 命令"] + Ink --> Voice["voice/ + services/voice*
按住一个键 → PCM 流"] + + KB --> Action["dispatch 到 action handler"] + Vim --> Buffer["编辑 buffer / 运动 / 操作符"] + Voice --> Native["原生录音模块"] + Native --> WS[("Deepgram WebSocket")] + WS --> Text["转写文本回灌到 PromptInput"] + + Action -.高优先级.-> Result["上层意图"] + Buffer -.中优先级.-> Result + Text -.低优先级.-> Result + + style Ink fill:#fff3e0 + style KB fill:#e1f5fe + style Vim fill:#f3e5f5 + style Voice fill:#e8f5e9 +``` + +--- + ## 一、Keybindings:一套带优先级的按键解析器 ### 1.1 为什么这件事值得做成一个子系统? diff --git "a/docs/29-Buddy\345\256\240\347\211\251.md" "b/docs/29-Buddy\345\256\240\347\211\251.md" index 8e47cff..9fcbd32 100644 --- "a/docs/29-Buddy\345\256\240\347\211\251.md" +++ "b/docs/29-Buddy\345\256\240\347\211\251.md" @@ -27,6 +27,36 @@ Claude Code 的答案可以一句话概括:**把"骨"和"魂"切开存,把 --- +## 全景图:六个文件如何挂进五个子系统 + +```mermaid +graph TB + subgraph BuddyFiles["buddy/ 6 个源码文件"] + Types["types.ts
物种与稀有度词典"] + Comp["companion.ts
骨与魂的拆分"] + Sprites["sprites.ts
18 个像素画"] + Sprite["CompanionSprite.tsx
帧动画与气泡"] + Prompt["prompt.ts
第三人称介绍"] + Notif["useBuddyNotification.tsx
彩虹色入口提示"] + end + + Comp -.companion 字段.-> Config[("配置:companion / companionMuted")] + Sprite -.嵌入.-> PromptInput[("PromptInput UI")] + Prompt -.注入.-> SP[("System Prompt")] + Comp -.attach.-> Att[("Attachment 消息流")] + Notif -.feature('BUDDY').-> Cmd[("commands/buddy/")] + + Types -.被引用.-> Comp + Types -.被引用.-> Sprites + Sprites -.被引用.-> Sprite + + style BuddyFiles fill:#fff3e0 + style Config fill:#e1f5fe + style SP fill:#f3e5f5 +``` + +--- + ## 一、骨与魂:一半算出来,一半存下来 打开 `buddy/types.ts:100-124`,能看到 `Companion` 这个类型被一刀切成两半: diff --git "a/docs/30-Doctor\345\261\217\344\270\216OutputStyle\344\275\223\351\252\214.md" "b/docs/30-Doctor\345\261\217\344\270\216OutputStyle\344\275\223\351\252\214.md" index 54aa9ed..9d42e03 100644 --- "a/docs/30-Doctor\345\261\217\344\270\216OutputStyle\344\275\223\351\252\214.md" +++ "b/docs/30-Doctor\345\261\217\344\270\216OutputStyle\344\275\223\351\252\214.md" @@ -12,6 +12,33 @@ --- +## 全景图:screens/ 三屏 + outputStyles 的体验入口 + +```mermaid +graph TB + User["用户"] + + User --> Doctor["screens/Doctor.tsx
自检仪表盘(574 行)"] + User --> Resume["screens/ResumeConversation.tsx
会话拣选器"] + User --> OS["/output-style 命令
+ .claude/output-styles/*.md"] + + Doctor --> Diag["getDoctorDiagnostic()"] + Diag --> Items["安装 / 冲突 / 自动更新 /
MCP token / 权限规则 / hooks ..."] + + Resume --> Local["本地 session 文件"] + Resume --> Remote["远端 session API"] + + OS --> Loader["outputStyles/loadOutputStylesDir.ts"] + Loader --> Const["constants/outputStyles.ts
getOutputStyleConfig()"] + Const --> SP[("system prompt 末尾
getOutputStyleSection()")] + + style Doctor fill:#e1f5fe + style Resume fill:#fff3e0 + style OS fill:#f3e5f5 +``` + +--- + ## 一、Doctor 屏:把诊断结果摆在一屏上 ### 1.1 命令入口薄到只剩门面