10 · 执行世界:文件、Shell、PTY、SSH、LSP 与操作系统沙箱¶
一个 Agent能执行什么,要从 tool consumer → service定义 → provider →操作系统一路读到结束。cwd、tool visibility、审批、文件写限制、进程范围管理是五种不同机制。本章主要研究main da00f7f,发布实验采用rc.2;Linux源码核对不等于已完成macOS/Windows平台实测。
10.1 capability seam 如何让整个Agent搬到远程¶
ctx.fs定义文件访问、目标身份、版本、atomic write/edit;ctx.subprocess定义普通进程、PTY、control通道、managed range与output dispositions;ctx.shell在subprocess之上定义命令执行;ctx.terminals定义持久交互终端;ctx.sandbox只负责将argv包装成有执行限制的argv与enforcement事实;ctx.sandboxPolicy读取部署/session政策。工具分别消费这些service,而不直接把每种平台操作写进AgentLoop。
更换远程execution world要配套换 fs、subprocess、sandbox,保证路径身份、spawn位置和限制在同一台机器。只把bash换成ssh而read/write还留本机,会让模型读A机器、改B机器;service seam避免重复实现Tools,但不会自动修正错误组合。
10.2 FsTarget、版本与atomic mutation¶
LocalFileSystem.resolve()把displayPath与canonical targetKey分开,realpath处理aliases;同一实际文件通过不同symlink路径访问时共享目标身份。配置cwd只是relative resolution default,不是目录围栏。readText/readBytes/stream/list各有不同内存与限制语义,不应从read工具preview大小推断底层服务永远不读全文件。源码:index.ts · export class LocalFileSystem。
mutations按targetKey用进程内FIFO promise链串行。writeText()先probe文件类型,再检查intent:replaceIfVersion要求目标仍在且版本相等;createIfAbsent拒绝已经存在文件;没有intent则是unconditional atomic write。edit读取、验证版本、literal search/replace,再atomic publish。该lock是这个provider实例的mutating calls排序,不是跨进程全局文件锁;外部editor和其他process仍可能修改文件。源码:index.ts · private async withLock、index.ts · override async writeText。
fs-observation-policy另外拥有session→target的WeakMap:fs/observed记录authoritative present或absent版本;write未观察/已确认不存在时给createIfAbsent,观察存在时给replaceIfVersion;edit未读过报FS_NOT_OBSERVED,已知不存在报FS_NOT_FOUND。卸掉这个插件,provider默认unconditional行为仍存在。 所以“所有写文件都要求先读”是组合的策略,不是fs抽象内建不变量。HMR dispose清空观察状态,重新加载不会沿用旧版本证据。源码:index.ts · writeIntent(target:、index.ts · editIntent(target:。
不要混淆内容CAS与文件权限:版本匹配不表示路径获授权;沙箱允许写路径不表示旧读结果仍有效。自己Agent应同时保留两种检查。
10.3 FsSandbox的真实限制:围栏只检查mutation¶
SandboxedFileSystem继承LocalFileSystem,读操作透传。read-only拒绝write/edit;workspace-write允许canonical目标在workspace root或平台temp writableRoots中;danger-full-access不封mutation。write前立即重新canonicalize并把fresh target交给父类,减小symlink变化窗口。源码注释明确它是可信代码里对model-controlled路径的policy containment,不是kernel安全边界;ancestor symlink在检查和syscall间被并发替换仍有残余TOCTOU。源码:index.ts · private async checkedTarget。
因此read-only这个词只说明DSH file effects,读取仍允许;workspace-write通常还允许temp;你的私有数据读取隔离、网络出口、Docker socket或任意host service,不由这个mode统一承诺。要给不可信租户使用,另外部署实际OS/container/account权限和读取白名单provider。
write/edit 在两个快照中都可能先完成 mutation,再同步发出 fs/observed;若 observer 抛错,已提交的文件修改可能最终表现为 tool error。不能据此盲目重试,先重读实际文件。rc.2→main 在这两个文件中的改动只是 file_path schema description 增加参数顺序指导,没有新增 observer 异常容错或改变副作用提交顺序。
10.4 一个政策源,三种优先级¶
SandboxPolicyService.resolve()取显式approved mode → session最后sandbox/mode事件 →部署default;workspace root取session不可变cwd →部署fallback,要求absolute execution-world path。schema默认read-only,但bundle可以显式改,比如sdk-minimal默认danger-full-access。service把模式/root作为cache-safe runtime context提供给模型,loop会记录成history,而不是把每个变化塞进不稳定system prompt。源码:index.ts · resolve(request:。
approveEscalation()要求sandbox_permissions+非空justification配对、目标严格更宽、approver和Agent身份存在、signal有效,审批成功后才执行该次call;拒绝/取消/unavailable不执行。升级一次不改变session standing mode,另一个nested tool也不自动获得同样授权。schema enum保留workspace-write与danger-full-access,真实有效模式在execution时再检查。源码:escalation.ts · export const WIDER_MODES、escalation.ts · export function validateEscalationArgs。
自己Agent的产品设置应明确standing policy、哪些工具可升权、审批渠道是否有实现。仅把“用户给完整权限”的自然语言存persona,不能代替你在DSH中实际配置这些service与approval provider。
10.5 本地process sandbox:选择、探测、包装、归因¶
LocalSandboxProvider选择platform runner。Linux候选bwrap→Landlock,macOS为Seatbelt,Windows为ACL restricted-token runner。竞争候选有bounded functional probe缓存;找不到/不能实施confinement会抛SandboxUnavailableError,不返回原argv假装成功。config允许显式runnerCommand,但必须声明runner failure signatures;这被视为部署所有者对runner能力的断言,不是代码能够自动证明你的替代程序真的隔离。源码:index.ts · export interface Config。
| backend | 实际策略和边界 |
|---|---|
| bwrap | 根ro-bind,dev/proc与PID namespace,die-with-parent;workspace-write额外workspace bind和/tmp tmpfs;没有unshare-net,因此不阻断网络 |
| Landlock launcher | 根readonly grants、/dev/null写;workspace-write增加/tmp与workspace;kernel ABI支持决定full/partial可执行事实 |
| Seatbelt | allow default后deny file-write,再开放/dev/null和shared writable roots;不是deny all网络/读取 |
| Windows ACL | restricted token + workspace SID/temp SID grants;hard link别名、读取和已有其他工具ACL等限制,reports partial |
源码:profiles.ts · export function bwrapProfileArgs、profiles.ts · export function landlockProfileArgs、profiles.ts · export function seatbeltProfileArgs。
ConfinedArgv带enforcement、denialSignatures、runnerFailureRules,consumer必须在结算时区分:程序内部permission denied(政策拒绝)、runner无法启动/实施profile(基础设施不可用)、业务命令普通非零(任务失败)。若只搜stderr的permission denied会把runner拒绝误当成工作区操作权限,甚至错误要求用户升权。SandboxBashExecutor保留每个process的事实,provider spawn rejection只有在错误独立指向argv[0]且workdir已确认可用时归因runner。源码:index.ts · override onProcessDone。
10.6 native目录的作用不能被“纯TypeScript”掩盖¶
native/system提供Linux静态landlock-run和POSIX flock addon。consumer安装平台预编译包,不在安装机器自动compile;import entry也不立即载入addon。Linuxx64/arm64分别带glibc/musl addon,macOS只有flock addon;Landlock还依赖enforcing Linux kernel。缺launcher probe unusable;缺flock binding锁acquisition拒绝。native主要承担系统原语,policy和Session生命周期仍在TypeScript owners。源码:README.md · Importing either entry。
flock acquisition非阻塞exclusive,竞争报EAGAIN/EWOULDBLOCK,锁随open file description的最后descriptor关闭释放。它保护Session写持有权,不能把provider内fs target promise锁误当成这个跨进程advisory锁,更不能把文件锁当业务外部API exactly-once。课程没有在本机构建所有native平台或仿真Windows ACL;可用性验证以具体运行记录为准。
10.7 subprocess的managed range和输出处理¶
LocalSubprocessRuntime的spec显式给argv、cwd、stdio dispositions、env、grace、signal。stdout/stderr可pipe、inherit或bounded tail collection+spill;消费者如果选择pipe就承担读取/backpressure,不能以为service替你无限缓存。ambient env先scrub credentials,显式env override是部署行为;PTC进一步要求空program-visible env。源码:index.ts · export class LocalSubprocessRuntime。
平台managed range让终止覆盖后代范围,而不只kill第一PID。正常dispose先request termination,同时等待direct outcome与range exit,再关闭control endpoints;sibling失败经allSettled收集,必要时force termination。Node同步exit phase另有final termination;没有机会执行JS的硬宿主崩溃不是相同保证。POSIX通常TERM后KILL,Windows即刻terminate;具体Linuxscope/native/fallback能力有不同强弱,源码不隐藏fallback warning。不要以“一定杀净所有系统进程”概括。
resolveExecutable()先scrub环境并查absolute/PATH candidate;相对含斜线命令被拒绝。bash-local作为consumer把命令给bash -c,bash-sandbox覆盖shell启动路径先confine再spawn。subprocess service自己不自动把每个child放入file sandbox:managed lifetime不等于confinement。
10.8 Terminal为什么需要单独生命周期¶
持久terminal不是每次bash -c。terminal-bash使用profile-freeinteractive shell,维护PTY、screen/scrollback、读offset与readiness。shellDialect可bash/pwsh;backendType、shellPath/args、rows/cols、scrollbackLines/bytes、maxReadBytes、poll/readiness/timeout/grace是可配置字段。source默认timeout30000,而minimal preset显式300000。源码:config.ts · export function resolveConfig。
它通过process foreground/activity与prompt handoff判断发送命令后何时可归还,并非看见任意一个 $ 字符就宣称结束;idle silence、handoff grace、tail grace承担不同timing误差。保存变量/cwd的terminal可能发生跨call副作用,掉线/restart/换policy时须按backend的关闭/重建语义,不要假定可从Session transcript恢复live shell。shell输出屏幕snapshot与durable tool content也是不同对象。
用minimal preset时terminal service通过isolate与该preset角色绑定;仍不提供tenant OS隔离。远程PTY用SSH的independent stream,遵守同execution world。
10.9 SSH不是“把命令字符串拼ssh”¶
SshConnection配置由部署拥有:OpenSSH host alias、absolute remote Node/helper/workspace、helper SHA-256,可选PTC bootstrapPath+hash必须成对。POSIX client启动SSHmaster、版本匹配helper,ready等待hello/协议/摘要验证;model tool args不能随意替换远程host和key。源码:index.ts · export interface Config。
一次connection不自动reconnect;失联使活跃operations失效。helper lease依赖heartbeats,丢heartbeat启动remote managed cleanup;data/control stdout/PTY走独立authenticated forwarded streams,避免大输出阻塞所有RPC。request/response framing有maxFrameBytes与maxPending,administrative timeout与process observation wait不同。
远程process经历prepare → allocate independent streams → process.start ack。start前取消可终止准备;ack之后结果不确定或channel失联不能自动replaymutation。RemoteProcess terminate不能确认时会dispose整个ssh lease;filesystem write/edit将policy+expected一起传helper。SandboxSSH让remote provider在同host执行confine,而不是本机生成错误绝对路径。源码:index.ts · private async start、index.ts · override async writeText、index.ts · override async confine。
PTC在SSH world需要已验证的remote bootstrap,不会把本机source路径当作remote可import资源。processPathFromHostPath()可能返回undefined正是要求consumer承认资产未部署,而不是静默调用本机。
10.10 LSP:只读查询接口与受信server进程¶
lsp-stdio把provider id→server config注册到ctx.lsp,启动时resolve全部command和验证bound,再发布provider,避免一半表已注册、一半坏配置。first query lazy singleflight一个canonical workspace server,host通过ctx.fs读取source,暂时didOpen用于hover/definition等query;framing解Content-Length,negotiates position encoding,结果normalize。transport在下一次readonlyquery前/过程中失败可以换server,不是自动重执行文件修改。
每个servercommand由配置指定、直接argv执行、不经shell;workspace/configuration返回静态configuration;workspace/applyEdit拒绝,此Host不替server修改文件或执行command。源码:index.ts · export async function apply、connection.ts · export class LspConnection。
只读LSP工具API不说明LSP二进制没有主机写权限。 LspConnection调用ctx.subprocess.spawn,代码没有自动先sandbox.confine;注册未知server等于执行受信部署程序。shutdown/exit有timeout,随后managed termination;bytes/文档/stderr限制防止query驱动的无界内存。不把“能接language server”写成“自带TypeScript语言服务安装”,command和language-extension table仍需你部署。
10.11 自己Agent的执行能力清单¶
科研/客服Agent可以只装白名单read-onlytools,不装shell/PTC/browser。编码Agent可明确workspace-write,保存prior observation策略,nativebackend实际探测通过后再执行。远程coding则整组换fs/subprocess/sandbox并预装helper/bootstrap/LSP。所有场景都要把“请求已提交”“directchild结束”“managedrange结束”“结果已持久化”“业务验收通过”分别监测;任一处失败可能发生在实际副作用之后,重试前核验真实世界。