运行时环境
Node.js
编译器与 Workbench 需要 Node.js 22.19 或更高;除非 runtime.node 抬高下限,生成式可执行文件面向
Node.js 22.12 或更高。两者都在安装中说明,而所选的可执行文件下限
会作为 runtime.node 记录在产物清单中。
宿主路径 token
生成的文档通过各宿主会替换的那个 token 引用插件根目录,即每个所选宿主都加载的组合根目录,编译器把 正确的拼写写进各宿主自己的文档,而不是假定只有一种:
替换是按位置的,而不是全局的。Codex 的 ${PLUGIN_ROOT} 只是其生成的钩子命令的命令根,别无他用:
Codex 的 MCP 运行时不插值任何路径 token,因此 MCP command、args 或 env 取值中的插件根 token
只有在位于取值开头、且该服务器的 cwd 就是插件根时才被接受,此时编译器会把它改写为 cwd: "./"
之下的 ./ 相对路径;嵌在中间的 token、缺少该 cwd 的 token,以及任何插件数据或工作区根 token
都是构建错误。Claude Code 只在 Skill 与 agent 内容、钩子与 monitor 命令、MCP 服务器
以及 LSP 服务器中替换 ${CLAUDE_PLUGIN_ROOT} 及其同类,绝不在 settings.json 中替换,这正是
claude.settings 直接拒绝路径 token 的原因。Cursor 被固定的加载器有自己的可替换字段表,位于该表之外的
token 会在构建时报告 AB6028,并由 Doctor 报告 AB7320。
环境变量
这三个原生冒烟测试的开关之所以存在,是因为那些运行需要一个真实的、已登录的 CLI;它们绝不属于日常测试 运行的一部分。
mcp run 下的 .env 优先级
agent-bundle mcp run 按三层组合启动环境,优先级由低到高:
- 生成式服务器自身的 env,其中路径 token 已经解析完毕。
.env文件层。- 操作者真实的
process.env,因此已导出的变量总是胜出。
.env 层是项目根目录下(运行时称之为工作区根目录)Rsbuild 的约定集合(.env、.env.local、
.env.<mode>、.env.<mode>.local),除非 --env-file <path> 用你指定的那些文件替换它,或者
--no-env 把它移除。
这两个标志互斥。文件被读入一个临时对象,因此真实的 process.env 绝不会被改动。
已安装包中的操作者 .env 文件
宿主直接启动已安装的包,因此包自己的外壳会自行应用同一层:stdio MCP 入口、运行插件代码的 hook 包装器
以及产物 CLI bin/<name>.mjs 在启动时读取 <插件根目录>/.env 再读取 .env.local(或
AGENT_BUNDLE_ENV_FILE 指定的文件),只填补宿主未设置的变量,导出的变量胜出,宿主透传的清单 env
默认值让位于文件(stdio 入口随行携带其服务器声明的 env 块以区分二者),.env.local 胜过 .env,取值
永不记录,文件缺失没有开销,无法读取则跳过。这一层在服务器、处理器、路由、provider 与 state 模块求值之前
应用,因此模块顶层的 process.env 读取也能看到它。插件根目录在宿主设置了
AGENT_BUNDLE_PLUGIN_ROOT 时取它,否则取外壳自身所在目录的上一级。不做 ${VAR} 插值。在 mcp run 下
插件根目录就是项目根目录,因此外壳重读的正是 mcp run 已经合成过的文件,不会改变任何东西;--env-file
与 --no-env 以 AGENT_BUNDLE_ENV_FILE 传给子进程。npm 包 bin 不读取任何包文件。Doctor 会报告每个文件
是否存在及其变量数量(AB7331)。细节与优先级表见
安装。
持久状态
已安装产物,MCP 入口、Flight worker、产物 CLI bin、渲染 worker 与独立 hook 包装器,把框架状态根目录
推导为 ~/.agent-bundle/state/<plugin>-<digest>。<plugin> 是代码根目录的 basename,当它不是安全
路径段时则为 plugin;<digest> 是代码根目录真实路径的 SHA-256 摘要的前 16 位十六进制字符。因此两次
安装绝不会共享状态,只读安装也能启动。XDG_STATE_HOME 按上表把推导位置挪走,AGENT_BUNDLE_STATE_ROOT
则覆盖它。npm 包 bin 仍使用 ./.agent-bundle/state。
只有 workspace-durable 状态定义使用 SQLite 驱动;其他生命期使用内存驱动,
不在磁盘上留下任何东西。每个生成的进程只解析代码根目录与状态根目录一次(@agent-bundle/runtime 的
resolvePluginRoot),并以 (await agent()).plugin 发布,{ root, stateRoot },因此把自己的文件
放在框架状态旁边的路由或 provider 读取 plugin.value.stateRoot 即可,不必再自行推导路径。
在 mcp run 之下,env 取值中的 plugin-root 锚点默认展开到项目根目录,而不是产物:在那里产物只是
一个临时构建产物,把持久状态锚定其上会让状态在每次重建时被割裂。若要按字节忠实地演练一次「复制产物后
启动」,请传入指向产物根目录本身(组合插件根目录,而不是某个宿主子目录)的 --plugin-root <path>。
除非 AGENT_BUNDLE_STATE_ROOT 指定了位置,状态根目录随后从展开后的代码根目录推导
(~/.agent-bundle/state/<project>-<digest>)。由 agent-bundle dev 启动的 Workbench MCP 会话与
Workbench 路由调用会把 AGENT_BUNDLE_STATE_ROOT 固定为 <project>/.agent-bundle/state,每个项目
一个状态根,位于构建 epoch 旁边而非其内部,因此能跨重新发布与 epoch retirement 保留,也不会在每次重建
时多出一个用户数据目录。warm dev runtime 本身不会设置该变量。
当服务器名是单个安全路径段时,逐服务器状态目录直接使用该名字;其他任何名字都会变成内容寻址的
server-<digest> 段,因此像 ../shared 这样的名字绝不可能穿出状态根目录。
默认的状态投影预算为:每次提交 5,000 毫秒、每个事件 262,144 字节、状态 1,048,576 字节、100,000 次修订。
agent-bundle inspect --state 会为每个定义报告解析出的驱动、生命期、持久位置与预算来源。
早于当前 SQLite 布局的存储
驱动只读取它今天写出的布局。对早于该布局写出的存储,它既不接管也不迁移,具体有两种情形。
使用 #201 之前文件名的数据库(后缀是状态 id 自身起始字节的十六进制,而不是 SHA-256 摘要)永远不会被
打开。根目录模式的存储若发现该文件、且当前文件名下没有数据库,打开会以点名旧文件的 corrupt
AgentStateError 失败,而不是在旁边新建一个空存储。旧文件原样保留。一旦当前文件名下已有数据库,
旧文件就不再被检查。
使用当前文件名、但 agent_state_journal 表与驱动所创建的不一致的数据库,会在打开时以点名该表的
corrupt AgentStateError 失败。列名、列顺序与各列的 NOT NULL 标志都参与比较,因此像旧版运行时用
ALTER TABLE 留下的可空 result_state 列,会在读取任何行之前就被拒绝。
两种情形的恢复方式相同:把过期的数据库文件移出状态根目录以保留副本,或将其删除(每个 *.sqlite 及其
-wal、-shm 附属文件,或整个状态根目录)。下次启动会按定义的初始状态新建存储。旧历史不会被任何机制重建。