diff --git "a/docs/18-MCP\345\215\217\350\256\256\345\256\236\347\216\260.md" "b/docs/18-MCP\345\215\217\350\256\256\345\256\236\347\216\260.md" index 3bddc7c..17c6c75 100644 --- "a/docs/18-MCP\345\215\217\350\256\256\345\256\236\347\216\260.md" +++ "b/docs/18-MCP\345\215\217\350\256\256\345\256\236\347\216\260.md" @@ -278,7 +278,7 @@ if (doesEnterpriseMcpConfigExist()) { 需要注意的是,enterprise 独占模式并不完全排斥所有外部配置。SDK 类型的服务器(`type: 'sdk'`)在策略过滤时被豁免(`filterMcpServersByPolicy` 中 `c.type === 'sdk'` 直接放行),因为 SDK 服务器是进程内传输的占位符,CLI 不会为它们 spawn 进程或打开网络连接,URL/command 形式的 allowlist 对它们也无意义。 -### 2.2 Project 配置的向上遍历 +### 2.4 Project 配置的向上遍历 Project 级别的 `.mcp.json` 有一个特殊行为:**从 CWD 开始,向上遍历到文件系统根目录**,越靠近 CWD 的配置优先级越高: @@ -306,7 +306,7 @@ case 'project': { 这意味着 monorepo 的根目录可以定义通用的 MCP 服务器,子项目目录可以覆盖或添加自己的。 -### 2.3 插件去重:基于签名的内容比对 +### 2.5 插件去重:基于签名的内容比对 当多个来源定义了指向同一个底层服务的 MCP 服务器时(例如,用户手动配置了 Slack MCP,插件也提供了 Slack MCP),需要智能去重。 @@ -332,7 +332,7 @@ export function getMcpServerSignature(config: McpServerConfig): string | null { - **插件内先到先得**:多个插件提供相同服务器时,先加载的赢 - **手动配置 > claude.ai 连接器**:用户手动配置表达了更强的意图 -### 2.4 环境变量展开 +### 2.6 环境变量展开 MCP 配置支持 `${VAR}` 和 `${VAR:-default}` 语法的环境变量展开: @@ -356,7 +356,7 @@ export function expandEnvVarsInString(value: string): { 这个展开会递归应用到 stdio 服务器的 `command`、`args`、`env`,以及远程服务器的 `url`、`headers` 上。 -### 2.5 企业策略过滤:Allowlist 与 Denylist +### 2.7 企业策略过滤:Allowlist 与 Denylist 企业管理员可以通过 `allowedMcpServers` 和 `deniedMcpServers` 控制哪些 MCP 服务器可以使用。策略支持三种匹配方式: @@ -548,7 +548,7 @@ const timeoutPromise = new Promise((_, reject) => { const timeoutId = setTimeout(() => { if (inProcessServer) inProcessServer.close().catch(() => {}) transport.close().catch(() => {}) - reject(new TelemetrySafeError_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS( + reject(new TelemetrySafeError( `MCP server "${name}" connection timed out after ${getConnectionTimeoutMs()}ms`, 'MCP connection timeout', )) diff --git "a/docs/19-\346\235\203\351\231\220\347\263\273\347\273\237\344\270\216\350\277\234\347\250\213\346\235\203\351\231\220\345\233\236\347\201\214.md" "b/docs/19-\346\235\203\351\231\220\347\263\273\347\273\237\344\270\216\350\277\234\347\250\213\346\235\203\351\231\220\345\233\236\347\201\214.md" index 8665981..37558d6 100644 --- "a/docs/19-\346\235\203\351\231\220\347\263\273\347\273\237\344\270\216\350\277\234\347\250\213\346\235\203\351\231\220\345\233\236\347\201\214.md" +++ "b/docs/19-\346\235\203\351\231\220\347\263\273\347\273\237\344\270\216\350\277\234\347\250\213\346\235\203\351\231\220\345\233\236\347\201\214.md" @@ -784,7 +784,7 @@ Auto 模式不是对每个操作都调用昂贵的 Classifier API,而是先通 前面八节都默认"问"和"答"发生在同一台电脑上:模型想跑工具,本地 CLI 弹个对话框,用户在终端按 yes/no。新版多了一个更有意思的场景——你在咖啡店掏出手机,claude.ai 推来一条通知:"是否允许在你笔记本上执行 `rm node_modules`?"你点一下允许,几秒之后家里那台电脑上的工具就真的跑起来了。 -把这条链路想象成一个对讲机:本地 CLI 想问问题,远程那头的人有权回答。能架起这部对讲机,靠的是新增的三个落点,本节就来逐个拆开。 +把这条链路想象成一条专用的内部通道:本地 CLI 把权限问题发出去,远程那头的人有权回答。能架起这条通道,靠的是新增的三个落点,本节就来逐个拆开。 ### 9.1 三个新落点:接口层、本地协调层、回灌入口 @@ -804,9 +804,9 @@ flowchart LR 三个落点对应的源码很短,可以这样理解它们的角色: -第一处是**接口层**,落在 `bridge/bridgePermissionCallbacks.ts:1-43`。这个文件只有 43 行,给"对讲机"画了一份最小的协议:怎么把问题发出去、怎么收回答、怎么取消提问。值得一提的是它没有用裸 `as` 把消息强转成业务类型,而是写了一个类型守卫专门校验回包合不合法——跨进程消息一旦相信"对面会发对的",运行时就会无声无息出 bug,这份小心是必要的。 +第一处是**接口层**,落在 `bridge/bridgePermissionCallbacks.ts:1-43`。这个文件只有 43 行,给这条通道画了一份最小的协议:怎么把问题发出去、怎么收回答、怎么取消提问。值得一提的是它没有用裸 `as` 把消息强转成业务类型,而是写了一个类型守卫专门校验回包合不合法——跨进程消息一旦相信"对面会发对的",运行时就会无声无息出 bug,这份小心是必要的。 -第二处是**本地协调层**,落在 `hooks/useReplBridge.tsx:369-585`。它负责"在本地这边坐镇":每个发出去的问题分一个 `requestId`,远端回包了再按 id 找回原来等着的那段代码。它做完之后会把对讲机的引用挂到全局状态里,权限内核需要时直接取。 +第二处是**本地协调层**,落在 `hooks/useReplBridge.tsx:369-585`。它负责"在本地这边坐镇":每个发出去的问题分一个 `requestId`,远端回包了再按 id 找回原来等着的那段代码。它做完之后会把通道的引用挂到全局状态里,权限内核需要时直接取。 第三处是**反向回灌入口**,落在 `remote/remotePermissionBridge.ts:1-78`。前两个文件解决的是"本地工具想问远端用户";这个文件解决的是反过来的麻烦:远端模型在云端调了一个工具,但本地 CLI 必须帮它弹出确认对话框——而本地此刻既不知道这工具长什么样,也没有完整的对话上下文。它的解法是"现场捏一个最小可信对象"塞给本地确认队列,§9.5 会展开。 @@ -874,7 +874,7 @@ sequenceDiagram Local--xKernel: 用户后按的回答被丢弃 ``` -注意图里两条"撤销"线很重要。任何一条赛道赢下之后,必须主动通知**所有还在等待的赛道**:"别问了,已经有答案了。" 否则远端用户那边会留下一个孤儿提示框,本地工具明明已经跑完,他还在那盯着是否允许——这是最让用户困惑的"幽灵提示"问题。`interactiveHandler.ts:92-298` 把这条规矩落到代码里:六条赢家路径(本地三个回调、async hook、async classifier、recheckPermission),每一条都显式做了一次撤销。 +注意图里两条"撤销"线很重要。任何一条赛道赢下之后,必须主动通知**所有还在等待的赛道**:"别问了,已经有答案了。" 否则远端用户那边会留下一个孤儿提示框,本地工具明明已经跑完,他还在那盯着是否允许——这是最让用户困惑的"幽灵提示"问题。`interactiveHandler.ts:92-298` 把这条规矩落到代码里:上一节列出的五条赛道再加上后面 §9.4 要讲的 `recheckPermission`,总共六个可能"赢"的入口,每一个都显式做了一次撤销。 ### 9.4 一个反直觉的小动作:recheckPermission 也要 claim 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 9d73ec1..812fda6 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" @@ -113,7 +113,7 @@ Review the code changes on the given branch... | 字段 | 类型 | 默认值 | 源码位置 | |------|------|--------|---------| -| `name` | string | undefined(显示名,不影响 Skill 标识) | `loadSkillsDir.ts:238-240` — `displayName` | +| `name` | string | undefined(显示名 `displayName`,不影响 Skill 标识;Skill 标识始终是目录名 `entry.name`) | `loadSkillsDir.ts:238-240` — `displayName` | | `description` | string | 从 Markdown 正文首行提取 | `loadSkillsDir.ts:208-214` | | `allowed-tools` | string/string[] | `[]` | `loadSkillsDir.ts:242-245` | | `argument-hint` | string | undefined | `loadSkillsDir.ts:246-249` | @@ -520,6 +520,8 @@ Plugin 中的命令自动带有 Plugin 名称前缀。命名逻辑在 `getComman // 例:my-plugin:sub:deploy ``` +嵌套目录形式只有在 plugin 把命令文件按 namespace 分层组织时才会出现 —— 例如 `commands/sub/deploy.md` 会被注册成 `my-plugin:sub:deploy`,相对路径里的 `/` 会按层级转换为 `:`。普通的扁平 `commands/build.md` 不会带 namespace。 + ### 3.4 Plugin 变量替换 Plugin 命令支持特有的变量替换(`utils/plugins/loadPluginCommands.ts:340-377`): @@ -787,7 +789,7 @@ MCP Skill 有一个关键安全限制:**不执行 Shell 命令嵌入**(`load --- -## Output Style:体验层的第三条扩展路径 +## 六、Output Style:体验层的第三条扩展路径 前面五节一直在围着 Skill / Agent / Plugin / Hook 这条"行为面"主线转 —— 谁注入 prompt、谁调工具、谁拦截事件。但 Claude Code 还藏着另一条扩展路径,叫 Output Style。它跟 Skill 看起来都是 `.md` 文件、都吃 frontmatter,第一眼很容易混为一谈;实际上两者切入对话的位置完全不同 —— Skill 是把内容注入到 user/assistant 这一侧,Output Style 则是在 system prompt 末端追加一段 `# Output Style: ...` 风格指令(`constants/prompts.ts:151-157`),并可选地省略 `getSimpleDoingTasksSection()` 那段默认任务清单(`constants/prompts.ts:564-567`),intro / system / actions / tools / tone / efficiency 以及 memory / env / language / MCP 等其余 system sections 一概照常保留。前者在"一次回合里要做什么"上加料,后者只是在原有 system prompt 之上追加风格指令、调整输出腔调,并不会顶替或换掉 system 那一侧。 @@ -866,7 +868,7 @@ Plugin 则是这三者的"打包发行单元"。一个完整的 plugin 可以同 --- -## 六、实战示例 +## 七、实战示例 ### 示例 1:代码审查 Skill @@ -969,9 +971,78 @@ Important: 1. 每次文件被编辑/写入后,从 stdin JSON 中提取 `file_path` 字段,自动运行 ESLint 修复 2. 当 Agent 即将停止时,异步运行测试套件;如果测试失败(退出码 2),唤醒模型继续修复(注意 `Stop` 事件的退出码 2 含义是"继续对话") +### 示例 4:完整 Plugin 包 + +把前面 Skill / Agent / Hook / Output Style 一起打包成一个 plugin,就是开发者交付给团队的最小完整单元: + +``` +team-toolkit/ +├── plugin.json +├── commands/ +│ └── stand-up.md +├── skills/ +│ └── review-pr/ +│ └── SKILL.md +├── agents/ +│ └── test-fixer.md +├── hooks/ +│ └── hooks.json +└── output-styles/ + └── concise-zh.md +``` + +`plugin.json` 把所有相对路径写死,并通过 `userConfig` 暴露一项最小用户配置: + +```json +{ + "name": "team-toolkit", + "version": "1.0.0", + "description": "团队内部共用的 review / test / output 套件", + "commands": "./commands", + "skills": "./skills", + "agents": "./agents", + "hooks": "./hooks/hooks.json", + "outputStyles": "./output-styles", + "mcpServers": { + "internal-search": { + "type": "stdio", + "command": "node", + "args": ["./mcp-server/index.js"] + } + }, + "userConfig": { + "githubToken": { + "type": "string", + "title": "GitHub Token", + "description": "用于 review-pr Skill 调用 GitHub API", + "required": true, + "sensitive": true + } + } +} +``` + +`output-styles/concise-zh.md` 给 plugin 用户默认套上简洁风格(注意 `force-for-plugin` 只对 plugin 来源生效,会覆盖用户在 `/output-style` 里的偏好): + +```markdown +--- +name: "concise-zh" +description: "简洁中文回答,没有客套" +force-for-plugin: true +--- + +用简短、直接的中文回答。先给结论再展开理由,不要客气话。 +``` + +> Plugin 来源的 Output Style 由 `utils/plugins/loadPluginOutputStyles.ts:36-85` 的 `loadOutputStyleFromFile()` 加载,frontmatter 只解析 `name`、`description`、`force-for-plugin` 三个字段(外加正文 prompt),其它键会被忽略 —— 比如 `keep-coding-instructions` 在 plugin 路径上不会生效,要保留 system-reminder 行为请直接在正文里写明。 +> +> 同时第 55 行会把样式名命名空间化为 `${pluginName}:${baseStyleName}`,所以下面 `concise-zh` 安装后真正注册的名字是 `team-toolkit:concise-zh`。 + +安装后,Plugin 会把 `commands/stand-up.md` 暴露为 `/team-toolkit:stand-up`、把 `skills/review-pr/` 注册为 `/team-toolkit:review-pr`、把 `agents/test-fixer.md` 加入可调用的 Agent 池、把 `hooks/hooks.json` 中的 hooks 接进事件总线,并自动把 `team-toolkit:concise-zh` 作为输出腔调启用 —— 这就是前面四档扩展点合在一起、走完整 Plugin 发行流程的样子。 + --- -## 七、可迁移的设计模式 +## 八、可迁移的设计模式 ### 模式 1:Markdown-as-Config + Frontmatter 约定 @@ -1000,8 +1071,6 @@ Claude Code 的扩展系统(Agent、Skill、Plugin)都遵循同一模式: --- ---- - ## 下一章预告 [第 22 章:Feature Flag 与编译期优化 — 同一份代码构建两个产品](./22-FeatureFlag与编译期优化.md) diff --git "a/docs/22-FeatureFlag\344\270\216\347\274\226\350\257\221\346\234\237\344\274\230\345\214\226.md" "b/docs/22-FeatureFlag\344\270\216\347\274\226\350\257\221\346\234\237\344\274\230\345\214\226.md" index fce121d..d539fec 100644 --- "a/docs/22-FeatureFlag\344\270\216\347\274\226\350\257\221\346\234\237\344\274\230\345\214\226.md" +++ "b/docs/22-FeatureFlag\344\270\216\347\274\226\350\257\221\346\234\237\344\274\230\345\214\226.md" @@ -116,6 +116,8 @@ grep -rhoE "feature\(['\"]([A-Z_0-9]+)['\"]\)" --include="*.ts" --include="*.tsx | `HISTORY_SNIP` | 16 | 历史片段剪辑 | | `CHICAGO_MCP` | 16 | Computer Use MCP | +这些 flag 中,`KAIROS`(希腊语「恰当时机」)出现 154 次,几乎是第二名的 1.5 倍 —— 它对应的是 Claude Code 的「助手」(Kairos Assistant)模式,本质上是一个把 Claude Code 内核当成"主动型聊天助手"使用的内部大型实验功能:相比默认的请求-响应循环,它会在更多触发点主动开口(如长任务结束、idle 提醒、定时简报),并依赖 `KAIROS_BRIEF`、`KAIROS_CHANNELS`、`KAIROS_GITHUB_WEBHOOKS` 等一系列同前缀的子 flag 协同。所以严格来说 `KAIROS` 不是"助手模式"这么宽泛,而是"主动型助手"的总开关。 + #### 1.3.1 剩余 74 个 flag 的分类速查表 按主题域分组(**逐个列出**,不再让读者自行 grep)。"次"指 `feature('X')` 在源码中的出现次数。下方各组合计 74 个唯一 flag(`BUDDY` 已在 Top 16 中、未在此重复计数)。 @@ -327,7 +329,7 @@ const contextCollapse = feature('CONTEXT_COLLAPSE') ### 1.5 编译期 + 运行时双重门控:Ablation Baseline -一个特别精巧的用法是 `cli.tsx` 中的 Ablation Baseline(消融实验基线)。它展示了编译期 `feature()` 和运行时环境变量**组合使用**的模式: +一个特别精巧的用法是 `cli.tsx` 中的 Ablation Baseline。**先解释一下名字** —— 在内部实验流水线里,开发者需要一个"什么花哨功能都关掉"的基线版本来对比一个新功能到底带来了多大效果,这种"去掉某条件作为对照组"的做法在机器学习里叫"消融实验"(Ablation Study),所以这里的"基线"指的就是"实验对照组"。对外部读者来说,它的意义在于:这是一个**编译期 `feature()` 和运行时环境变量组合使用**的范本,外部构建里这整段会被 DCE 删掉,所以你不会真的在你的 `claude` 里遇到它,但模式本身可以借鉴。 ```typescript // entrypoints/cli.tsx:16-26 @@ -728,7 +730,9 @@ function isScratchpadGateEnabled(): boolean { } ``` -这展示了三层如何嵌套:`feature()` 决定 coordinator 代码是否存在 → 环境变量决定 coordinator 是否激活 → GrowthBook 决定 coordinator 内部的 scratchpad 子功能是否启用。 +这里调用名带 `Statsig`、所在文件却叫 `growthbook.ts`,并不是命名错误,而是**迁移期的兼容层**:项目历史上用 Statsig 做实验平台,现在正在迁到 GrowthBook,`services/analytics/growthbook.ts:792-836` 的注释明确写道这个函数是"MIGRATION ONLY"——它先查 GrowthBook 缓存,未命中再回退到 `config.cachedStatsigGates`。也就是说,Statsig 是"上一代"实验平台、GrowthBook 是"这一代",两者通过这种命名前缀 + 旧缓存兜底的方式共存在同一个文件里,直到所有 gate 完成迁移。 + +这展示了三层如何嵌套:`feature()` 决定 coordinator 代码是否存在 → 环境变量决定 coordinator 是否激活 → GrowthBook(含 Statsig 兼容回退)决定 coordinator 内部的 scratchpad 子功能是否启用。 ```mermaid graph LR diff --git "a/docs/23-\345\256\242\346\210\267\347\253\257\344\274\240\350\276\223\344\270\216API\351\207\215\350\257\225.md" "b/docs/23-\345\256\242\346\210\267\347\253\257\344\274\240\350\276\223\344\270\216API\351\207\215\350\257\225.md" index aaa6ee1..33d3cc8 100644 --- "a/docs/23-\345\256\242\346\210\267\347\253\257\344\274\240\350\276\223\344\270\216API\351\207\215\350\257\225.md" +++ "b/docs/23-\345\256\242\346\210\267\347\253\257\344\274\240\350\276\223\344\270\216API\351\207\215\350\257\225.md" @@ -414,9 +414,11 @@ Claude Code 的 API 调用默认走流式(SSE),失败时自动降级到非 > 这部分内容已迁移到[第 25 章:DirectConnect 与上游代理](./25-DirectConnect-与上游代理.md)。本节作为占位锚点保留,便于从历史目录直接跳转,并提示读者:流式降级与本章的 `withRetry` 主循环、传输层长连接是三条互不相同的代码路径,请勿混淆。 -## 客户端传输层:WebSocket / SSE / Hybrid 三态 +--- + +## 五、客户端传输层:WebSocket / SSE / Hybrid 三态 -到此为止讨论的都是面向模型 API(`api.anthropic.com/messages`)的请求-响应往返。但 Claude Code 还有一条独立的传输路径 —— **客户端与会话服务的长连接**。Bridge、Teleport、远程容器都依赖它在浏览器、容器、CLI 之间双向同步事件。它的失败模式和模型 API 完全不同:断网不再是"重发一次 POST"那么简单,而是要面对**断线重连、事件重放、token 刷新、休眠唤醒、批量上传与背压、多副本切换**。 +到此为止讨论的都是面向模型 API(`api.anthropic.com/messages`)的请求-响应往返。但 Claude Code 还有一条独立的传输路径 —— **客户端与会话服务的长连接**。这条路径承载的就是 **CCR**(Claude Cloud Runtime,会话云端运行时)—— 它把每个 Claude Code 会话托管在云侧 worker 里,再通过本节的 WebSocket / SSE / Hybrid 把事件流双向同步到本地 CLI 和远端浏览器/手机端(Bridge / Teleport)。后文出现的 `CCR v2`、`worker`、`worker_epoch`、`Bridge`、`Teleport` 都是这套架构里的角色,下一章 C24 会专门拆解;本节只把它们当作"长连接对侧"的代号使用。它的失败模式和模型 API 完全不同:断网不再是"重发一次 POST"那么简单,而是要面对**断线重连、事件重放、token 刷新、休眠唤醒、批量上传与背压、多副本切换**。 `cli/transports/` 目录维护了三个共享 `Transport` 接口的实现,由一个轻量级 dispatcher 根据环境变量挑选: @@ -744,7 +746,7 @@ if (response.status === 409) { --- -## 五、连接错误分类与用户友好提示 +## 六、连接错误分类与用户友好提示 模型 API 重试层和传输层共享同一套底层错误工具,集中在 `services/api/errorUtils.ts`。 @@ -842,7 +844,7 @@ type NestedAPIError = { --- -## 六、资源泄漏防护 +## 七、资源泄漏防护 ### 7.1 流资源释放 @@ -889,7 +891,7 @@ return (input, init) => { --- -## 七、完整的 API 调用生命周期 +## 八、完整的 API 调用生命周期 把模型 API 重试链与传输层放在同一张图里,可以看到 Claude Code 完整的对外通信形态: @@ -948,7 +950,7 @@ sequenceDiagram --- -## 八、可迁移的设计模式 +## 九、可迁移的设计模式 ### 模式 1:AsyncGenerator 重试层 diff --git "a/docs/26-Ink\346\241\206\346\236\266\346\267\261\345\272\246\345\256\232\345\210\266.md" "b/docs/26-Ink\346\241\206\346\236\266\346\267\261\345\272\246\345\256\232\345\210\266.md" index ad2d136..a03bdd5 100644 --- "a/docs/26-Ink\346\241\206\346\236\266\346\267\261\345\272\246\345\256\232\345\210\266.md" +++ "b/docs/26-Ink\346\241\206\346\236\266\346\267\261\345\272\246\345\256\232\345\210\266.md" @@ -1,6 +1,10 @@ # 第 26 章:Ink 框架深度定制 — 在终端中运行 React +> 本章是《深入 Claude Code 源码》系列第 26 章,也是"终端 UI 与多模态输入"这一篇的开篇。前面几章我们走完了网络层的两条暗线(Bridge IPC、DirectConnect 与 Upstream Proxy),从这一章起视线转回 CLI 本机:用户敲下回车看到的那一帧画面,到底是怎么从一棵 React 树变成屏幕上 ANSI 字节的。 +> > 本章深入 Claude Code 的 forked Ink 框架(`ink/` 目录,96 个 `.ts` / `.tsx` 文件、19,842 行),揭示如何在终端中构建一个完整的 React 渲染引擎:从自定义 Reconciler、Yoga 布局、双缓冲渲染管线,到虚拟滚动、鼠标事件、文本选择等深度定制。这一版还要把视线拉到与 `ink/` 配套的 `native-ts/` 目录——团队把原本走 WASM / NAPI 的三段原生模块(`yoga-layout` / `color-diff` / `file-index`)重新写成了纯 TypeScript 实现,Ink 的布局引擎就是其中第一段。 +> +> **术语速查**:本章会反复出现几个 ANSI / 终端协议缩写——**DECSTBM**(DEC Set Top/Bottom Margin,CSI `r`,定义滚动区域上下边界,让终端用硬件指令滚动而非重写整屏)、**DEC 2026**("Synchronized Output",CSI `?2026h/l`,把一帧多次写入包成 BSU/ESU 块让终端原子刷新避免半帧闪烁)、**OSC 8**(OSC `8;params;URI ST` 序列,把一段文本标成可点击超链接,iTerm2/WezTerm 支持)、**DA1**(Primary Device Attributes,CSI `c`,VT100 起所有终端都会回的"自报家门"查询,本章用它做能力探测哨兵)、**BiDi**(Bidirectional Text,阿拉伯文/希伯来文等 RTL 字符在 LTR 文本里的视觉重排算法)。后文出现时不再展开。 ## 为什么要 Fork Ink? @@ -751,8 +755,6 @@ TerminalQuerier 的 DECRQM 查询机制用于探测其他终端能力(如 Kitt --- ---- - ## 下一章预告 [第 27 章:组件与设计系统 — 终端 UI 的组件化实践](./27-组件与设计系统.md) diff --git a/scripts/heading-rewrite-allowlist.txt b/scripts/heading-rewrite-allowlist.txt index fe1ebe7..b188414 100644 --- a/scripts/heading-rewrite-allowlist.txt +++ b/scripts/heading-rewrite-allowlist.txt @@ -23,3 +23,15 @@ docs/09-Thinking-Effort-与-Advisor.md :: ## 番外:PromptSuggestion — 预 // 两个 v1 标题被改写,均为事实/编号勘误。 docs/14-Agent系统与SubAgent调用.md :: ## 七.5、AgentSummary:后台进度摘要 docs/14-Agent系统与SubAgent调用.md :: ## 八、可迁移的设计模式 + +// C21(YAO-152):v1 Output Style 段缺节号;review 报告要求补 §六,后续节号顺延(实战示例 六→七、可迁移的设计模式 七→八)。 +docs/21-Skill-Plugin-OutputStyle三扩展点.md :: ## Output Style:体验层的第三条扩展路径 +docs/21-Skill-Plugin-OutputStyle三扩展点.md :: ## 六、实战示例 +docs/21-Skill-Plugin-OutputStyle三扩展点.md :: ## 七、可迁移的设计模式 + +// C23(YAO-152):v1 客户端传输层段缺节号;review 报告要求补 §五,后续节号顺延(连接错误 五→六、资源泄漏 六→七、生命周期 七→八、设计模式 八→九)。 +docs/23-客户端传输与API重试.md :: ## 客户端传输层:WebSocket / SSE / Hybrid 三态 +docs/23-客户端传输与API重试.md :: ## 五、连接错误分类与用户友好提示 +docs/23-客户端传输与API重试.md :: ## 六、资源泄漏防护 +docs/23-客户端传输与API重试.md :: ## 七、完整的 API 调用生命周期 +docs/23-客户端传输与API重试.md :: ## 八、可迁移的设计模式