# kaos：执行环境抽象

> 统一的文件与进程 API、本地与 SSH 两种实现、withCwd 的不可变设计，以及 v2 引擎用 os/ 与 Runtime 接过这层抽象之后 kaos 的位置。

- 作者：David（道雾轩）
- 专栏：Kimi Code 源码解读（https://daiw.org/manual/kimi-code.md）
- 最后更新：2026-09-19
- 原文：https://daiw.org/manual/kimi-code/kaos
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

`packages/kaos`（约 3187 行，12 个源文件）是最小但最基础的包之一。名字是 **K**imi **A**gent **O**perating **S**ystem 的缩写，包描述是「Execution environment abstraction for AI agent applications」。

## 接口

`src/kaos.ts` 的 `Kaos` 接口，路径操作一段（第 25 行起）：

```ts
  /** Return the path style used by this environment. */
  pathClass(): 'posix' | 'win32';
  /** Normalize the given path string (resolve `.` / `..` segments). */
  normpath(path: string): string;
  /** Return the home directory of the current user. */
  gethome(): string;
  /** Return the current working directory. */
  getcwd(): string;
```

之后是 `chdir` / `withCwd` / `withEnv`、`stat` / `iterdir` / `glob`、`readBytes` / `readText` / `readLines` / `writeBytes` / `writeText` / `mkdir`，以及 `exec` / `execWithEnv`。

设计要点：

**路径操作同步、IO 操作异步。** 路径计算是纯字符串运算（即使目标是远程机器），不需要往返；文件与进程操作必然要 IO。

**`pathClass()` 返回目标环境的路径风格。** 本地在 Windows 上跑、通过 SSH 连 Linux 时，路径风格是**远端的** `posix` 而非本地的 `win32`。

**`withCwd()` 返回新的 `Kaos` 而非修改自身。** 把「在某个目录下操作」表达成新的环境句柄，避免 `chdir` 的全局状态问题。同样思路的还有 `withEnv(env)`：返回一个给所有子进程叠加环境变量的新 `Kaos`，而且记录在 spawn 时才读取，调用方改一个稳定的 record 就能影响之后的执行。

`current.ts` 用 `AsyncLocalStorage` 绑定「当前 Kaos」：`runWithKaos()` 让并发调用各自在自己的异步上下文里拿到不同实例，互不污染。

## 两种实现

```text
local.ts   LocalKaos —— 本地（node:child_process）
ssh.ts     SSHKaos   —— 远程（ssh2 + SFTP）
```

`ssh.ts` 从 `ssh2` 引入 `Client`、`ClientChannel`、`ConnectConfig`、`SFTPWrapper` 等类型——SFTP 传文件、开 channel 跑命令。接口注释写着「local, SSH, containers, etc.」，容器是预留的方向。

## Environment 探测

`osEnv` 由具体实现填充：`LocalKaos` 用 `detectEnvironmentFromNode()` 直接读，`SSHKaos` 则要连上去**主动探测**。

`login-shell-path.ts` 解决的问题是：从 GUI 启动器或非 login 的父 shell 拉起时，`process.env.PATH` 缺了 `/opt/homebrew/bin` 这类条目，Bash 工具找不到用户在交互 shell 里能用的命令。做法是跑一次用户的 login shell（`$SHELL -l -c /usr/bin/env`），把缺的 PATH 条目补在末尾；Windows 跳过。

v0.29.1 之后新增的 `shell-path-bridge.ts` 处理 Windows 上的 Git Bash / MSYS2：原生路径与 shell 看到的 POSIX 路径互相翻译（`/c/Users/x` 即 `C:\Users\x`，`/tmp/x` 其实是 `%TEMP%\x`），词法翻不了的交给 bash 旁边的 `cygpath -w`，非 win32 bash 环境下恒等。

## v1 退场之后，kaos 在哪

这是本篇变化最大的一节。v0.29.1 时，v1 引擎、acp-adapter 与 node-sdk 都依赖 kaos，v2 则另有 `src/os/`。现在 v1 与 acp-adapter 都已删除，**引擎不再使用 kaos**：agent-core-v2 的 `package.json` 没有声明它，`src/` 里也没有任何 kaos 的 import。全仓声明依赖 kaos 的只剩 node-sdk（放在 `devDependencies`），源码里也只是 `import type { Kaos }`——`KimiHarness` 的会话选项仍有 `kaos` / `persistenceKaos` 两个字段。node-sdk 的 v2 客户端注释说得很坦白：

> agent-core-v2 has no kaos injection point (its fs/process abstraction is the engine-internal hostFs domain, resolved at bootstrap)

所以在 v2 引擎下，这两个选项会被忽略，退化为普通的本地创建 / 恢复。

接过这层抽象的是 v2 自己的几块：

```text
src/os/interface/            IHostFileSystem、IHostProcessService、IHostEnvironment、终端
src/os/backends/node-local/  本地实现
src/runtime/                 Runtime：按能力（fs / process / terminal）组合的执行环境
src/_base/execEnv/           环境探测、login shell PATH、路径桥、文本解码等底层件
```

`_base/execEnv/` 里能看到 kaos 的影子：`BufferedReadable`、`decodeTextWithErrors`、`globPatternToRegex`、`applyLoginShellPathFromNode`、`createShellPathBridge`，名字与 kaos 里的一一对应。`Runtime` 则比 `Kaos` 多了能力声明与状态（`connecting` / `ready` / `degraded` / `disconnected` / `draining` / `disposed`），工具按需租用，`acquire()` 返回 `RuntimeLease`。

## 约束依然成立

v1 的 Bash 工具文件头写着「Execution goes through Kaos, **never directly via `node:child_process`**」。v2 是零注释区，这句话没了，做法没变：`agent/tools/os/bash/bashTool.ts` 第 187 行先 `this.runtime.acquire(['process'])` 租一个带进程能力的运行时，再从 `lease.runtime.process` 起进程。整个 v2 `src/` 里直接 import `node:child_process` 的只有 5 个文件：本地后端 `hostProcessService.ts`、`_base/execEnv/` 的环境探测与路径桥，以及 externalHooks 的 hook 运行器和 tower 的 git 协议。

「换个实例就能换执行环境」的承诺，在 v2 里由 acp-server 兑现：它在 Session 作用域注册 ACP 版 `IHostFileSystem`，文本读写走编辑器；再用 `AcpRuntimeProviderFactory` 让 Bash 在编辑器终端里执行（见 [klient、SDK 与 ACP](https://daiw.org/manual/kimi-code/klient-acp)）。文件工具与 Bash 工具一行不改。

反过来，SSH 目前没有进入 v2：`os/backends/` 下只有 `node-local`，引擎也不依赖 `ssh2`。`SSHKaos` 仍在 kaos 包里，却已没有通往引擎的路径。

## 为什么值得单独一层

三个收益在 v2 里都还成立：

1. **可测试**。`runtime/fakeRuntime.ts` 这类替身让工具测试不碰真实磁盘与进程。
2. **可替换**。Runtime 按能力声明，本地、编辑器（ACP）都只是一个 provider。
3. **可扩展**。将来接容器或远程执行，同样是加 backend / provider，工具层零改动。

代价也还是那句：每个 IO 调用多一层间接，而且**必须全员遵守**——这类抽象的价值与破例次数成反比。kaos 这个包本身的去向也说明了一点：抽象的思路会留下来，具体的包却可能被新架构内化。

下一篇：[收尾复盘](https://daiw.org/manual/kimi-code/recap)。
