Module Structure
该节点连接命令执行与 workspace 隔离。Bash 可走本地、ACP 或沙箱路径,并把长前台 命令原进程移交为后台任务;worktree 管理器则将 Session 映射到独立 Git 工作目录并 在交付前验证源工作区没有漂移。
Directory Layout
packages/cli/src/tools/builtin/shell/— Bash、后台管理、stdin、终止与输出捕获packages/cli/src/context/storage/DurableForegroundProcess.ts— 前台命令租约与 handoffpackages/cli/src/context/storage/DurableProcessLeaseStore.ts— 可恢复的进程身份租约packages/cli/src/utils/process/— 启动闸门、进程组拥有权和跨平台终止packages/cli/src/utils/shell/— 命令归一化、只读审计和验证命令识别packages/cli/src/worktree/WorktreeManager.ts— worktree 生命周期、diff 制品和变更交付packages/cli/src/tools/builtin/worktree/worktreeTools.ts— Agent 可调用的进入/退出适配器
Key Entry Points
bashToolinpackages/cli/src/tools/builtin/shell/bash.ts— 本地、ACP、后台与自动 handoff 分流BackgroundShellManagerinpackages/cli/src/tools/builtin/shell/BackgroundShellManager.ts— Session-owned Shell 生命周期prepareForegroundProcess()inpackages/cli/src/context/storage/DurableForegroundProcess.ts— 先持久登记再释放命令启动闸门WorktreeManager.enter()/apply()/exit()inpackages/cli/src/worktree/WorktreeManager.ts— 隔离、交付和离开工作树
Gotchas
- Bash 明确是
isRetrySafe=false;即使抛出EBUSY/EAGAIN也不能自动重放,因为命令可能已经产生外部副作用 (packages/cli/src/tools/builtin/shell/bash.ts,packages/cli/tests/unit/tooling/tools/builtin/bash.test.ts) Bash的isConcurrencySafe=false但显式parallelism='shared':独立命令可同批进入,仍受 execute bucket 限流;由于没有file_path,通用 FileLockManager 不会替 Shell 串行工作区写入 (packages/cli/src/tools/builtin/shell/bash.ts,packages/cli/src/tools/execution/ToolExecutor.ts)- ACP terminal 执行设置
allowLocalFallback=false;远端终端失败必须原样失败,不能在宿主机悄悄重跑同一命令 (packages/cli/src/tools/builtin/shell/bash.ts,git:27f9df37) WriteStdin会产生外部可见输入且默认不可重放,最多接受 64 KiB;ACP 后台终端当前不支持 stdin 写入 (packages/cli/src/tools/builtin/shell/writeStdin.ts,packages/cli/tests/unit/tooling/tools/builtin/write-stdin.test.ts)- 前台自动转后台复用原进程,不重启命令;只有存在 Session、超时大于 handoff budget、非只读审计代理且首个命令不是
sleep时才有资格 (packages/cli/src/tools/builtin/shell/ForegroundCommandHandoff.ts,packages/cli/src/tools/builtin/shell/bash.ts) - foreground → background 租约提交失败时,前台 owner 仍保持权威并继续等待;不能同时把两个 lease 当作有效所有者 (
packages/cli/src/context/storage/DurableForegroundProcess.ts,packages/cli/src/tools/builtin/shell/bash.ts) - 后台 Shell ID 绑定 Session;TaskOutput、WriteStdin、KillShell 用其他 Session ID 查询时都按不存在处理,不能跨会话接管 (
packages/cli/src/tools/builtin/shell/BackgroundShellManager.ts,packages/cli/src/tools/builtin/task/taskOutput.ts) - 本地前后台输出各流只保留最后 1 MiB,随后还会按命令类型二次投影;
stdout_total_bytes/omitted_bytes才能区分“命令没输出”和“早期输出已丢弃” (packages/cli/src/tools/builtin/shell/BoundedOutputBuffer.ts,packages/cli/src/tools/builtin/shell/ShellOutputProjection.ts) - worktree 从 committed
HEAD创建,原工作区未提交改动不会复制;返回的sourceHadChanges是警告,不代表新 worktree 包含这些内容 (packages/cli/src/worktree/WorktreeManager.ts,packages/cli/src/tools/builtin/worktree/worktreeTools.ts) - worktree 交付要求源仓库 HEAD 和创建时工作状态指纹都未变化;只检查 patch 可应用而忽略源状态会覆盖任务期间的用户改动 (
packages/cli/src/worktree/WorktreeManager.ts) ExitWorktree(action='remove')在无法验证状态或存在未提交文件/未合并 commit 时拒绝,只有用户明确允许discard_changes=true才强制删除 (packages/cli/src/worktree/WorktreeManager.ts,packages/cli/src/tools/builtin/worktree/worktreeTools.ts)- stale worktree GC 只删除符合托管命名、足够旧、Git 身份一致且无脏文件/未推送 commit 的目录;异常目录和活跃 Session 均保留 (
packages/cli/src/worktree/WorktreeManager.ts)
Architecture
- 命令启动经过独立 Node admission gate:目标子进程仅在 lease 持久化后收到启动字节,owner 消失时 gate 负责终止进程组,关闭了“进程已启动但尚未登记”的崩溃窗口 (
packages/cli/src/utils/process/CommandAdmissionGate.ts,packages/cli/src/context/storage/DurableForegroundProcess.ts) DurableProcessLeaseStore同时记录 owner 与 root PID 的平台指纹;恢复时先识别活 owner,再验证 PID 未复用,POSIX leader 已退出但进程组仍活跃时单独清理 leaderless group (packages/cli/src/context/storage/DurableProcessLeaseStore.ts,packages/cli/src/utils/process/ProcessIdentity.ts)BackgroundShellManager统一管理本地后台、自动 handoff 和 ACP 外部前台候选,默认最多 16 个全局活动进程、每 Session 4 个 (packages/cli/src/tools/builtin/shell/BackgroundShellManager.ts)- worktree manager 以 Session keyed mutex 串行生命周期操作,目录位于按仓库路径哈希隔离的 managed root,分支名同时包含逻辑名称和 Session 哈希 (
packages/cli/src/worktree/WorktreeManager.ts) - 任务交付使用临时 Git index 收集 tracked/untracked 变更,生成 binary patch,先
git apply --check再应用到源工作区,不通过 merge 或复制目录 (packages/cli/src/worktree/WorktreeManager.ts)
Decisions
- managed worktree 同时服务显式 Enter/Exit 工具和子代理任务隔离;工具层只负责用户交互,实际恢复、差异和交付语义集中在
WorktreeManager(packages/cli/src/tools/builtin/worktree/worktreeTools.ts,packages/cli/src/agent/subagents/SubagentWorktreeLifecycle.ts,git:c80db2c1) - 后台与前台命令都升级为持久 lease 和 owned process tree,以便 Session 恢复时回收 orphan,而不是只依赖当前 Node 子进程句柄 (
packages/cli/src/context/storage/DurableProcessLeaseStore.ts,git:c012135e,git:cbe0e8ed) - 输出限制分为 capture 上限和模型/UI projection 上限,既限制常驻内存又保留完整计数事实,避免单纯截断字符串后无法判断损失量 (
packages/cli/src/tools/builtin/shell/ShellOutputCapture.ts,packages/cli/src/tools/builtin/shell/OutputTruncator.ts,git:81a9acc9)
Patterns
- 本地 POSIX 子进程作为 detached group leader 启动,终止先发
SIGTERM再在 grace period 后发SIGKILL;Windows 使用taskkill /T并保留直接 child fallback (packages/cli/src/utils/process/OwnedProcessTree.ts) - workspace-required 或只读审计 Bash 会先生成沙箱命令;cwd 在 lexical 与 realpath 两层都必须位于 workspace,沙箱启动失败按 permission denied fail closed (
packages/cli/src/tools/builtin/shell/WorkspaceWriteSandbox.ts,packages/cli/src/tools/builtin/shell/bash.ts) - 只读审计沙箱不继承任意进程环境、禁止网络和 workspace 写入,只允许专用临时目录写入及受信任 PATH 中的运行时;普通 worktree 沙箱允许 workspace 写入 (
packages/cli/src/tools/builtin/shell/WorkspaceWriteSandbox.ts) - SessionRuntime 启动时先回收 foreground/background orphan,关闭时主动 kill 本 Session 后台进程并释放 worktree 内存绑定 (
packages/cli/src/agent/runtime/SessionRuntime.ts)
Resource Bounds
- 后台与前台 capture 分别为 stdout/stderr 各 1 MiB,并最多保留 32 个 chunk;超大 chunk 从 UTF-8 边界保留尾部 (
packages/cli/src/tools/builtin/shell/BoundedOutputBuffer.ts) - worktree diff 最多 100 个文件、单文件 1 MiB、总计 2 MiB;真正交付的 binary patch 上限为 50 MiB,展示截断不改变交付内容 (
packages/cli/src/worktree/WorktreeManager.ts) - 后台容量耗尽返回
RESOURCE_EXHAUSTED/background_shell_busy且可稍后重试,不会排队等待并额外占用内存 (packages/cli/src/tools/builtin/shell/BackgroundShellManager.ts,packages/cli/src/tools/builtin/shell/bash.ts)