kimi-code 深度掌握系列文章-执行环境抽象Kaos(十二)
1. Kaos 的设计哲学
1.1 为什么需要执行环境抽象?
一个 Agent 系统不能只"思考",还必须能"行动"——读取文件、搜索目录、执行 shell 命令。而"行动"的载体决定了它行为的边界。同一个 Agent 可能运行在:
- 用户的本地开发机上直接操作进程文件系统)
- 通过 ACP 协议连接的远程 IDE 中(文件由客户端提供,命令在远端执行)
- 一个 SSH 连接的远程服务器上(通过 SFTP 读写文件,通过 SSH channel 执行命令)
- 未来的容器 runtime、WSL、甚至云函数中
如果每次添加新环境都需要改动所有工具实现(bash、read、write、glob、grep 等),系统将迅速膨胀为一团不可维护的条件分支。Kaos(Kimi Agent Operating System)就是为了解决这个问题而生的——它把"执行环境的契约"抽象为一组接口,让上层工具只依赖接口,不关心实现。
1.2 核心理念:同一份 Agent 代码,不同环境运行
Agent 的推理逻辑(turn、plan、tool selection)不因执行环境的变化而改变。无论是在本地 machine 还是远程 SSH host,Agent 调用
kaos.readText('/path/to/file')的代码完全一致——Kaos 负责将这次调用路由到正确的底层实现。
这种设计带来三个关键收益:
- 可测试性:测试中可以注入一个纯内存的 mock Kaos,不碰文件系统就能验证工具逻辑
- 可扩展性:添加新环境只需实现一个 Kaos 子类,agent-core 和所有工具零改动
- 环境切换:同一进程内可以同时持有多个 Kaos 实例(例如一个本地 Kaos 做 prompt 工程,一个 SSH Kaos 在远程执行任务)
1.3 与 Kosong 的对称设计
如果把 kimi-code 的核心抽象成两个维度:
┌──────────────────────────────────────────────────────────────┐
│ kimi-code 核心抽象 │
│ │
│ ┌─────────────────────┐ ┌─────────────────────────────┐ │
│ │ Kosong │ │ Kaos │ │
│ │ LLM 抽象层 │ │ 执行环境抽象 │ │
│ │ │ │ │ │
│ │ 抽象 "思考" │ │ 抽象 "行动" │ │
│ │ ChatProvider │ │ Kaos interface │ │
│ │ ├ Kimi │ │ ├ LocalKaos (本地) │ │
│ │ ├ Anthropic │ │ ├ AcpKaos (IDE 代理) │ │
│ │ ├ OpenAI │ │ └ SSHKaos (远程主机) │ │
│ │ └ Google GenAI │ │ │ │
│ └─────────────────────┘ └─────────────────────────────┘ │
│ │
│ Kosong 管理 "Agent 和 LLM 之间的契约" │
│ Kaos 管理 "Agent 和操作系统之间的契约" │
└──────────────────────────────────────────────────────────────┘
Kosong 回答"用哪个模型、怎么发请求",Kaos 回答"文件在哪、命令在哪执行"。两者互不依赖,但共同为 Agent 提供了完整的运行时能力。一个管"大脑",一个管"手脚"。
名称上也暗示了这种关系——“kosong"在马来语中意为"空”(不绑定任何供应商),"kaos"从 Agent Operating System 的首字母缩写而来——两者都是围绕"抽象"这一主题的对称命名。
2. Kaos 接口设计
Kaos 接口定义在 packages/kaos/src/kaos.ts,是整个执行环境抽象的契约文件。它按照操作类型组织为三个区域:路径操作、文件/目录操作、进程执行。
2.1 接口全貌
export interface Kaos {
/** 环境标识名,如 "local"、"ssh:host.example.com" */
readonly name: string;
/** 目标环境的 OS / shell 探测结果 */
readonly osEnv: Environment;
// ── 路径操作(同步) ──────────────────────────────
/** 返回路径风格 'posix' | 'win32' */
pathClass(): 'posix' | 'win32';
/** 规范化路径(解析 . / .. 段) */
normpath(path: string): string;
/** 当前用户的家目录 */
gethome(): string;
/** 当前工作目录 */
getcwd(): string;
// ── 目录操作(异步) ──────────────────────────────
/** 切换工作目录 */
chdir(path: string): Promise<void>;
/** 返回绑定新 cwd 的新 Kaos 实例 */
withCwd(cwd: string): Kaos;
/** 返回叠加环境变量的新 Kaos 实例 */
withEnv(env: Record<string, string>): Kaos;
/** 获取文件的 stat 元数据 */
stat(path: string, options?: { followSymlinks?: boolean }): Promise<StatResult>;
/** 迭代目录下的条目 */
iterdir(path: string): AsyncGenerator<string>;
/** 按 glob pattern 匹配文件 */
glob(path: string, pattern: string,
options?: { caseSensitive?: boolean }): AsyncGenerator<string>;
// ── 文件操作(异步) ──────────────────────────────
/** 按字节读取文件 */
readBytes(path: string, n?: number): Promise<Buffer>;
/** 以文本方式读取文件 */
readText(path: string,
options?: { encoding?: BufferEncoding;
errors?: 'strict' | 'replace' | 'ignore' }): Promise<string>;
/** 逐行迭代读取文件 */
readLines(path: string, options?: { ... }): AsyncGenerator<string>;
/** 写入原始字节 */
writeBytes(path: string, data: Buffer): Promise<number>;
/** 写入文本 */
writeText(path: string, data: string,
options?: { mode?: 'w' | 'a'; encoding?: BufferEncoding }): Promise<number>;
/** 创建目录 */
mkdir(path: string,
options?: { parents?: boolean; existOk?: boolean }): Promise<void>;
// ── 进程执行 ──────────────────────────────────────
/** 启动一个进程 */
exec(...args: string[]): Promise<KaosProcess>;
/** 启动一个进程并显式指定环境变量 */
execWithEnv(args: string[], env?: Record<string, string>): Promise<KaosProcess>;
}
2.2 KaosProcess 接口
exec() 的返回值 KaosProcess 是进程生命周期的统一抽象,定义在 packages/kaos/src/process.ts:
export interface KaosProcess {
/** 标准输入流(Writable) */
readonly stdin: Writable;
/** 标准输出流(Readable) */
readonly stdout: Readable;
/** 标准错误流(Readable) */
readonly stderr: Readable;
/** 操作系统进程 ID */
readonly pid: number;
/** 进程退出码(未完成时为 null) */
readonly exitCode: number | null;
/** 等待进程结束并返回退出码 */
wait(): Promise<number>;
/** 向进程发送信号 */
kill(signal?: NodeJS.Signals): Promise<void>;
/** 释放 I/O 资源 */
dispose(): Promise<void> | void;
}
2.3 设计要点解析
不可变配置链:withCwd() 和 withEnv() 返回新实例而非修改原实例。每个工具调用可以通过链式调用创建独立的环境快照:
const kaosInProject = kaos
.withCwd('/home/user/project')
.withEnv({ NODE_ENV: 'production' });
// 原始 kaos 不受影响,kaosInProject 继承其所有设置并叠加
这个模式和 Kosong 的 withThinking() / withMaxCompletionTokens() 一致——不可变克隆保证了并发安全,多工具有独立的 cwd 和 env 互不污染。
Python 语义兼容:Kaos 接口大量借鉴了 Python 的 os / open / pathlib 设计。例如 readText() 的 errors 参数直接映射 Python 的 open(errors=...)——'strict' 遇到非法字节抛错,'replace' 替换为 U+FFFD,'ignore' 跳过。这使得从 Python 生态移植的文件处理逻辑可以零摩擦迁入。
AsyncGenerator 模式:glob()、iterdir()、readLines() 都返回 AsyncGenerator,支持大目录和大文件的流式处理。尤其是 readLines() 在 LocalKaos 中实现了真正的分块流式读取——不将整个文件加载到内存,而是在 64KB 的分块中寻找换行符并逐条 yield。
StatResult 与 Python os.stat_result 对齐:
export interface StatResult {
stMode: number; // 文件类型 + 权限位
stIno: number; // inode 编号
stDev: number; // 设备编号
stNlink: number; // 硬链接数
stUid: number; // 属主 uid
stGid: number; // 属组 gid
stSize: number; // 文件大小(字节)
stAtime: number; // 最后访问时间(Unix 时间戳)
stMtime: number; // 最后修改时间
stCtime: number; // 最后状态变更时间
}
3. 多种 Kaos 实现
目前 kimi-code 中有三种 Kaos 实现,各自对应一种执行环境:
3.1 LocalKaos —— 直接映射本地文件系统
LocalKaos(packages/kaos/src/local.ts)是最常用的实现。它将 Kaos 接口的方法直接映射到 Node.js 的 fs/promises 和 child_process。
export class LocalKaos implements Kaos {
readonly name: string = 'local';
private _cwd: string;
private readonly _envLayers: readonly Record<string, string>[];
static async create(): Promise<LocalKaos> {
const [osEnv] = await Promise.all([
detectEnvironmentFromNode(),
applyLoginShellPathFromNode(),
]);
return new LocalKaos(osEnv);
}
// 路径解析:所有方法内部通过 _resolvePath() 将相对路径
// 转为基于实例 _cwd 的绝对路径
private _resolvePath(path: string): string {
if (isAbsolute(path)) return normalize(path);
return join(this._cwd, path);
}
async exec(...args: string[]): Promise<KaosProcess> {
const command = args[0];
const restArgs = args.slice(1);
const child = spawn(command, restArgs, {
cwd: this._cwd,
env: this._buildExecEnv(),
stdio: ['pipe', 'pipe', 'pipe'],
detached: !isWindows,
windowsHide: true,
});
await waitForSpawn(child);
return new LocalProcess(child);
}
}
关键实现细节:
- 独立 cwd:LocalKaos 维护自己的
_cwd,绝不调用process.chdir()。这允许多个 LocalKaos 实例在同一个进程中各自拥有独立的工作目录——通过runWithKaos()切换上下文时不会相互污染 - 环境变量分层:
_envLayers是一个栈式结构,每次withEnv()追加一层。exec 时从process.env出发逐层覆盖,确保层间隔离且并发安全 - login-shell PATH 注入:
create()时会探测用户 login shell 的 PATH(通过$SHELL -l -c /usr/bin/env),提取当前进程 PATH 缺失的条目并追加上去。这解决了 GUI 启动的进程缺失 Homebrew 等用户级工具路径的经典问题
3.2 AcpKaos —— 通过 ACP 协议在远程 IDE 中执行
AcpKaos(packages/acp-adapter/src/kaos-acp.ts)是一个 Decorator 模式 的实现——它包装一个 inner Kaos(通常是 LocalKaos),将文件读写重定向到 ACP(Agent Client Protocol)通道,其他操作委托给 inner。
export class AcpKaos implements Kaos {
constructor(
private readonly conn: AgentSideConnection,
private readonly sessionId: string,
private readonly inner: Kaos,
) {}
// 文件读写:通过 ACP fs/readTextFile 和 fs/writeTextFile
async readText(path: string, options?: { ... }): Promise<string> {
const resp = await this.conn.readTextFile({
sessionId: this.sessionId,
path: this.toClientPath(path),
});
return resp.content;
}
// 二进制读写:回退到 inner(ACP 只有文本通道)
readBytes(path: string, n?: number): Promise<Buffer> {
return this.inner.readBytes(path, n);
}
// 进程执行:始终委托 inner
exec(...args: string[]): Promise<KaosProcess> {
return this.inner.exec(...args);
}
}
AcpKaos 的核心价值在于能力门控的无感化——工具代码不需要知道文件读写的来源是本地磁盘还是 ACP 客户端的未保存缓冲区。如果客户端不支持 ACP 文件桥接,AcpAdapter 就不创建 AcpKaos 包装,工具自然地使用底层 LocalKaos,对上层完全透明。
一个精妙的设计决策:二进制数据不走 ACP。readBytes() 始终走 inner,因为 ACP 的 fs/readTextFile 返回的是已解码的字符串——图片、视频、归档等非 UTF-8 载荷从中转过会被破坏。文本通道和二进制通道的明确分离,避免了"所有文件都走 ACP"可能引入的数据损坏。
3.3 SSHKaos —— 通过 SSH 在远程主机执行
SSHKaos(packages/kaos/src/ssh.ts)基于 ssh2 库实现。文件操作通过 SFTP 子协议,进程执行通过 SSH channel 的 exec 方法。
export class SSHKaos implements Kaos {
private _client: Client;
private _sftp: SFTPWrapper;
private _cwd: string;
static async create(options: SSHKaosOptions): Promise<SSHKaos> {
const client = await connectClient(config);
const sftp = await getSftp(client);
const home = await sftpRealpath(sftp, '.');
const cwd = options.cwd ? await sftpRealpath(sftp, options.cwd) : home;
return new SSHKaos(client, sftp, home, cwd);
}
// exec 的核心:构建远程 shell 命令并通过 client.exec 发送
private async _execInternal(args: string[], env?: Record<string, string>): Promise<KaosProcess> {
const command = SSHKaos._buildExecCommand(args, this._cwd, env);
const channel = await clientExec(this._client, command);
return new SSHProcess(channel);
}
}
关键差异点:
- 命令构造:SSHKaos 通过
_buildExecCommand()将命令拼接为cd '<cwd>' && KEY='v' <cmd> <args>的形式。环境变量通过 POSIX inline 赋值注入而非 ssh2 的ExecOptions.env,因为 sshd 的AcceptEnv指令默认只放行 LANG/LC_*,inline 方式绕过了服务器配置限制 - shell quoting:每个参数都经过
shellQuote()处理——安全字符直接放行,含特殊字符的用单引号包裹并转义内嵌引号——防止参数被 shell 误解析 - 信号发送:
kill()将 Node 信号名(SIGTERM)去掉SIG前缀(TERM)后通过 SSH channel 发送,遵循 RFC 4254 §6.9 规范 - SFTP 错误映射:每个 SFTP 操作失败都被映射为 KaosSSHError 的子类——
KaosFileNotFoundError、KaosPermissionError、KaosConnectionError——上层代码可以按类型精确响应
3.4 三种实现的对比
| 特性 | LocalKaos | AcpKaos | SSHKaos |
|---|---|---|---|
| 文件读写 | fs/promises 直接操作 | ACP RPC(文本);inner(二进制) | SFTP 协议 |
| 进程执行 | child_process.spawn | 委托 inner | SSH channel exec |
| cwd 管理 | 实例级 _cwd | 包装 inner 的 cwd | SFTP realpath 解析 |
| env 注入 | spawn env 选项 | 委托 inner | inline 赋值(绕过 AcceptEnv) |
| 流式读取 | 分块流式(64KB) | ACP 全文读取后模拟 | SFTP 全文读取后模拟 |
| 适用场景 | 本地 CLI / 桌面应用 | 远程 IDE / Zed / VS Code 代理 | 远程服务器管理 |
4. AsyncLocalStorage 上下文传递
4.1 为什么不用全局变量?
在异步 JavaScript 中,全局变量是并发不安全的——两个并行的 Agent turn 可能读取不同的文件,全局 currentKaos 会被后一个覆盖前一个。参数传递虽然安全,但会让每个工具方法的签名都携带一个 kaos: Kaos 参数,侵入性太强。
Kaos 的选择是 Node.js 的 AsyncLocalStorage(ALS)——利用异步执行上下文(async context)自动传递:
// packages/kaos/src/current.ts
import { AsyncLocalStorage } from 'node:async_hooks';
const kaosStorage = new AsyncLocalStorage<Kaos>();
/** 获取当前异步上下文绑定的 Kaos 实例 */
export function getCurrentKaos(): Kaos {
const store = kaosStorage.getStore();
if (store === undefined) {
throw new KaosError(
'No Kaos is bound to the current async context. Call ' +
'setCurrentKaos(...) once at startup, or wrap in runWithKaos(...).'
);
}
return store;
}
/** 在启动时一次性绑定默认 Kaos */
export function setCurrentKaos(kaos: Kaos): void {
kaosStorage.enterWith(kaos);
}
/** 临时切换到指定 Kaos 执行回调 */
export function runWithKaos<T>(kaos: Kaos, fn: () => T): T {
return kaosStorage.run(kaos, fn);
}
4.2 ALS 的工作原理
AsyncLocalStorage 基于 Node.js 的 async_hooks 模块。每当一个异步操作被调度(如 await、setTimeout、Promise.then),ALS 会追踪其"父"上下文并自动传播 store。调用链中任何深度都能通过 getCurrentKaos() 拿到正确的实例,无需显式传参。
// 启动时绑定本地环境
setCurrentKaos(await LocalKaos.create());
// 后续任何地方直接使用——ALS 保证上下文正确
const content = await readText('/path/to/file.txt');
// 等价于
const content = await getCurrentKaos().readText('/path/to/file.txt');
// 临时切换到 SSH 环境
await runWithKaos(sshKaos, async () => {
const remoteContent = await readText('/etc/hostname');
// 这里 getCurrentKaos() 返回 sshKaos
});
// 离开 runWithKaos 后自动恢复
const localContent = await readText('/etc/hostname');
// 这里 getCurrentKaos() 返回原来的 localKaos
4.3 模块级便捷函数
packages/kaos/src/current.ts 还导出了一组模块级便捷函数,作为 getCurrentKaos() 的简写:
// 以下函数内部都调用 getCurrentKaos(),无需手动传递
export function readText(path: string, options?: { ... }): Promise<string>;
export function writeText(path: string, data: string, options?: { ... }): Promise<number>;
export function exec(...args: string[]): Promise<KaosProcess>;
export function glob(path: string, pattern: string, options?: { ... }): AsyncGenerator<string>;
export function stat(path: string, options?: { ... }): Promise<StatResult>;
export function mkdir(path: string, options?: { ... }): Promise<void>;
export function chdir(path: string): Promise<void>;
export function normpath(path: string): string;
// ... 等 20 余个便捷函数
这些函数让工具实现可以极度简洁——一个 import { readText } from '@moonshot-ai/kaos' 就够了,其余由 ALS 自动处理。
5. Shell 和进程管理
5.1 环境探测
Environment 接口记录了运行环境的 OS / shell 信息:
export interface Environment {
readonly osKind: OsKind; // 'macOS' | 'Linux' | 'Windows' | ...
readonly osArch: string; // 'x64' | 'arm64' | ...
readonly osVersion: string; // 内核版本号
readonly shellName: ShellName; // 'bash' | 'sh'
readonly shellPath: string; // shell 可执行文件的完整路径
}
detectEnvironment() 是一个纯函数——接受注入的依赖(platform、env、isFile 等),确保测试可以在任何平台无差异运行。Windows 上的 shell 探测尤其复杂:
- 先查
KIMI_SHELL_PATH环境变量(用户显式覆盖) - 在 PATH 中搜索
git.exe,从它的位置反推 Git Bash 安装路径 - 执行
git --exec-path获取 Git 的 exec 目录,进一步反推 bash.exe 位置 - 回退到常见安装路径(
Program Files\Git\bin\bash.exe等) - 如果全都找不到,抛出
KaosShellNotFoundError,引导用户安装 Git for Windows
5.2 进程生命周期管理
LocalProcess 封装了 Node.js ChildProcess 的完整生命周期:
- spawn 等待:
waitForSpawn()确保进程真正启动后才返回——避免写入 stdin 到一个从未启动的进程 - 输出缓冲:stdout 和 stderr 包装在
BufferedReadable中,在保持源反压的同时允许消费者在流结束后还能读取已缓冲数据 - 进程组终止:在 POSIX 上,
kill()通过process.kill(-pid, signal)终止整个进程组(因为detached:true使子进程成为进程组 leader)。在 Windows 上,通过taskkill /T /F /PID终止进程树——因为 Node 的ChildProcess.kill()在 Windows 上只终止 shell 父进程而留下孙进程孤儿 - 防护性检查:
kill()在pid <= 0时直接返回——防止 spawn 失败的进程误杀整个进程组
5.3 安全模式与进程隔离
进程启动通过 buildLocalSpawnOptions() 统一配置:
export function buildLocalSpawnOptions(
isWindows: boolean,
cwd: string,
env: Record<string, string> | undefined,
): SpawnOptions {
return {
cwd,
env,
stdio: ['pipe', 'pipe', 'pipe'], // stdin/stdout/stderr 全部管道化
detached: !isWindows, // POSIX: 进程组分离
windowsHide: true, // Windows: 隐藏控制台窗口
};
}
关键的隔离措施:windowsHide: true 阻止每次执行命令时弹出控制台窗口;detached: !isWindows 确保在 POSIX 上子进程独立于父进程进程组,配合 kill 时的 process.kill(-pid) 实现完整的进程组管理。
6. 安全考量
6.1 SFTP 命令注入防护
SSHKaos 的 _buildExecCommand() 方法将命令拼接为字符串发送到远程 shell,这是潜在的注入点。防护分两层:
- 参数 quoting:
shellQuote()对每个参数做 POSIX sh 兼容转义——安全字符集外的参数用单引号包裹,内嵌单引号替换为'"'"' - 环境变量名校验:
execWithEnv()在注入环境变量时校验 key 必须符合/^[A-Za-z_][A-Za-z0-9_]*$/,拒绝任何包含特殊字符的 key,防止PATH=foo && rm -rf /这类注入
// SSHKaos._buildExecCommand() 中的 env 注入防护
for (const [key, value] of Object.entries(env)) {
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
throw new KaosValueError(
`SSHKaos.execWithEnv(): invalid env variable name ${JSON.stringify(key)}`
);
}
assignments.push(`${key}=${shellQuote(value)}`);
}
6.2 路径遍历防护
Kaos 的所有文件操作通过 _resolvePath() 进行路径规范化——使用 pathe 库的 normalize() 解析 .. 和 . 段。相对路径总是基于实例的 _cwd 解析,且 normpath() 方法也暴露给调用方做显式规范化。
对于 AcpKaos,路径转换 toClientPath() 在 Windows 上做正斜杠到反斜杠的无害转换:
private toClientPath(path: string): string {
if (this.inner.pathClass() !== 'win32') return path;
return path.replaceAll('/', '\\');
}
6.3 错误隔离
Kaos 定义了一套层级错误类型,确保不同来源的错误不会混淆:
KaosError // 基础错误
├── KaosValueError // 参数无效
├── KaosFileExistsError // 文件/目录已存在
├── KaosShellNotFoundError // Shell 未找到(Windows 特有)
└── KaosSSHError // SSH 操作错误
├── KaosFileNotFoundError // 远程文件不存在
├── KaosPermissionError // 权限不足
└── KaosConnectionError // 连接中断
这种分层让调用方可以精确响应:重试连接中断,忽略"文件已存在"(配合 existOk),向用户报告权限问题。
6.4 敏感路径保护
虽然 Kaos 自身不实现敏感路径白名单/黑名单(那是上层工具层的职责),但其设计为此类保护提供了天然支点——在 Kaos 接口和具体工具之间插入一个检查层即可全局拦截。例如可以在 getCurrentKaos() 返回的实例上包装一层代理,在 readText() 和 writeText() 调用前检查路径是否在白名单内。
7. Kaos 与 Agent 的集成
7.1 Agent 如何获取 Kaos 实例
在 agent-core 层,接口直接引用 Kaos 类型而非实现类。Agent 在初始化时接收一个 Kaos 实例(通常由 CLI 或桌面应用的主入口创建),注入到工具注册表中:
// agent-core 中的工具获取 kaos 的方式
export class BashTool {
async execute(args: BashInput, kaos: Kaos): Promise<BashOutput> {
const proc = await kaos.exec(args.command, ...args.args);
// ... 处理输出
}
}
而在更上层的工具实现中,可以通过 ALS 便捷函数直接访问:
// 工具层无需显式接收 kaos 参数
import { readText, writeText, glob, exec } from '@moonshot-ai/kaos';
async function readTool(filePath: string) {
const content = await readText(filePath);
return { content, lineCount: content.split('\n').length };
}
7.2 工具如何通过 Kaos 执行
kimi-code 的核心工具与 Kaos 的对应关系:
| 工具 | Kaos 方法 | 说明 |
|---|---|---|
Bash |
kaos.exec() |
执行任意 shell 命令 |
Read |
kaos.readText() / kaos.readBytes() / kaos.readLines() |
读取文件内容 |
Write |
kaos.writeText() / kaos.writeBytes() |
写入文件 |
Glob |
kaos.glob() |
按 pattern 搜索文件 |
Grep |
kaos.glob() + kaos.readLines() |
先 glob 再逐行匹配 |
7.3 测试策略
Kaos 的测试分三个层次:
- 单元测试(
test/local.test.ts、test/current.test.ts):注入 mock 文件系统,验证接口契约的正确性。LocalKaos 的readText、writeText、mkdir不碰真实磁盘——通过 vitest 的 mock 拦截fs/promises - 集成测试 / E2E(
test/e2e/):在真实文件系统上运行,覆盖进程生命周期(process-lifecycle.test.ts)、exec 边界情况(exec-edge-cases.test.ts)、并发操作(concurrent-operations.test.ts)、符号链接 stat 等价性(symlink-stat-parity.test.ts) - SSH mock 测试(
test/e2e/ssh-mock.test.ts):通过模拟 ssh2 的行为验证命令构造、shell quoting 的正确性,无需真实 SSH 连接
环境探测(detectEnvironment)的测试尤为精妙——所有 probe 逻辑是依赖注入的,测试可以伪造一个"Windows 平台上有 Git Bash at /fake/git/bin/bash.exe"的场景,在任何操作系统上验证 Windows shell 探测逻辑。
总结
Kaos 是 kimi-code 中连接 Agent 推理与操作系统操作的桥梁。它通过接口抽象 + 多实现 + ALS 上下文传递的架构解决了一个核心问题:
如何让一套 Agent 工具代码在本地、远程 IDE、SSH 主机等不同执行环境中零修改运行?
关键设计决策回顾:
- 接口契约:Kaos 接口完全独立于实现,新环境只需实现 20 余个方法即可接入
- 不可变配置:
withCwd()/withEnv()返回克隆而非修改原实例,并发安全且与 Kosong 设计一致 - ALS 上下文:AsyncLocalStorage 传递 Kaos 实例,免去参数传递的侵入性,同时天然支持并发隔离
- 分层实现:LocalKaos 处理本地,AcpKaos 包装远程 IDE,SSHKaos 管理远程主机——三者共享同一份接口契约但实现策略迥异
- 安全纵深:从 shell quoting、env key 校验、路径规范化到错误类型分层,多层防护确保跨越环境边界时不引入注入风险
在下一篇中,我们将探讨 Transcript——Agent 如何记录和回放每一次完整的交互历史,以及它与 Kaos/Kosong 的协作形成完整的"输入-推理-行动-记录"闭环。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)