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 负责将这次调用路由到正确的底层实现。

这种设计带来三个关键收益:

  1. 可测试性​:测试中可以注入一个纯内存的 mock Kaos,不碰文件系统就能验证工具逻辑
  2. 可扩展性​:添加新环境只需实现一个 Kaos 子类,agent-core 和所有工具零改动
  3. 环境切换​:同一进程内可以同时持有多个 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 —— 直接映射本地文件系统

LocalKaospackages/kaos/src/local.ts)是最常用的实现。它将 Kaos 接口的方法直接映射到 Node.js 的 fs/promiseschild_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 中执行

AcpKaospackages/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 在远程主机执行

SSHKaospackages/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 的子类——KaosFileNotFoundErrorKaosPermissionErrorKaosConnectionError——上层代码可以按类型精确响应

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 模块。每当一个异步操作被调度(如 awaitsetTimeoutPromise.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() 是一个纯函数——接受注入的依赖(platformenvisFile 等),确保测试可以在任何平台无差异运行。Windows 上的 shell 探测尤其复杂:

  1. 先查 KIMI_SHELL_PATH 环境变量(用户显式覆盖)
  2. 在 PATH 中搜索 git.exe,从它的位置反推 Git Bash 安装路径
  3. 执行 git --exec-path 获取 Git 的 exec 目录,进一步反推 bash.exe 位置
  4. 回退到常见安装路径(Program Files\Git\bin\bash.exe 等)
  5. 如果全都找不到,抛出 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 的测试分三个层次:

  1. 单元测试​(test/local.test.tstest/current.test.ts):注入 mock 文件系统,验证接口契约的正确性。LocalKaos 的 readTextwriteTextmkdir 不碰真实磁盘——通过 vitest 的 mock 拦截 fs/promises
  2. 集成测试 / E2E​(test/e2e/):在真实文件系统上运行,覆盖进程生命周期(process-lifecycle.test.ts)、exec 边界情况(exec-edge-cases.test.ts)、并发操作(concurrent-operations.test.ts)、符号链接 stat 等价性(symlink-stat-parity.test.ts
  3. 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 的协作形成完整的"输入-推理-行动-记录"闭环。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐