close

Volume 2 · Chapter 26

文件系统:事件 gate 型 seam 的样板

15 页给过判定口诀:provider 与 policy 靠事件名连接 = 事件 gate 型。fs 就是这种类型的样板——fs/write-intentfs/edit-intentfs/observed 三个事件让 policy 注入检查,而不是靠 ctx.get() 直接调。

(卷一连接点) 14 页工具流水线(tool-fs 的 body)· 15 页事件 gate 判定 本域:fs / tool-fs / tool-fs-search / tool-str-replace-editor / fs-sandbox / fs-observation-policy (回心脏) fs 写意图经事件 gate → 策略放行/拒绝 → 工具结果

示例本次示例:模型写文件的意图闸门

示例轨迹 26-1 · write-intent 的拦截
# 模型:write({path:'src/auth.ts', content:'...'})
# → tool-fs 执行(inject=['tools','fs','systemPrompt'])
# → 写之前发 fs/write-intent(fs/src/index.ts:58,waterfall):
#   payload = { target: {path:'src/auth.ts'}, actor: exec.agent }
# fs-observation-policy 的监听器(fs-observation-policy/src/index.ts:116-119):
#   占据决策槽、不调 next() → 策略判定:
#     只读检查 → 放行(返回 intent)
#     写到工作区外 → 拒绝(返回 undefined 或改写)
# 判定结果回传 tool-fs → 执行或拒绝 → tool/result
# 对比 shell(15 页):这里没有 ctx.get('policy') 调用——策略是「拦」下来的
来源:26 页 §3 + fs-observation-policy 的 116-119 行真实注释
示例轨迹 26-2 · 一个事件 gate 只能有一个决策者
# 如果第二个插件也监听 fs/write-intent 且不调 next:
# 它短路掉第一个 policy 的判定(08 页 waterfall 语义)
# → 决策冲突 → 第一个 policy 形同虚设
# 教训:事件 gate 型 policy 的组合必须靠 policy 内部编排,
# 不能指望事件链叠加(26 页易错点)
来源:26 页 §5 + 08 页 waterfall 短路语义

§1挂载条目

  • fs-observation-policy → dsh-fs-observation-policy——写意图的观察策略(事件 gate 的消费者)
  • tool-fs → dsh-tool-fs(1517 行)——文件读写工具(inject = ['tools','fs','systemPrompt'],src/index.ts:22)
  • tool-fs-search → dsh-tool-fs-search(1578 行)——文件搜索工具
  • tool-str-replace-editor → dsh-tool-str-replace-editor(553 行)——字符串替换编辑
  • fs-sandbox → dsh-fs-sandbox——沙箱化 fs 提供方(static inject = ['sandboxPolicy'],src/index.ts:60)

§2包文件地图

规模角色
packages/fs/fs/Service DefinitionFileSystem extends Service(src/index.ts:86)+ 事件声明——fs/write-intent 是 waterfall(src/index.ts:58)
packages/fs/fs-observation-policy/Policy:监听 write-intent,占据唯一决策槽、不调 next()(src/index.ts:116-119)——这是与 Service 型 seam 最大的区别
packages/fs/tool-fs/12 文件 / 1517 行Consumer:文件读写工具(read 结果带 diff 视图——05 页老站提过的 presentResult meta)
packages/fs/tool-fs-search/8 文件 / 1578 行搜索工具(内置 grep 语义)

§3机制:事件 gate 的完整形态

  1. provider 发意图:fs 工具要写文件前,发出 fs/write-intent(waterfall)——携带目标与 actor。
  2. policy 拦截:fs-observation-policy 的监听器不调 next()(占据决策槽)——返回策略的判定(放行/改写/拒绝)。这就是「事件 gate」:policy 不通过 ctx.get 被调用,而是通过事件链被下来。
  3. 观察回传fs/observed 报告实际发生的变化(供注入、UI 更新等下游)。

与 15 页的 shell 对比:shell 的连接是 ctx.shell.resolve → run(调用链),fs 的连接是 intent → 事件 → policy(拦截链)。前者适合「能力替换」,后者适合「行为管制」。

§4关键代码

packages/fs/fs/src/index.ts事件声明与 Service Definition58, 86
58    'fs/write-intent'(target: FsTarget, actor: object | undefined, next: () => FsWriteIntent | undefined | Promise<FsWriteIntent | undefined>): Promise<FsWriteIntent | undefined>
86export abstract class FileSystem extends Service {
packages/fs/fs-observation-policy/src/index.tspolicy 占据决策槽116-119
116  // fs/write-intent: occupy the single decision slot — do NOT call next().
119  ctx.on('fs/write-intent', (target, actor) => Promise.resolve().then(() => gate.writeIntent(target, actor)))
58

waterfall 事件声明:返回值是 FsWriteIntent | undefined——policy 的判定就是返回值(放行/改写/拒绝)。

86

FileSystem 的 Definition 与 shell 同构(都是 Service),区别在「连接方式」——fs 多了一层事件 gate。

116-119

注释就是判定口诀的原文:occupy the single decision slot, do NOT call next()。事件 gate 型 policy 的典型形态——不调 next = 短路(08 页 waterfall 语义),决策完全由 policy 给出。

§5易错点

事件 gate 的监听器不调 next() 是有意的——但这意味着只能有一个 policy 占据决策槽。第二个 policy 也会短路掉第一个的判定。多 policy 组合要靠 policy 内部编排,不能指望事件链叠加。

fs 与 subprocess 共享执行世界(15 页提过):把 fs + subprocess 提供方一起指向远程沙箱(e2b),bash、PTY、LSP 全部跟着搬——这就是为什么它们是两个独立的 seam 却设计成可指向同一后端。