kaos:执行环境抽象

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

作者 David更新于 第 32 篇(共 33 篇)

packages/kaos(约 3187 行,12 个源文件)是最小但最基础的包之一。名字是 Kimi Agent Operating System 的缩写,包描述是「Execution environment abstraction for AI agent applications」。

接口

src/kaos.ts 的 Kaos 接口,路径操作一段(第 25 行起):

  /** 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() 让并发调用各自在自己的异步上下文里拿到不同实例,互不污染。

两种实现

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 自己的几块:

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)。文件工具与 Bash 工具一行不改。

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

为什么值得单独一层

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

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

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

下一篇:收尾复盘。

本页目录