1. 应用架构概览
1.1 定位与入口
apps/kimi-code 是 kimi-code 系统的主应用入口。它不是引擎、不是 SDK、不是协议层------它是用户直接交互的那一层,负责把引擎的能力从终端暴露给开发者。当你输入 kimi 回车,整个系统的第一个 process.title 设置、第一个 import 调用都发生在这里。
技术栈非常明确:
- TypeScript:整个应用从头到尾是 TypeScript,编译为 ESNext 模块
- Commander.js:CLI 参数解析和子命令注册
- pi-tui:自研的终端 UI 框架,提供差分渲染和组件模型
- node-sdk :通过
@moonshot-ai/kimi-code-sdk消费引擎能力,不直接依赖引擎内部实现
1.2 与 node-sdk 的关系
CLI 应用没有直接导入引擎包(packages/engine)。它完全通过 SDK 的 createKimiHarness 获取 KimiHarness 实例,然后通过该实例创建 Session、订阅事件、调用工具。这种分层隔离确保了 CLI 不绑定引擎内部数据结构,SDK 变更时 CLI 只需调整对公开 API 的调用。
scss
┌─────────────────────────────────────────────────────────────┐
│ apps/kimi-code │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐ │
│ │ src/cli │ │ src/tui │ │ src/native│ │src/migration│ │
│ │ (命令) │ │ (UI) │ │ (SEA) │ │ (迁移) │ │
│ └─────┬────┘ └─────┬─────┘ └─────┬─────┘ └─────┬──────┘ │
│ │ │ │ │ │
│ └──────────────┴──────────────┴──────────────┘
│ │ │
│ src/main.ts (入口) │
└──────────────────────────────┬───────────────────────────────┘
│ KimiHarness
▼
┌─────────────────────────────────┐
│ @moonshot-ai/kimi-code-sdk │
│ (Harness → Session → EventSource) │
└─────────────────┬───────────────┘
│
▼
┌─────────────────────────────────┐
│ packages/engine │
│ (Agent 调度 / Tool 执行 / ...) │
└─────────────────────────────────┘
2. 源码结构全景
apps/kimi-code/src/ 下共 8 个一级目录 / 文件,每个都有清晰的职责边界:
| 路径 | 职责 |
|---|---|
src/main.ts |
进程入口:安装 crash handler、全局 proxy dispatcher、native module hook,解析 CLI 参数,委托到 UI 运行器(runPrompt / runShell) |
src/cli/ |
CLI 子命令定义与调度:commands.ts 用 Commander.js 注册所有选项和子命令;run-prompt.ts 处理 -p headless 模式;run-shell.ts 启动 TUI 交互模式;sub/ 下为 acp / web / login / provider / vis 等子命令 |
src/tui/ |
终端 UI 核心:KimiTUI 主类、组件树、事件控制器、主题系统、反向 RPC、slash 命令系统 |
src/constant/ |
应用级常量:进程名、UI 模式、终端 escape code、更新相关常量 |
src/feedback/ |
反馈收集系统:代码库扫描、打包、上传到远程服务 |
src/migration/ |
kimi-cli → kimi-code 迁移:检测、UI 屏幕、会话 badge、命令注册 |
src/native/ |
SEA 原生二进制打包:module hook 重定向、native asset manifest 解析与缓存、stale cache 清理 |
src/utils/ |
工具函数:clipboard、input history、路径解析、terminal restore、shell quote |
2.1 main.ts 的启动流程
main() 函数做的事极其克制------它只做进程级初始化,然后立即将控制权交出去:
设置进程标题
process.title = PROCESS_NAME('kimi-code'),方便在 ps 或任务管理器中识别
安装基础设施
依次调用 installCrashHandlers()、installGlobalProxyDispatcher()、installNativeModuleHook()。其中 installNativeModuleHook() 是关键------在 SEA 模式下,它拦截 Node.js 的 Module._load,将对 pi-tui 原生 .node 文件的绝对路径 require 重定向到磁盘缓存
创建 Commander 程序
createProgram() 注册所有选项(--session、--continue、--yolo、--auto、--model、--prompt、--output-format、--skills-dir、--agent、--agent-file、--plan)和子命令(acp、web、login、doctor、vis、export、provider、migrate、upgrade)
解析并路由
Commander 根据用户输入路由到对应的 handler:无参数 → onMain → handleMainCommand;kimi migrate → onMigrate;kimi upgrade → onUpgrade
3. pi-tui:差分渲染终端 UI 框架
3.1 设计理念
@moonshot-ai/pi-tui 是 kimi-code 自研的终端 UI 框架。它与常见的终端 UI 方案(如 Blessed、Ink)的核心区在于差分渲染:不是每次状态变化都整屏重绘,而是只更新发生变化的部分。
差分渲染的要点:
- 虚拟树比较:pi-tui 维护一棵组件虚拟树(类似 React 的 Virtual DOM),状态变更时计算旧树和新树的差异
- 最小化 ANSI 输出 :只对变化的行输出更新色码(如
ESC[y;xH定位 + 新内容),而不是\x1B[2J清屏后重绘 - 终端能力检测 :pi-tui 在启动时通过
getCapabilities()探测当前终端环境(支持的颜色数、是否支持光标定位、窗口大小等),据此调整渲染策略
为什么差分渲染很重要?在 kimi-code 的流式对话场景中,每秒可能有数十个文本增量事件到达。如果每次增量都整屏重绘,会导致屏幕闪烁和性能问题。差分渲染将更新控制在局部区域------比如对话区域追加一行文本,输入区域保持不变。
3.2 组件模型
pi-tui 的组件模型借鉴 React 的设计:
- 组件接口 :每个组件实现
Component接口,提供render(width)方法返回渲染行数组 - 可聚焦组件 :实现
Focusable接口的组件可以接收键盘事件(如编辑器输入框) - 组件树:KimiTUI 构建一棵完整的组件树,根节点下挂载 GutterContainer、Editor、Transcript 等子组件
- 生命周期钩子 :
mount()/unmount()提供组件挂载/卸载通知
核心组件导入来自:
Typescript
import {
type Component,
type Focusable,
getCapabilities,
Spacer,
} from '@moonshot-ai/pi-tui';
3.3 事件处理循环
pi-tui 维护一个事件循环来处理:
- 键盘输入 :raw mode 下逐键读取,分发到当前聚焦的
Focusable组件 - 窗口大小变化:SIGWINCH 信号触发所有组件的 re-layout,因为终端宽度改变影响文本换行
- 渲染请求 :应用层通过
state.ui.requestRender()标记脏区域,下次事件循环迭代执行差分渲染 - 定时器:支持基于事件循环的定时任务(如 spinner 动画帧)
3.4 终端能力检测与适配
pi-tui 通过 getCapabilities() 获取当前终端的能力信息,包括:
- 颜色支持:检测终端是否支持 256 色或真彩色(truecolor),以决定使用 ANSI 256 color codes 还是 RGB
- 光标控制:检测是否支持光标隐藏/显示(DECTCEM)和定位
- 终端尺寸:获取当前终端的行数和列数,用于组件布局
- OSC 52 Clipboard:检测终端是否支持 OSC 52 协议(用于终端内剪贴板操作)
KimiTUI 在启动时还会进行平台特殊处理:
- 检测 tmux 环境:在有 bug 的 tmux 版本中显示键盘警告
- 终端主题跟踪:监听终端配色方案变化(如 macOS Terminal 的 light/dark 切换),自动调整 TUI 主题
- 终端焦点跟踪:监听终端的 focus/blur 事件,用于通知系统(未聚焦时发出桌面通知)
4. 主要 UI 组件详解
KimiTUI 构建了一棵丰富的组件树。以下是各区块的核心组件:
4.1 Chrome 装饰层
src/tui/components/chrome/ 下的组件构成 TUI 的"外壳":
4.2 对话区域(Transcript)
对话历史被建模为一系列 TranscriptEntry,每个 entry 有明确的 kind 和 renderMode:
Typescript
export type TranscriptEntryKind =
| 'welcome' // 欢迎消息
| 'user' // 用户输入
| 'assistant' // 模型回复
| 'tool_call' // 工具调用
| 'thinking' // 思考过程
| 'status' // 状态消息
| 'skill_activation' // skill 激活
| 'plugin_command' // 插件命令
| 'cron' // 定时任务
| 'goal'; // 目标相关
src/tui/components/messages/ 下的每个组件对应一种或多种 entry 类型:
| 组件 | 职责 |
|---|---|
BannerComponent |
显示欢迎横幅和公告信息(banner),支持 always / once / cooldown 三种显示策略 |
WelcomeComponent |
首次启动的欢迎面板,显示产品名称和版本 |
GutterContainer |
左侧 gutter 布局容器,将状态栏和对话区域组织在统一的列布局中 |
MoonLoader |
Moon 风格 spinner 动画组件,支持多种样式(spinner、progress、dots 等) |
DeviceCodeBoxComponent |
OAuth 设备码登录的 UI:显示设备码和验证 URL |
TodoPanel |
Todo 任务列表面板 |
WorkingTips |
底部状态栏中的随机工作提示 |
| 组件 | 渲染的 entry kind |
|---|---|
UserMessageComponent |
用户输入的消息,支持 @file mention、image attachment |
AssistantMessageComponent |
模型回复内容,支持 Markdown 渲染(代码高亮、表格、列表) |
ThinkingComponent |
模型的思考过程(thinking / reasoning tokens),默认折叠显示 |
ToolCallComponent |
工具调用详情:工具名称、参数、结果,支持 truncate 状态 |
ShellRunComponent |
Shell 命令执行:显示命令 + 实时流式输出,支持 ctrl+b 后台化 |
StatusMessageComponent |
通用状态消息(系统通知、警告、提示) |
GoalPanel |
Goal 创建和完成卡片 |
PluginCommandComponent |
插件命令激活的卡片 |
SkillActivationComponent |
Skill 激活的卡片,显示 skill 名称和参数 |
CronMessageComponent |
定时任务创建/触发卡片 |
BackgroundAgentStatusComponent |
Background agent 启动/完成/失败的状态卡片 |
PlanBox |
Plan mode 下的计划展示框 |
McpStatusPanel |
MCP 服务器连接状态面板 |
UsagePanel |
Token 使用量面板(上下文用量、累计 token) |
4.3 输入编辑器
src/tui/components/editor/ 提供了终端内的丰富输入体验:
- **
custom-editor.ts**:支持行编辑、多行输入(Ctrl+J 换行)、历史回退(↑/↓ 从input-history加载)、buffer 编辑 - **
file-mention-provider.ts**:提供@文件提及的自动补全支持,扫描工作目录下的文件 - **
wrapping-select-list.ts**:通用的可循环选择列表组件,用于交互选择(session picker、model picker 等)
4.4 权限审批
这是 kimi-code TUI 中最复杂的交互组件之一:
- **
ApprovalPanelComponent**:显示工具调用的审批面板,列出待审批的操作,支持 approve / reject / approve-all 操作 - **
ApprovalPreviewViewer**:全屏 diff 预览器,可以预览文件变更内容 - **
QuestionDialogComponent**:当 agent 需要向用户提问时显示的对话框 - **
CompactionComponent**:对话上下文的 compaction 状态指示器
审批系统通过反向 RPC 与 SDK 通信------SDK 调用 showApprovalPanel 时 TUI 展示审批面板;用户做出选择后,TUI 通过 ApprovalController 将响应返回给 SDK。
4.5 状态栏
底部状态栏由 tui.toml 中的 statusLine 配置驱动。内置的 STATUS_LINE_ITEMS 可选:
Typescript
const STATUS_LINE_ITEMS = ['mode', 'goal', 'model', 'tasks', 'cwd', 'git', 'tips'];
- mode:当前权限模式(manual / yolo / auto)和 plan mode 指示
- goal:当前活跃 goal 的名称(如果有)
- model:当前使用的模型名称
- tasks:foreground task 计数
- cwd:当前工作目录
- git:当前 Git 分支名
- tips:轮换的工作提示文本
4.6 AppState:TUI 的"状态心脏"
整个 TUI 的状态由一个 AppState 对象统一管理:
Typescript
export interface AppState {
model: string;
workDir: string;
additionalDirs: readonly string[];
sessionId: string;
permissionMode: PermissionMode; // 'manual' | 'yolo' | 'auto'
planMode: boolean;
inputMode: 'prompt' | 'bash'; // 编辑器在普通输入还是 ! shell 模式
swarmMode: boolean; // 是否处于 swarm 多 agent 模式
thinkingEffort: ThinkingEffort; // 'off' | 'on' | 'high'
contextUsage: number; // 上下文使用百分比
contextTokens: number; // 当前 token 数
maxContextTokens: number; // 最大 token 数
isCompacting: boolean; // 是否正在 compact
isReplaying: boolean; // 是否正在重放历史
streamingPhase: 'idle' | 'waiting' | 'thinking' | 'composing' | 'shell';
streamingStartTime: number;
theme: ThemeName;
version: string;
availableModels: Record<string, ModelAlias>;
sessionTitle: string | null;
goal?: GoalSnapshot | null;
mcpServersSummary: string | null;
banner?: BannerState | null;
// ... 更多配置项
}
streamingPhase 字段是 UI 与 SDK 事件流之间的关键桥梁------它告诉 TUI 当前应该显示什么视图:
idle:等待用户输入waiting:已提交 prompt,等待模型响应thinking:模型正在生成 reasoning tokenscomposing:模型正在生成回复文本shell:正在执行 shell 命令
5. CLI 子命令体系
kimi-code 的 CLI 入口由 Commander.js 驱动。所有子命令在 src/cli/commands.ts 的 createProgram() 中注册:
5.1 默认模式:交互式 TUI
不带任何子命令执行 kimi,Commander 走到最后的 .argument('[args...]') handler,将解析后的选项打包为 CLIOptions,调用 onMain()。
handleMainCommand 判断 uiMode:
- 如果有
-p / --prompt→uiMode === 'print'→ 走runPrompt()(headless 单次模式) - 否则 → 走
runShell()→ 启动 KimiTUI 交互模式
5.2 单次请求模式:kimi -p "..."
Typescript
$ kimi -p "这段代码有什么问题?" --output-format stream-json
runPrompt() 创建 Harness 和 Session,发送 prompt,等待所有事件完成,将结果写入 stdout 后退出。它不创建任何 TUI 组件,完全是 headless 运行。
-p 模式的退出流程经过精心设计:
- 正常完成:事件循环自然排空后退出
- 异常超时:armed 一个 unref'd 超时计时器,防止被 stray ref 卡住
- flush 输出:确保 stdout 和 stderr 的所有 buffer 数据被写出
5.3 ACP 协议模式:kimi acp
Typescript
$ kimi acp # 启动 ACP JSON-RPC over stdio 服务器
$ kimi acp --login # 仅运行 device-code 登录流程
registerAcpCommand() 创建带 uiMode: 'acp' 的 Harness,调用 runAcpServer() 接管 stdin/stdout 作为 JSON-RPC 通道。(注意:ACP 相关的 runAcpServer 和 ACP_BUILTIN_SLASH_COMMANDS 来自 @moonshot-ai/acp-adapter 包,不是 CLI 应用自身实现的。)
ACP 模式还支持动态 slash command 列表:每次 session 创建后,serverb 通过 resolveSlashCommands 从 session.listSkills() 获取当前会话可用的 skill 命令,合并到内置命令列表中,通过 available_commands_update 通知告知客户端。
5.4 Web UI 启动:kimi web
Typescript
$ kimi web # 启动 kap-server 并在浏览器中打开 Web UI
$ kimi web --no-open # 启动但不在浏览器打开
registerWebCommand() 注册 web 子命令及其子命令 web rotate-token。同时注册一个已废弃的 server 子命令(用于旧版本清理)。
5.5 其他子命令
| 命令 | 功能 | 源码 |
|---|---|---|
kimi login |
启动设备码登录流程 | src/cli/sub/login.ts |
kimi provider |
管理模型提供者配置 | src/cli/sub/provider.ts |
kimi doctor |
系统诊断信息输出 | src/cli/sub/doctor.ts |
kimi export |
导出会话历史 | src/cli/sub/export.ts |
kimi vis |
启动可视化调试工具 | src/cli/sub/vis.ts |
kimi migrate |
从旧版 kimi-cli 迁移数据 | src/cli/commands.ts(调用 src/migration/) |
kimi upgrade |
升级到最新版本 | src/cli/sub/upgrade.ts |
6. 事件驱动架构
6.1 事件流的来源
CLI 应用自身不产生任何领域事件。所有事件来自 SDK 提供的 Session 对象。当用户发送一条 prompt,引擎开始运行时,SDK 将引擎内部事件转换为类型化的 Event 对象,通过 session.events 的 AsyncIterable 暴露给 TUI。
6.2 SessionEventHandler:事件到 UI 的桥梁
src/tui/controllers/session-event-handler.ts 是事件驱动架构的核心。它处理来自 SDK 的 20+ 种事件类型,并调用 TUI 状态更新和组件操作方法:
Typescript
// session-event-handler.ts 处理的事件类型(部分列表)
import type {
AgentStatusUpdatedEvent,
AssistantDeltaEvent, // 模型文本增量
BackgroundTaskStartedEvent, // 后台任务启动
CompactionStartedEvent, // 上下文压缩开始
CronFiredEvent, // 定时任务触发
ErrorEvent, // 错误事件
GoalUpdatedEvent, // 目标更新
HookResultEvent, // Hook 执行结果
SkillActivatedEvent, // Skill 激活
ThinkingDeltaEvent, // 思考过程增量
ToolCallDeltaEvent, // 工具调用参数增量
ToolCallStartedEvent, // 工具调用开始
ToolProgressEvent, // 工具执行进度
ToolResultEvent, // 工具调用结果
TurnStartedEvent, // Turn 开始
TurnEndedEvent, // Turn 结束
TurnStepStartedEvent, // Step 开始
TurnStepCompletedEvent, // Step 完成
WarningEvent, // 警告事件
} from '@moonshot-ai/kimi-code-sdk';
6.3 事件到 UI 更新的映射
每种事件有明确的 UI 映射关系:
| SDK 事件 | TUI 响应 |
|---|---|
TurnStarted |
设置 streamingPhase = 'waiting',显示 spinner |
ThinkingDelta |
创建或追加 ThinkingComponent,更新 thinking 内容 |
AssistantDelta |
创建或追加 AssistantMessageComponent,流式写入模型回复文本 |
ToolCallStarted |
创建 ToolCallComponent 卡片,显示工具名称和参数(可能是流式的) |
ToolCallDelta |
追加工具调用的流式参数内容 |
ToolResult |
更新对应 ToolCallComponent 的结果区域 |
TurnEnded |
重置 streamingPhase = 'idle',显示 token 使用统计 |
Error |
状态栏显示错误信息,创建 StatusMessageComponent |
SkillActivated |
创建 SkillActivationComponent 卡片 |
GoalUpdated |
更新状态栏 goal badge,插入 GoalMarker |
6.4 StreamingUIController:流式渲染协调器
src/tui/controllers/streaming-ui.ts 负责管理流式渲染的协调:
- Transcript 窗口管理:当会话历史过长时,应用 transcript windowing 策略(只保留最近 N 个 turn + hysteresis 机制)
- 自动滚动:流式输出时保持视图滚动到最新内容
- 渲染调度 :将高频率的增量事件(如每 token 一次
AssistantDelta)批处理,减少渲染调用次数
6.5 反向 RPC:当引擎需要 UI 响应
正常数据流是:SDK 事件 → TUI 更新。但 permission 审批和 question 提问需要反向通信------SDK 的 Session 需要等待用户输入才能继续执行。
src/tui/reverse-rpc/ 实现了这个机制:
- **
ApprovalController**:管理来自引擎的审批请求队列,每个请求包含一个response回调,TUI 展示审批面板后,用户的操作通过回调返回给引擎 - **
QuestionController**:同上,管理引擎的提问请求 - **
ModalCoordinator**:协调多个模态面板的显示优先级(审批 > 提问 > compaction 提示)
Typescript
// 反向 RPC handler 注册示例(简化自 kimi-tui.ts)
registerReverseRPCHandlers(approvalController, questionController, {
showApprovalPanel: (payload) => {
this.showApprovalPanel(payload); // TUI 显示审批面板
},
hideApprovalPanel: () => {
this.hideApprovalPanel(); // TUI 隐藏审批面板
},
showQuestionDialog: (payload) => {
this.showQuestionDialog(payload); // TUI 显示提问对话框
},
hideQuestionDialog: () => {
this.hideQuestionDialog(); // TUI 隐藏提问对话框
},
});
7. SEA 打包与分发
7.1 什么是 SEA?
Node.js Single Executable Application (SEA) 是 Node.js 20+ 提供的特性,可以将一个完整的 Node.js 应用打包为一个独立的二进制文件。用户无需安装 Node.js runtime 或 npm install 依赖。
7.2 SEA 的挑战:原生模块
pi-tui 包含平台特定的原生 .node 二进制模块(terminal manipulation、clipboard 访问等)。在 SEA 模式下,sea blob 中的文件没有实际的磁盘路径,因此 require() 和 process.dlopen() 会失败。
kimi-code 的解决方案:
- 构建时 :将所有原生
.node文件作为 sea assets 注入 blob,同时注入一个 native asset manifest(JSON 文件,记录每个文件的路径和 SHA256) - 运行时 :
ensureNativeAssetTree()检查磁盘缓存目录,将 manifest 中的所有文件写出到<cacheDir>/native/<version>/<target>/<hash>/下 - 重定向 :
installNativeModuleHook()(src/native/module-hook.ts)拦截Module._load,当 pi-tui 尝试 require 绝对路径的原生文件时,将其重定向到磁盘缓存中的副本 - 清理 :启动时通过
queueMicrotask异步清理过期的缓存目录(cleanupStaleNativeCacheForCurrent()),保留当前版本和最近修改的备份版本
Typescript
SEA 二进制 = Node.js runtime + sea blob
│
▼
sea blob 包含:
├── main.js (编译后的应用代码)
├── kimi-code-sdk (bundled)
├── pi-tui JS (bundled)
├── native/darwin/prebuilds/arm64/pi-terminal.node (sea asset)
├── native/darwin/prebuilds/arm64/pi-keyboard.node (sea asset)
└── native-manifest-darwin-arm64.json (sea asset)
│
首次启动时 extract to
│
▼
磁盘缓存: <cacheDir>/native/<version>/darwin-arm64/<sha256>/
├── node_modules/.kimi-native-entry.cjs
└── native/darwin/prebuilds/arm64/
├── pi-terminal.node
└── pi-keyboard.node
7.3 分发渠道
kimi-code 通过多种方式分发:
- npm :
npm install -g kimi-code,这是最主要的安装方式 - 自带 Node 的包管理器:支持多种 Node 版本管理器和包管理器
- CDN 更新 :
kimi upgrade命令从 CDN 获取最新版本,支持锁定版本、回滚等操作(src/cli/update/下的完整更新子系统) - SEA 二进制:独立可执行文件分发,适用于没有 Node.js 的环境
7.4 更新子系统
src/cli/update/ 目录包含了完整的自动更新机制:
- **
cdn.ts**:CDN 下载源 - **
preflight.ts**:启动时更新检查,runUpdatePreflight()在main.ts的handleMainCommand中最先调用 - **
install-lock.ts**:更新锁,防止并发更新 - **
rollout.ts**:灰度发布控制 - **
prompt.ts**:更新提示(是否自动安装更新) - **
cache.ts**:缓存管理
总结
kimi-code 的 CLI/TUI 架构展示了如何在终端中构建一个现代 AI Agent 界面。几个关键设计决策值得关注:
- 分层隔离:CLI 不直接依赖引擎,通过 SDK 的 Harness/Session 接口消费能力
- 差分渲染:pi-tui 的增量更新策略保证了流式交互的流畅性,避免终端闪烁
- 事件驱动:20+ 种 SDK 事件类型与 UI 组件一一映射,SessionEventHandler 是解耦的核心
- 反向 RPC:审批和提问需要引擎等待 UI 响应,ApprovalController/QuestionController 实现了双向通信
- SEA 打包:通过 module hook + 磁盘缓存的方案解决了原生模块在单文件可执行程序中的分发问题
这个架构的复杂度并非多余------它是支撑 kimi-code 从简单的"终端聊天"进化为"完整 Agent 工作台"的基础设施。每一次 prompt 输入、每一个 tool call 卡片、每一个 permission 确认,都在这条精心设计的流水线上流转。