Codex 系统手册

面向执行的 app-server 和 core 命令编排

stage-14.2.115 个文件

这一阶段是系统真正“把命令跑起来”的执行通道,位于主干活儿的中段。app-server 先接住客户端请求,检查启动、输入、改窗口、终止等操作,再交给底层进程跑,并把输出送回去。TUI 的小工具把本地和远端文件、后台命令包成同一套用法。core 这边像调度室:接收用户或模型的 shell、unified-exec 请求,补好目录、环境、超时、审批、沙箱和联网规则,再调用真正的执行器;特殊 zsh 路线不合适时会退回普通执行。整体目标是能跑命令,但不让它乱跑。

本阶段的文件15

app-server 执行入口点

这些文件暴露面向 app-server 的命令和进程执行 API,并为客户端连接管理长期运行的执行会话。

app-server/src/request_processors/command_exec_processor.rs源码 ↗
orchestrationrequest handling

可以把这个文件看成“命令执行前台接待员”。客户端说“帮我运行这个命令”时,它不会马上照做,而是先确认当前有本地运行环境,检查命令不能为空,检查参数不能互相打架,比如既说不要超时又给了超时时间。然后它会准备工作目录、环境变量、超时规则、输出大小限制、终端尺寸,以及沙箱权限。沙箱可以理解成给命令划出的安全活动范围,防止它随便读写文件或联网。它还会按需要启动受控网络代理,也就是把网络访问先经过一个可检查的通道。准备好后,它把这些信息打包成底层执行请求,交给 CommandExecManager 真正启动和维护进程。后续客户端写入输入、改窗口大小、终止命令、断开连接,也都通过这里转交给管理器。

函数细节9
CommandExecRequestProcessor::new14–29 ↗
fn new(
        arg0_paths: Arg0DispatchPaths,
        config: Arc<Config>,
        outgoing: Arc<OutgoingMessageSender>,
        config_manager: ConfigManager,
        environment_manager: Arc<Enviro

作用:创建一个新的命令执行请求处理器,把它以后办事需要的配置、发送消息通道、环境管理器等都装进去。有人要搭建请求处理流程时会调用它。

数据流:输入是一组已经准备好的零件:路径信息、全局配置、对客户端发消息的工具、配置管理器和环境管理器。它把这些保存到结构体里,并新建一个默认的 CommandExecManager,用来后续照看正在运行的命令。输出是一个可以开始处理命令请求的处理器对象。

调用关系:它在上层初始化请求处理器时被调用。它自己只做组装工作,其中 CommandExecManager 用默认方式创建;后面的启动命令、写入、调整大小、终止等操作都会依赖这里保存下来的这些零件。

调用图:调用 1 个内部函数(default);被 1 处调用(new)。

CommandExecRequestProcessor::one_off_command_exec31–40 ↗
async fn one_off_command_exec(
        &self,
        request_id: &ConnectionRequestId,
        params: CommandExecParams,
    ) -> Result<Option<ClientResponsePayload>, JSONRPCErrorError>

作用:处理一次性的“执行命令”请求。它先确认服务器确实有本地环境可以运行命令,再把请求交给真正的执行准备流程。

数据流:输入是请求编号和命令参数。它先读取环境管理器,确认本地环境存在;如果不存在,就返回错误。确认通过后,它把请求编号和参数交给 exec_one_off_command。成功时不直接返回命令结果,而是返回空响应,因为命令的输出会通过另外的消息通道陆续发给客户端。

调用关系:它由 handle_initialized_client_request 在收到已初始化客户端的执行命令请求时调用。它先调用 require_local_environment 做门禁检查,再调用 exec_one_off_command 继续处理。

调用图:调用 2 个内部函数(exec_one_off_command, require_local_environment);被 1 处调用(handle_initialized_client_request)。

CommandExecRequestProcessor::command_exec_write42–51 ↗
async fn command_exec_write(
        &self,
        request_id: ConnectionRequestId,
        params: CommandExecWriteParams,
    ) -> Result<Option<ClientResponsePayload>, JSONRPCErrorError>

作用:把客户端发来的输入写进某个正在运行的命令里,比如给交互式程序输入一行文字。

数据流:输入是请求编号和写入参数。它把这些交给 command_exec_manager.write,由管理器找到对应的进程并写入数据。完成后,它把管理器返回的结果包装成客户端能识别的响应;如果失败,就把错误传回去。

调用关系:它由 handle_initialized_client_request 在收到写入命令输入的请求时调用。这个函数不自己碰进程,只是把活儿转交给 CommandExecManager 的 write。

调用图:调用 1 个内部函数(write);被 1 处调用(handle_initialized_client_request)。

CommandExecRequestProcessor::command_exec_resize53–62 ↗
async fn command_exec_resize(
        &self,
        request_id: ConnectionRequestId,
        params: CommandExecResizeParams,
    ) -> Result<Option<ClientResponsePayload>, JSONRPCErrorError>

作用:处理客户端要求调整命令终端窗口大小的请求。它主要用于带 tty 的交互式命令,tty 可以理解成一个虚拟终端窗口。

数据流:输入是请求编号和新的终端尺寸参数。它把这些交给 command_exec_manager.resize,让管理器更新对应运行中进程的终端大小。然后把结果转成客户端响应返回。

调用关系:它由 handle_initialized_client_request 在客户端调整终端大小时调用。具体怎么找到进程、怎么改尺寸,都交给 CommandExecManager 的 resize 完成。

调用图:调用 1 个内部函数(resize);被 1 处调用(handle_initialized_client_request)。

CommandExecRequestProcessor::command_exec_terminate64–73 ↗
async fn command_exec_terminate(
        &self,
        request_id: ConnectionRequestId,
        params: CommandExecTerminateParams,
    ) -> Result<Option<ClientResponsePayload>, JSONRPCErrorError>

作用:处理客户端要求停止某个正在运行命令的请求。比如用户点了取消,或者不想让命令继续跑了。

数据流:输入是请求编号和终止参数。它把这些交给 command_exec_manager.terminate,由管理器结束对应进程。之后把终止操作的结果包装成客户端响应返回。

调用关系:它由 handle_initialized_client_request 在收到终止请求时调用。这个函数负责把请求送到正确的管理器入口,真正的停止动作由 CommandExecManager 的 terminate 做。

调用图:调用 1 个内部函数(terminate);被 1 处调用(handle_initialized_client_request)。

CommandExecRequestProcessor::connection_closed75–79 ↗
async fn connection_closed(&self, connection_id: ConnectionId)

作用:当某个客户端连接断开时,通知命令执行管理器清理和这个连接有关的命令。这样可以避免客户端走了,后台命令还没人管地继续占资源。

数据流:输入是断开的连接编号。它把这个编号交给 command_exec_manager.connection_closed。管理器据此检查哪些命令属于这个连接,并做相应清理。这个函数没有返回业务结果。

调用关系:它由外层连接关闭处理流程调用。它本身只是一个通知入口,把“连接断了”这件事传给 CommandExecManager。

调用图:调用 1 个内部函数(connection_closed);被 1 处调用(connection_closed)。

CommandExecRequestProcessor::require_local_environment81–87 ↗
fn require_local_environment(&self) -> Result<(), JSONRPCErrorError>

作用:检查当前是否配置了本地运行环境。没有本地环境时,执行本地命令就没有落脚点,所以必须提前拦住。

数据流:它不接收额外输入,而是读取 environment_manager。它尝试拿到本地环境;拿到了就返回成功,拿不到就生成一个“本地环境未配置”的内部错误。

调用关系:它被 one_off_command_exec 当作第一道门禁调用。只有它通过后,请求才会继续进入真正的命令执行准备流程。

调用图:被 1 处调用(one_off_command_exec)。

CommandExecRequestProcessor::exec_one_off_command89–96 ↗
async fn exec_one_off_command(
        &self,
        request_id: &ConnectionRequestId,
        params: CommandExecParams,
    ) -> Result<(), JSONRPCErrorError>

作用:这是执行一次性命令的薄包装。它主要负责把借用的请求编号复制一份,再交给内部函数做大量准备工作。

数据流:输入是请求编号引用和命令参数。它复制请求编号,避免后续异步任务需要长期使用时受到借用限制,然后调用 exec_one_off_command_inner。输出就是内部函数的成功或错误结果。

调用关系:它被 one_off_command_exec 调用。它自己不做复杂判断,只是连接外层入口和 exec_one_off_command_inner 这个真正干活的函数。

调用图:调用 1 个内部函数(exec_one_off_command_inner);被 1 处调用(one_off_command_exec);外部调用 1 个(clone)。

CommandExecRequestProcessor::exec_one_off_command_inner98–344 ↗
async fn exec_one_off_command_inner(
        &self,
        request_id: ConnectionRequestId,
        params: CommandExecParams,
    ) -> Result<(), JSONRPCErrorError>

作用:这是本文件最核心的函数,负责把“我要运行这个命令”的原始请求,整理成安全、完整、可执行的底层任务。它会检查参数、套用权限规则、准备环境变量和沙箱,最后启动命令。

数据流:输入是请求编号和完整命令参数。它先记录调试信息,然后检查命令是否为空、权限配置是否冲突、终端尺寸是否只在 tty 模式下使用、输出限制和超时设置是否自相矛盾。接着它计算工作目录,生成环境变量,并套用客户端给的环境变量增删。之后它把超时时间转成内部格式,决定输出是否限量,选择捕获输出的方式,再根据 permissionProfile 或 sandboxPolicy 算出最终权限。permissionProfile 是一套权限档案,sandboxPolicy 是旧式沙箱策略;两者不能同时用。需要时,它会通过配置管理器重新加载某个工作目录下的权限配置,也可能启动受控网络代理。最后它把所有信息组成 ExecParams,再调用 build_exec_request 生成真正的底层执行请求,并交给 command_exec_manager.start 启动。结果是命令开始运行,或者返回清楚的参数错误、权限错误、内部错误。

调用关系:它只被 exec_one_off_command 调用,是执行命令请求的核心流水线。它会向 ConfigManager 请求按目录加载配置,会调用权限和沙箱相关转换函数来得到最终安全边界,会调用 build_exec_request 生成底层执行请求,最后把启动进程和后续流式输出的工作交给 CommandExecManager 的 start。

调用图:调用 7 个内部函数(start, load_for_cwd, build_exec_request, from_runtime_permissions_with_enforcement, from_legacy_sandbox_policy, from_legacy_sandbox_policy_for_cwd, from);被 1 处调用(exec_one_off_command);外部调用 9 个(new, default, clone, Cancellation, format!, default, from_config, debug!, try_from)。

app-server/src/command_exec.rs源码 ↗
orchestrationrequest handling

这个文件像一个“远程命令管家”。客户端说“帮我跑这个命令”,它会检查参数、分配或记录进程编号,然后真正把程序启动起来。启动后,它把每个运行中的命令登记在表里,后续客户端才能按 processId 找到它,继续写输入、改终端大小,或者要求终止。它还会同时盯着几件事:客户端有没有发控制指令、程序有没有自己退出、有没有超时。程序的 stdout 和 stderr 会被收集;如果客户端要求流式输出,就边跑边发通知,否则等命令结束后一次性回包。这里还特别处理了 Windows 沙箱:这种模式不支持交互式流控,所以会拒绝写入、终止、resize 这类操作。整体上,它把“JSON-RPC 请求”翻译成“本机进程操作”,再把结果翻译回协议响应。

函数细节22
CommandExecManager::default54–59 ↗
fn default() -> Self

作用:创建一个空的命令执行管家。新建后还没有任何正在运行的命令,并准备好从 1 开始生成内部进程编号。

数据流:进去没有参数 → 它创建一张共享的会话表,用来记住每个连接下的进程;再创建一个原子计数器(可被多个任务安全递增的数字) → 出来一个可用的 CommandExecManager。

调用关系:服务或测试需要命令执行能力时会先调用它。后面的 start、write、terminate、resize 都依赖它保存的会话表来找到对应进程。

调用图:被 8 处调用(cancellation_expiration_keeps_process_alive_until_terminated, dropped_control_request_is_reported_as_not_running, timeout_or_cancellation_reports_cancellation_without_timeout_exit_code, windows_sandbox_non_streaming_exec_uses_execution_path, windows_sandbox_process_ids_reject_terminate_requests, windows_sandbox_process_ids_reject_write_requests, windows_sandbox_streaming_exec_is_rejected, new);外部调用 4 个(new, new, new, new)。

InternalProcessId::error_repr134–139 ↗
fn error_repr(&self) -> String

作用:把内部使用的进程编号变成适合放进错误消息里的文字。这样客户端看到报错时,能知道是哪一个 processId 出了问题。

数据流:进去一个 InternalProcessId,可能是服务器生成的数字,也可能是客户端给的字符串 → 数字直接转成文本,字符串尽量转成 JSON 字符串格式 → 出来一段清楚的错误展示文本。

调用关系:当 start 发现重复进程编号、send_control 找不到进程、command_no_longer_running_error 生成报错时,会用这种格式让错误更好读。

调用图:外部调用 1 个(to_string)。

CommandExecManager::start143–306 ↗
async fn start(
        &self,
        params: StartCommandExecParams,
    ) -> Result<(), JSONRPCErrorError>

作用:启动一个新命令,并把它纳入这个管家的追踪范围。它是 command/exec 请求真正开始工作的地方。

数据流:进去的是启动参数:连接和请求编号、命令内容、工作目录、环境变量、是否需要终端、是否流式输入输出、输出上限等 → 它先校验哪些组合合法,决定进程编号,防止同一连接下 processId 重复;然后按模式启动普通管道进程或伪终端进程(伪终端就是模拟一个真实终端窗口);最后开一个后台任务继续监控运行 → 如果启动成功返回空结果;如果参数不合法或进程拉起失败,就返回 JSON-RPC 错误。

调用关系:它由上层 exec_one_off_command_inner 在收到执行命令请求时调用。对于 Windows 受限沙箱,它走 execute_env 的非交互路径;对于普通情况,它启动进程后把长期监控工作交给 run_command。

调用图:调用 4 个内部函数(run_command, internal_error, invalid_request, execute_env);被 1 处调用(exec_one_off_command_inner);外部调用 8 个(clone, spawn_pipe_process, spawn_pipe_process_no_stdin, spawn_pty_process, format!, matches!, channel, spawn)。

CommandExecManager::write308–340 ↗
async fn write(
        &self,
        request_id: ConnectionRequestId,
        params: CommandExecWriteParams,
    ) -> Result<CommandExecWriteResponse, JSONRPCErrorError>

作用:把客户端发来的输入写进某个正在运行的命令。比如远程 shell 正在等用户输入时,就靠它把文字送进去。

数据流:进去的是请求编号和写入参数,包括 processId、base64 编码的字节内容、是否关闭 stdin → 它先确认至少有内容或关闭动作,再把 base64 还原成原始字节 → 通过 send_control 把“写入”指令送给对应进程 → 出来一个空的写入成功响应,或一个参数/状态错误。

调用关系:它由 command_exec_write 这类协议入口调用。它自己不直接碰进程,而是把指令交给 send_control,再由 run_command 收到后调用 handle_process_write。

调用图:调用 2 个内部函数(send_control, invalid_params);被 1 处调用(command_exec_write);外部调用 2 个(new, Client)。

CommandExecManager::terminate342–354 ↗
async fn terminate(
        &self,
        request_id: ConnectionRequestId,
        params: CommandExecTerminateParams,
    ) -> Result<CommandExecTerminateResponse, JSONRPCErrorError>

作用:请求停止某个正在运行的命令。客户端点“取消运行”或连接清理时,就需要这种能力。

数据流:进去的是请求编号和目标 processId → 它把 processId 和连接编号合成唯一钥匙 → 通过 send_control 发出“终止”指令 → 出来一个空的终止成功响应,或返回找不到进程、进程不支持等错误。

调用关系:它由 command_exec_terminate 调用。真正向进程发终止信号的动作发生在 run_command 收到 CommandControl::Terminate 之后。

调用图:调用 1 个内部函数(send_control);被 1 处调用(command_exec_terminate);外部调用 1 个(Client)。

CommandExecManager::resize356–373 ↗
async fn resize(
        &self,
        request_id: ConnectionRequestId,
        params: CommandExecResizeParams,
    ) -> Result<CommandExecResizeResponse, JSONRPCErrorError>

作用:调整交互式终端的窗口大小。比如客户端终端窗口变宽或变高时,要通知后台伪终端同步变化。

数据流:进去的是请求编号、processId、行数和列数 → 它先用 terminal_size_from_protocol 检查尺寸合法并转成内部格式 → 再通过 send_control 发出“调整大小”指令 → 出来一个空的 resize 响应,或返回尺寸非法、进程不存在等错误。

调用关系:它由 command_exec_resize 调用。它只负责把协议参数变成控制消息,实际 resize 由 run_command 收到消息后交给 handle_process_resize。

调用图:调用 2 个内部函数(send_control, terminal_size_from_protocol);被 1 处调用(command_exec_resize);外部调用 1 个(Client)。

CommandExecManager::connection_closed375–402 ↗
async fn connection_closed(&self, connection_id: ConnectionId)

作用:当某个客户端连接断开时,清理这个连接启动过的所有命令。这样不会留下没人管的后台进程。

数据流:进去一个 connection_id → 它从会话表里找出这个连接名下的所有进程并移除 → 对仍然活跃的进程发送“终止”指令;不等待客户端响应,因为连接已经没了 → 出来没有返回值,但会触发后台进程收尾。

调用关系:它由更外层的 connection_closed 流程调用。它是断线清理环节,和 start 登记会话、run_command 结束后移除会话一起保证进程表不会长期脏掉。

调用图:被 1 处调用(connection_closed);外部调用 1 个(with_capacity)。

CommandExecManager::send_control404–439 ↗
async fn send_control(
        &self,
        process_id: ConnectionProcessId,
        control: CommandControl,
    ) -> Result<(), JSONRPCErrorError>

作用:把“写入、终止、调整大小”这些控制命令安全地送到目标进程。它是外部请求和后台运行任务之间的传话筒。

数据流:进去一个连接+进程编号,以及一个控制动作 → 它先在会话表里查目标是否存在,并确认不是 Windows 沙箱那种不支持控制的会话;然后创建一次性回复通道,把控制请求发给后台任务;最后等待后台任务确认成功或失败 → 出来成功为空,失败就是 JSON-RPC 错误。

调用关系:write、terminate、resize 都调用它。它把活儿交给 run_command 的控制循环,run_command 再根据具体动作调用 handle_process_write、handle_process_resize 或直接请求终止。

调用图:调用 1 个内部函数(invalid_request);被 3 处调用(resize, terminate, write);外部调用 1 个(channel)。

run_command442–554 ↗
async fn run_command(params: RunCommandParams)

作用:在后台盯住一个已经启动的命令,直到它结束。它同时处理客户端控制、超时、进程退出、输出收集和最终响应。

数据流:进去的是已启动进程、控制消息接收器、输出设置、超时策略、请求信息 → 它开两个输出收集任务分别处理 stdout 和 stderr;然后用异步等待同时监听控制指令、超时事件和进程退出;退出后再给输出一点时间排空 → 最后向客户端发送 CommandExecResponse,里面有退出码、stdout、stderr。

调用关系:它由 CommandExecManager::start 启动在后台任务里。它把输出处理交给 spawn_process_output,把写入和 resize 分别交给 handle_process_write、handle_process_resize,并通过 outgoing 把最终响应发出去。

调用图:调用 1 个内部函数(spawn_process_output);被 1 处调用(start);外部调用 7 个(clone, from_millis, pin!, select!, spawn, sleep, channel)。

spawn_process_output556–618 ↗
fn spawn_process_output(params: SpawnProcessOutputParams) -> tokio::task::JoinHandle<String>

作用:专门处理某一路程序输出,比如 stdout 或 stderr。它决定输出是实时发给客户端,还是先攒起来等结束后一起返回。

数据流:进去的是输出字节流、连接和进程编号、是哪一路输出、是否流式发送、输出大小上限 → 它不断收字节块,把小块尽量合并到合适大小;如果有上限就截断;如果要流式输出,就把字节 base64 编码后发通知;如果不流式,就存进缓冲区 → 出来一个字符串形式的最终输出。

调用关系:run_command 会为 stdout 和 stderr 各调用一次它。它负责把底层字节变成协议通知或最终字符串,是进程输出到客户端之间的桥。

调用图:调用 1 个内部函数(bytes_to_string_smart);被 1 处调用(run_command);外部调用 4 个(CommandExecOutputDelta, new, select!, spawn)。

handle_process_write620–642 ↗
async fn handle_process_write(
    session: &ProcessHandle,
    stream_stdin: bool,
    delta: Vec<u8>,
    close_stdin: bool,
) -> Result<(), JSONRPCErrorError>

作用:真正把一段输入写到进程的 stdin。stdin 可以理解为程序的“键盘输入”。

数据流:进去的是进程句柄、这个命令是否允许流式 stdin、要写入的字节、是否关闭 stdin → 如果没开启流式 stdin,就拒绝;如果有字节,就发给进程写入通道;如果要求关闭,就关闭 stdin → 出来成功为空,失败返回例如“stdin 已关闭”之类的请求错误。

调用关系:run_command 收到 CommandControl::Write 后调用它。CommandExecManager::write 只是把客户端请求送到这里,实际写入发生在这个函数里。

调用图:调用 1 个内部函数(invalid_request);外部调用 2 个(close_stdin, writer_sender)。

handle_process_resize644–651 ↗
fn handle_process_resize(
    session: &ProcessHandle,
    size: TerminalSize,
) -> Result<(), JSONRPCErrorError>

作用:真正调整伪终端的尺寸。它让后台程序知道终端现在有多少行、多少列。

数据流:进去的是进程句柄和内部 TerminalSize → 它调用进程句柄的 resize 方法 → 成功则返回空,失败则把底层错误包装成客户端能理解的 invalid_request 错误。

调用关系:run_command 收到 CommandControl::Resize 后调用它。CommandExecManager::resize 负责前置校验和传话,这里负责落到实际进程。

调用图:外部调用 1 个(resize)。

terminal_size_from_protocol653–665 ↗
fn terminal_size_from_protocol(
    size: CommandExecTerminalSize,
) -> Result<TerminalSize, JSONRPCErrorError>

作用:把协议里的终端大小转换成本地进程库使用的格式,并顺手检查数值是否合理。

数据流:进去的是协议对象,里面有 rows 和 cols → 它检查行数和列数都必须大于 0 → 出来一个 TerminalSize;如果传了 0,就返回参数错误。

调用关系:CommandExecManager::resize 在发送 resize 控制消息前调用它。这样可以把坏参数挡在入口处,不让底层伪终端收到无意义尺寸。

调用图:调用 1 个内部函数(invalid_params);被 1 处调用(resize)。

command_no_longer_running_error667–672 ↗
fn command_no_longer_running_error(process_id: &InternalProcessId) -> JSONRPCErrorError

作用:生成“这个命令已经不在运行了”的统一错误。这样相关报错格式一致,客户端也更容易理解。

数据流:进去一个内部进程编号 → 它用 error_repr 把编号转成适合展示的文本,再拼成错误消息 → 出来一个 invalid_request 类型的 JSON-RPC 错误。

调用关系:当控制消息发不出去,或后台任务没有回确认时,send_control 会用这种错误告诉调用者:目标命令已经结束或不可用。

调用图:调用 1 个内部函数(invalid_request);外部调用 1 个(format!)。

tests::windows_sandbox_exec_request696–712 ↗
fn windows_sandbox_exec_request() -> ExecRequest

作用:为测试准备一个 Windows 受限沙箱用的执行请求。它把重复的测试配置集中起来,避免每个测试都手写一大段。

数据流:进去没有参数 → 它读取当前目录,构造一个运行 cmd 的 ExecRequest,并设置为 WindowsRestrictedToken 沙箱、只读权限等 → 出来一个可直接用于测试的执行请求。

调用关系:多个 Windows 沙箱相关测试会调用它,尤其是验证流式执行被拒绝、非流式执行走特殊路径这些行为。

调用图:调用 3 个内部函数(new, read_only, current_dir);外部调用 2 个(new, vec!)。

tests::windows_sandbox_streaming_exec_is_rejected715–745 ↗
async fn windows_sandbox_streaming_exec_is_rejected()

作用:测试 Windows 受限沙箱下不能开启流式输出。这个限制很重要,因为该沙箱路径不支持交互式控制。

数据流:进去是测试框架启动的异步测试环境 → 它创建 manager 和假 outgoing 通道,构造一个带 stream_stdout_stderr 的 Windows 沙箱请求 → 调用 start 后期待失败,并检查错误码和错误消息准确。

调用关系:它直接覆盖 CommandExecManager::start 的 Windows 沙箱参数校验分支,防止以后有人误把不支持的流式模式放行。

调用图:调用 3 个内部函数(disabled, default, new);外部调用 6 个(new, Integer, new, windows_sandbox_exec_request, assert_eq!, channel)。

tests::windows_sandbox_non_streaming_exec_uses_execution_path749–794 ↗
async fn windows_sandbox_non_streaming_exec_uses_execution_path()

作用:测试 Windows 沙箱的非流式命令会走 execute_env 那条执行路径,而不是普通交互式进程路径。

数据流:进去是异步测试环境 → 它启动一个非流式 Windows 沙箱请求,并从 outgoing 通道等待服务器发出的消息 → 因为测试环境下命令可能执行失败,所以它检查收到的是发给正确连接的执行错误。

调用关系:它调用 CommandExecManager::start,验证 start 在 WindowsRestrictedToken 且非流式时会异步执行并回传结果或错误。

调用图:调用 3 个内部函数(disabled, default, new);外部调用 10 个(new, from_secs, Integer, new, windows_sandbox_exec_request, assert!, assert_eq!, channel, panic!, timeout)。

tests::cancellation_expiration_keeps_process_alive_until_terminated798–879 ↗
async fn cancellation_expiration_keeps_process_alive_until_terminated()

作用:测试“仅取消令牌”这种过期策略不会立刻杀掉进程,进程会一直活着直到显式 terminate。这里是在保护取消语义不被误当成普通超时。

数据流:进去是异步测试环境 → 它启动一个 sleep 30 的命令,设置 ExecExpiration::Cancellation;先等待一小段时间确认没有提前返回;然后调用 terminate → 最后检查收到响应、退出码非 0、stdout 为空。

调用关系:它覆盖 start、run_command 和 terminate 的配合:start 启动命令,run_command 保持监听,terminate 通过 send_control 让 run_command 请求进程停止。

调用图:调用 6 个内部函数(disabled, default, new, new, read_only, current_dir);外部调用 15 个(new, new, from_secs, new, Integer, new, assert!, assert_eq!, assert_ne!, Cancellation (+5 more))。

tests::timeout_or_cancellation_reports_cancellation_without_timeout_exit_code883–951 ↗
async fn timeout_or_cancellation_reports_cancellation_without_timeout_exit_code()

作用:测试“超时或取消”里如果是取消触发,不应该伪装成超时退出码 124。这样客户端能区分真正超时和人为取消。

数据流:进去是异步测试环境 → 它启动 sleep 30,设置一个很长超时和一个取消令牌;随后立即触发取消 → 等待响应后解析 CommandExecResponse,并确认退出码不是 EXEC_TIMEOUT_EXIT_CODE。

调用关系:它主要验证 run_command 对 ExecExpirationOutcome 的判断:只有真正 TimedOut 才使用固定超时退出码,取消不使用。

调用图:调用 6 个内部函数(disabled, default, new, new, read_only, current_dir);外部调用 13 个(new, new, from_secs, new, Integer, new, assert_eq!, assert_ne!, channel, panic! (+3 more))。

tests::windows_sandbox_process_ids_reject_write_requests954–987 ↗
async fn windows_sandbox_process_ids_reject_write_requests()

作用:测试 Windows 沙箱会话即使登记了 processId,也不能接受 write 请求。因为这种会话不支持交互式 stdin。

数据流:进去是异步测试环境 → 它手动往 manager 的会话表塞入一个 UnsupportedWindowsSandbox 会话;然后对这个 processId 调用 write → 期待失败,并检查错误码和固定错误消息。

调用关系:它直接覆盖 CommandExecManager::write 到 send_control 的路径,确保 send_control 看到 UnsupportedWindowsSandbox 时会拒绝控制操作。

调用图:调用 1 个内部函数(default);外部调用 4 个(Integer, new, Client, assert_eq!)。

tests::windows_sandbox_process_ids_reject_terminate_requests990–1021 ↗
async fn windows_sandbox_process_ids_reject_terminate_requests()

作用:测试 Windows 沙箱会话不能通过 command/exec/terminate 这类流式控制接口停止。这个行为和 write、resize 的限制保持一致。

数据流:进去是异步测试环境 → 它手动登记一个 UnsupportedWindowsSandbox 会话;然后调用 terminate → 期待收到“这些控制操作不支持 Windows 沙箱进程”的错误。

调用关系:它覆盖 CommandExecManager::terminate 到 send_control 的路径,证明 send_control 对 Windows 沙箱的统一拦截不仅适用于 write,也适用于 terminate。

调用图:调用 1 个内部函数(default);外部调用 4 个(Integer, new, Client, assert_eq!)。

tests::dropped_control_request_is_reported_as_not_running1024–1059 ↗
async fn dropped_control_request_is_reported_as_not_running()

作用:测试如果控制请求发出后没人处理,客户端会收到“命令已经不在运行”的错误,而不是卡住或报奇怪错误。

数据流:进去是异步测试环境 → 它创建一个假的 Active 会话和控制通道;后台任务拿走请求后丢弃,不回复;然后调用 terminate → send_control 等不到确认,于是返回 no longer running 错误,测试检查错误内容。

调用关系:它验证 CommandExecManager::terminate、send_control 和 command_no_longer_running_error 的失败路径,保证后台任务异常消失时客户端能得到清楚反馈。

调用图:调用 1 个内部函数(default);外部调用 6 个(Integer, new, Client, assert_eq!, channel, spawn)。

app-server/src/request_processors/process_exec_processor.rs源码 ↗
orchestrationrequest handling

这个文件解决的是:客户端想运行一个命令,但真正的进程在服务器这台机器上。它先检查服务器有没有配置本地运行环境,再校验命令、进程名、终端大小等参数。启动后,它用一个表记住“某个连接里的某个进程”,避免两个同名进程撞车。每个进程都有一条控制通道,后续写入标准输入、调整伪终端大小、杀进程,都会先找到这条通道再发命令。进程运行时,stdout 和 stderr(标准输出和错误输出)会被单独收集;如果客户端要求实时流式输出,就边读边发通知,否则等进程结束后一次性带回。它还处理超时、连接断开自动杀进程、输出大小上限等情况,避免进程失控或输出把内存撑爆。

函数细节20
ProcessExecRequestProcessor::new57–66 ↗
fn new(
        outgoing: Arc<OutgoingMessageSender>,
        environment_manager: Arc<EnvironmentManager>,
    ) -> Self

作用:创建一个处理进程执行请求的对象。服务器启动相关组件时会用它,把发消息的通道、本地环境信息和进程会话表装到一起。

数据流:输入是对外发消息的工具和环境管理器 → 它保存这两样东西,并创建一个空的进程管理器 → 输出一个可以处理 spawn、stdin、resize、kill 等请求的处理器。

调用关系:它是这个文件的组装入口之一,上层初始化请求处理器时调用它。后面的所有进程请求都会通过这个对象转交给内部的 ProcessExecManager。

调用图:被 1 处调用(new);外部调用 1 个(default)。

ProcessExecRequestProcessor::process_spawn68–143 ↗
async fn process_spawn(
        &self,
        request_id: ConnectionRequestId,
        params: ProcessSpawnParams,
    ) -> Result<(), JSONRPCErrorError>

作用:处理客户端的“启动一个进程”请求。它负责把外部传来的参数检查清楚,再交给内部管理器真正启动进程。

数据流:输入是请求编号和启动参数,比如命令、工作目录、环境变量、是否开终端、超时时间 → 它先确认本地环境可用,检查命令和进程句柄不能为空,合并环境变量,转换超时和终端大小 → 最后把整理好的启动参数交给 ProcessExecManager::start;成功时不直接返回内容,而是由 start 先发启动响应,再后台运行进程。

调用关系:上层的 handle_initialized_client_request 收到 process/spawn 请求时会调用它。它自己不直接操作系统进程,而是做前置检查和参数翻译,然后把真正启动工作交给 ProcessExecManager::start。

调用图:调用 4 个内部函数(invalid_params, invalid_request, start, require_local_environment);被 1 处调用(handle_initialized_client_request);外部调用 6 个(new, Cancellation, format!, vars, debug!, try_from)。

ProcessExecRequestProcessor::process_write_stdin145–154 ↗
async fn process_write_stdin(
        &self,
        request_id: ConnectionRequestId,
        params: ProcessWriteStdinParams,
    ) -> Result<Option<ClientResponsePayload>, JSONRPCErrorError>

作用:处理客户端给某个正在运行的进程写输入的请求。比如用户在远程终端里敲字,就是走这里。

数据流:输入是请求编号和写入参数,里面有进程句柄、base64 编码的数据、是否关闭输入 → 它把请求转给 ProcessExecManager::write_stdin → 输出一个写入成功的响应,或返回参数错误、进程不存在等错误。

调用关系:上层的 handle_initialized_client_request 收到写 stdin 请求时调用它。它只是薄薄的一层转发,把活交给进程管理器。

调用图:调用 1 个内部函数(write_stdin);被 1 处调用(handle_initialized_client_request)。

ProcessExecRequestProcessor::process_resize_pty156–165 ↗
async fn process_resize_pty(
        &self,
        request_id: ConnectionRequestId,
        params: ProcessResizePtyParams,
    ) -> Result<Option<ClientResponsePayload>, JSONRPCErrorError>

作用:处理客户端调整终端窗口大小的请求。它用于交互式命令,比如全屏编辑器或 shell,需要知道当前终端有多少行多少列。

数据流:输入是请求编号和新的终端尺寸 → 它交给 ProcessExecManager::resize_pty 去检查并发送调整命令 → 输出一个调整成功的响应,或返回尺寸非法、进程不存在等错误。

调用关系:上层的 handle_initialized_client_request 收到 resize 请求时调用它。具体查找进程和发控制命令由 ProcessExecManager::resize_pty 完成。

调用图:调用 1 个内部函数(resize_pty);被 1 处调用(handle_initialized_client_request)。

ProcessExecRequestProcessor::process_kill167–176 ↗
async fn process_kill(
        &self,
        request_id: ConnectionRequestId,
        params: ProcessKillParams,
    ) -> Result<Option<ClientResponsePayload>, JSONRPCErrorError>

作用:处理客户端要求结束某个进程的请求。它相当于远程按下“停止运行”按钮。

数据流:输入是请求编号和要杀掉的进程句柄 → 它转给 ProcessExecManager::kill → 成功时返回空的 kill 响应;如果进程已经不存在,就返回错误。

调用关系:上层的 handle_initialized_client_request 收到 kill 请求时调用它。它把具体停止进程的命令交给 ProcessExecManager。

调用图:调用 1 个内部函数(kill);被 1 处调用(handle_initialized_client_request)。

ProcessExecRequestProcessor::connection_closed178–182 ↗
async fn connection_closed(&self, connection_id: ConnectionId)

作用:当客户端连接断开时,清理这个连接启动过的所有进程。这样可以避免用户走了,服务器上还留着没人管的命令。

数据流:输入是断开的连接编号 → 它通知内部进程管理器找出这个连接名下的所有会话并发送杀进程命令 → 没有普通返回值,但会触发后台进程退出。

调用关系:连接层发现连接关闭时会调用它。它把清理工作交给 ProcessExecManager::connection_closed。

调用图:调用 1 个内部函数(connection_closed);被 1 处调用(connection_closed)。

ProcessExecRequestProcessor::require_local_environment184–190 ↗
fn require_local_environment(&self) -> Result<(), JSONRPCErrorError>

作用:确认服务器确实有本地执行命令的环境。没有这个检查,远程或未配置环境下可能会错误地尝试启动本机进程。

数据流:输入是当前处理器里保存的环境管理器 → 它询问是否能拿到本地环境 → 如果能就返回成功,否则返回“本地环境未配置”的内部错误。

调用关系:ProcessExecRequestProcessor::process_spawn 在启动进程前先调用它。它是启动命令前的安全门。

调用图:被 1 处调用(process_spawn)。

ProcessExecManager::start265–353 ↗
async fn start(&self, params: StartProcessParams) -> Result<(), JSONRPCErrorError>

作用:真正登记并启动一个新进程。它既要防止同一个连接里进程句柄重复,也要选择用普通管道还是伪终端来运行命令。

数据流:输入是一整包启动参数,包括命令、目录、环境变量、是否使用 tty、输出上限等 → 它拆出程序名和参数,创建控制通道,把进程登记到会话表,再调用底层工具启动进程 → 启动成功后先发 spawn 响应,然后开一个后台任务运行 run_process;如果启动失败,会从会话表删掉记录并返回错误。

调用关系:ProcessExecRequestProcessor::process_spawn 完成参数检查后调用它。它启动底层进程后,把后续生命周期交给 run_process,并在后台任务结束时清理会话表。

调用图:调用 3 个内部函数(internal_error, invalid_request, run_process);被 1 处调用(process_spawn);外部调用 7 个(clone, spawn_pipe_process, spawn_pipe_process_no_stdin, spawn_pty_process, format!, channel, spawn)。

ProcessExecManager::write_stdin355–384 ↗
async fn write_stdin(
        &self,
        request_id: ConnectionRequestId,
        params: ProcessWriteStdinParams,
    ) -> Result<ProcessWriteStdinResponse, JSONRPCErrorError>

作用:把客户端发来的输入数据送到指定进程的标准输入。标准输入可以理解成程序“键盘输入”的入口。

数据流:输入是请求编号和写入参数 → 它要求至少有数据或关闭输入其中之一,解码 base64 数据,再包装成 Write 控制命令 → 通过 send_control 发给对应进程,成功后返回 ProcessWriteStdinResponse。

调用关系:ProcessExecRequestProcessor::process_write_stdin 会调用它。它不直接写进程,而是通过 ProcessExecManager::send_control 找到会话并排队发控制命令,最终由 run_process 里的控制循环执行。

调用图:调用 2 个内部函数(invalid_params, send_control);被 1 处调用(process_write_stdin);外部调用 1 个(new)。

ProcessExecManager::kill386–398 ↗
async fn kill(
        &self,
        request_id: ConnectionRequestId,
        params: ProcessKillParams,
    ) -> Result<ProcessKillResponse, JSONRPCErrorError>

作用:请求停止某个正在运行的进程。它用于用户主动取消命令,或界面上的停止按钮。

数据流:输入是请求编号和进程句柄 → 它生成 Kill 控制命令并通过 send_control 发给进程 → 如果进程接收到了命令,就返回 ProcessKillResponse。

调用关系:ProcessExecRequestProcessor::process_kill 会调用它。它依赖 send_control 找到正确的进程会话,真正的终止动作由 run_process 收到 Kill 后调用底层会话完成。

调用图:调用 1 个内部函数(send_control);被 1 处调用(process_kill)。

ProcessExecManager::resize_pty400–414 ↗
async fn resize_pty(
        &self,
        request_id: ConnectionRequestId,
        params: ProcessResizePtyParams,
    ) -> Result<ProcessResizePtyResponse, JSONRPCErrorError>

作用:调整某个伪终端进程的窗口大小。伪终端就是让程序以为自己正运行在一个真实终端里的工具。

数据流:输入是请求编号、进程句柄和协议里的行列数 → 它先用 terminal_size_from_protocol 检查并转换尺寸,再生成 Resize 控制命令 → 通过 send_control 发给对应进程,成功后返回 ProcessResizePtyResponse。

调用关系:ProcessExecRequestProcessor::process_resize_pty 会调用它。它把协议参数转换成本地终端尺寸,然后借助 send_control 交给 run_process 处理。

调用图:调用 2 个内部函数(send_control, terminal_size_from_protocol);被 1 处调用(process_resize_pty)。

ProcessExecManager::connection_closed416–442 ↗
async fn connection_closed(&self, connection_id: ConnectionId)

作用:清掉某个连接名下还活着的所有进程。它是防泄漏的保险丝,避免断线后后台命令继续跑。

数据流:输入是连接编号 → 它锁住会话表,找出这个连接的所有进程,先从表里移除,再逐个发送不等待回复的 Kill 控制命令 → 输出为空,但这些进程会被要求终止。

调用关系:ProcessExecRequestProcessor::connection_closed 在连接断开时调用它。它不等每个进程回复,因为连接已经没了,只需要尽力通知后台任务停止。

调用图:被 1 处调用(connection_closed);外部调用 1 个(with_capacity)。

ProcessExecManager::send_control444–473 ↗
async fn send_control(
        &self,
        connection_id: ConnectionId,
        process_handle: String,
        control: ProcessControl,
    ) -> Result<(), JSONRPCErrorError>

作用:把“写输入、调整大小、杀进程”这类控制命令送到指定进程。它像前台请求和后台进程之间的传话筒。

数据流:输入是连接编号、进程句柄和控制命令 → 它用连接编号加进程句柄到会话表里查找对应通道,创建一次性回复通道,把控制命令发过去并等待结果 → 输出成功或具体错误;如果找不到进程或进程已经停了,就返回相应错误。

调用关系:write_stdin、kill、resize_pty 都通过它发送命令。命令送达后,由 run_process 的控制循环读取并执行,再通过一次性通道把结果传回来。

调用图:被 3 处调用(kill, resize_pty, write_stdin);外部调用 1 个(channel)。

run_process476–597 ↗
async fn run_process(params: RunProcessParams)

作用:负责一个已启动进程的完整生命周期。它同时盯着三件事:客户端控制命令、超时信号、进程退出。

数据流:输入是已启动的进程、输出通道、控制通道、超时规则和输出设置 → 它启动两个收集任务分别读取 stdout 和 stderr,然后在循环里等待写入、调整、杀进程、超时或自然退出 → 进程结束后给输出收集一点有限时间收尾,最后向客户端发送 ProcessExited 通知,里面有退出码和收集到的输出。

调用关系:ProcessExecManager::start 启动进程成功后在后台任务里调用它。它会调用 collect_spawn_process_output 收集输出,并在控制命令到来时调用写入、调整或终止相关逻辑。

调用图:调用 1 个内部函数(collect_spawn_process_output);被 1 处调用(start);外部调用 8 个(clone, from_millis, ProcessExited, pin!, select!, spawn, sleep, channel)。

collect_spawn_process_output599–664 ↗
fn collect_spawn_process_output(
    params: SpawnProcessOutputParams,
) -> tokio::task::JoinHandle<ProcessOutputCapture>

作用:收集某个进程的一路输出,比如 stdout 或 stderr。它既可以实时把输出片段推给客户端,也可以先攒起来等进程结束再汇总。

数据流:输入是连接编号、进程句柄、输出接收通道、是否流式发送、输出大小上限等 → 它在后台读字节块,尽量合并成较大的块,按上限截断,必要时把片段 base64 编码后发 ProcessOutputDelta 通知 → 最后输出 ProcessOutputCapture,里面有转成文字的内容和是否达到上限。

调用关系:run_process 会为 stdout 和 stderr 各调用一次它。它负责输出这条支线,进程结束或等待输出超时后返回收集结果给 run_process。

调用图:调用 1 个内部函数(bytes_to_string_smart);被 1 处调用(run_process);外部调用 4 个(ProcessOutputDelta, new, select!, spawn)。

handle_process_write666–690 ↗
async fn handle_process_write(
    session: &ProcessHandle,
    stream_stdin: bool,
    delta: Vec<u8>,
    close_stdin: bool,
) -> Result<(), JSONRPCErrorError>

作用:真正执行“往进程标准输入写数据”这一步。它会先确认这个进程启动时允许输入流式写入。

数据流:输入是进程句柄、是否允许 stdin 流式输入、要写入的字节和是否关闭输入 → 如果不允许写,就返回错误;如果有数据,就送进底层写入队列;如果要求关闭输入,就关闭 stdin → 输出成功或错误。

调用关系:run_process 收到 Write 控制命令时会用它。ProcessExecManager::write_stdin 只是发命令,真正写入发生在这里。

调用图:调用 1 个内部函数(invalid_request);外部调用 2 个(close_stdin, writer_sender)。

handle_process_resize692–699 ↗
fn handle_process_resize(
    session: &ProcessHandle,
    size: TerminalSize,
) -> Result<(), JSONRPCErrorError>

作用:真正执行“调整伪终端大小”这一步。它把已经检查过的行列数交给底层进程会话。

数据流:输入是进程句柄和新的终端尺寸 → 它调用底层 resize 方法 → 成功则返回空成功;失败则把底层错误包装成客户端能看懂的请求错误。

调用关系:run_process 收到 Resize 控制命令时会用它。ProcessExecManager::resize_pty 负责前面查找进程和转换参数,具体调整在这里完成。

调用图:外部调用 1 个(resize)。

terminal_size_from_protocol701–713 ↗
fn terminal_size_from_protocol(
    size: ProcessTerminalSize,
) -> Result<TerminalSize, JSONRPCErrorError>

作用:把协议里的终端尺寸转换成本地执行工具认识的尺寸。它顺手挡掉 0 行或 0 列这种无效大小。

数据流:输入是协议对象里的 rows 和 cols → 它检查两者都必须大于 0 → 输出本地 TerminalSize;如果不合法,输出参数错误。

调用关系:ProcessExecRequestProcessor::process_spawn 在带初始终端大小启动时会用它,ProcessExecManager::resize_pty 在调整大小时也会用它。它是协议数据进入底层终端工具前的转换口。

调用图:调用 1 个内部函数(invalid_params);被 1 处调用(resize_pty)。

no_active_process_error715–719 ↗
fn no_active_process_error(process_handle: &str) -> JSONRPCErrorError

作用:生成“找不到活动进程”的错误消息。客户端传了一个不存在的进程句柄时会用到它。

数据流:输入是进程句柄字符串 → 它把句柄放进一段清楚的错误说明里 → 输出一个 invalid_request 类型的 JSON-RPC 错误。

调用关系:ProcessExecManager::send_control 查会话表失败时会用它。它让 write、resize、kill 这些操作在目标不存在时返回统一的错误格式。

调用图:调用 1 个内部函数(invalid_request);外部调用 1 个(format!)。

process_no_longer_running_error721–723 ↗
fn process_no_longer_running_error(process_handle: &str) -> JSONRPCErrorError

作用:生成“进程已经不在运行”的错误消息。它用于进程刚刚结束、控制命令已经送不到的情况。

数据流:输入是进程句柄字符串 → 它拼出说明该进程已停止的错误文本 → 输出一个 invalid_request 类型的 JSON-RPC 错误。

调用关系:ProcessExecManager::send_control 在控制通道发送失败或等待回复失败时会用它。它把后台进程消失这种竞态情况,转换成客户端能理解的错误。

调用图:调用 1 个内部函数(invalid_request);外部调用 1 个(format!)。

TUI 命令转发

这些文件提供 TUI 侧辅助工具,用于打包工作区命令请求并将其路由到 app-server。

tui/src/app_server_session/fs.rs源码 ↗
io_transportrequest handling

这个文件像一个“文件操作代办窗口”。TUI 想碰应用服务器那边的文件时,不直接自己读写,而是通过 AppServerSession 发请求。建目录、写文件、读文件、删除文件各有一个入口。写文件和读文件时,文件内容会用 base64(一种把二进制数据变成普通文字的编码)传输,这样 JSON 这种文本格式也能安全装下任意文件内容。最关键的零件是 request_fs_path:它会先判断当前连接方式。如果服务器在远端,就发 JSON-RPC(一种用 JSON 包装的远程调用请求);如果服务器就在同一进程里,就把路径检查成绝对路径后直接调用本地客户端。这样同一套 TUI 代码可以同时适配远程和本地两种运行方式,也能在出错时给出更清楚的错误提示。

函数细节5
AppServerSession::fs_create_directory_all_path24–42 ↗
async fn fs_create_directory_all_path(
        &mut self,
        path: &AppServerPath,
    ) -> Result<()>

作用:让应用服务器创建一个目录,并且会连同中间缺失的父目录一起创建。比如要建 a/b/c,就算 a 或 b 不存在,也会一起补上。

数据流:进去的是一个应用服务器路径。函数把它包装成“fs/createDirectory”请求,并标明 recursive 为 true,也就是允许递归创建。请求成功后不返回具体数据,只表示目录已经准备好;失败时返回带上下文的错误。

调用关系:这是上层想准备目录时会调用的入口。它把具体请求交给会话里的通用文件请求流程来发送;构造远程请求参数时用 json! 生成 JSON 数据,避免上层自己拼协议内容。

调用图:外部调用 1 个(json!)。

AppServerSession::fs_write_file_path44–64 ↗
async fn fs_write_file_path(
        &mut self,
        path: &AppServerPath,
        bytes: Vec<u8>,
    ) -> Result<()>

作用:把一段字节内容写到应用服务器指定路径的文件里。它适合保存生成的文件、缓存或用户操作产生的内容。

数据流:进去的是目标路径和一串原始字节。函数先把字节转成 base64 文本,再把路径和这段文本放进“fs/writeFile”请求。请求完成后,文件内容就应该已经写到服务器那边;函数本身只返回成功或失败。

调用关系:这是 TUI 需要保存文件时的入口。它负责把二进制内容变成适合 JSON 传输的文本,再交给会话请求机制发出去;构造远程参数时使用 json!。

调用图:外部调用 1 个(json!)。

AppServerSession::fs_read_file_path66–81 ↗
async fn fs_read_file_path(&mut self, path: &AppServerPath) -> Result<Vec<u8>>

作用:从应用服务器指定路径读取一个文件,并把文件内容还原成原始字节。上层不需要知道传输过程中用了什么编码。

数据流:进去的是一个路径。函数发出“fs/readFile”请求,拿到服务器返回的 base64 文本,然后把这段文本解码回 Vec<u8>,也就是原始文件字节。如果服务器返回的 base64 不合法,它会明确报“读文件返回了无效 base64 数据”。

调用关系:这是读取文件内容时的入口。它会调用 AppServerSession::request_fs_path 完成真正的请求发送和响应解析,自己只额外负责把返回内容从 base64 还原;构造请求参数时使用 json!。

调用图:调用 1 个内部函数(request_fs_path);外部调用 1 个(json!)。

AppServerSession::fs_remove_path83–99 ↗
async fn fs_remove_path(&mut self, path: &AppServerPath) -> Result<()>

作用:让应用服务器删除指定路径。它用于清理不再需要的文件或目录入口。

数据流:进去的是要删除的路径。函数把它包装成“fs/remove”请求,这里没有主动打开 recursive 或 force 选项,也就是不额外要求强制删除或递归删除。请求成功后不返回具体内容,只表示删除动作完成。

调用关系:这是上层要清理文件系统对象时会调用的入口。它把删除命令交给通用请求流程处理;远程 JSON 参数通过 json! 生成。

调用图:外部调用 1 个(json!)。

AppServerSession::request_fs_path101–134 ↗
async fn request_fs_path(
        &mut self,
        method: &str,
        path: &AppServerPath,
        local_request: impl FnOnce(RequestId, AbsolutePathBuf) -> ClientRequest,
        remote_params:

作用:这是这个文件里的核心共用通道:同一个文件操作请求,它会根据当前服务器是在远端还是在本进程里,选择不同的发送办法。这样建目录、读写文件、删除文件不用各写一套远程/本地分支。

数据流:进去的是方法名、路径、一个能构造本地请求的函数,以及远程请求要用的 JSON 参数。它先生成请求编号。若连接的是远程服务器,就发 JSON-RPC 请求,拿到 JSON 响应后反序列化成调用者要的类型;若是本进程服务器,就先把路径字符串检查并转换成绝对路径,再用 typed request 直接调用本地客户端。出来的是解析好的响应对象,或者带有方法名说明的错误。

调用关系:它是文件系统请求的统一底座,AppServerSession::fs_read_file_path 会调用它来完成真正通信;其他同类文件操作也按同样模式依赖它。它内部会用路径的 as_str 取出文本路径,用 from_absolute_path_checked 确认本地路径安全有效,并用 serde_json::from_value 把远程返回的 JSON 变成强类型结果。

调用图:调用 2 个内部函数(as_str, from_absolute_path_checked);被 1 处调用(fs_read_file_path);外部调用 1 个(from_value)。

tui/src/workspace_command.rs源码 ↗
io_transport后台查询和请求执行期间

这个文件像一个“代跑腿窗口”。TUI 界面有时需要在项目目录里跑一些不需要用户输入的命令,比如查 git 状态、拿 diff、做后台探测。问题是,真正的工作区可能在本机,也可能在远端服务器上;界面如果自己判断会很乱。这里把命令包装成 WorkspaceCommand,里面写清楚要运行的程序和参数、工作目录、环境变量、超时时间、最多收多少输出。然后 AppServerWorkspaceCommandRunner 把它翻译成 app-server 的 command/exec 请求发出去。app-server 是实际执行命令的一端。这个文件还特别区分了两类失败:命令正常跑完但返回非 0,这是普通命令失败;请求没发成、协议出错,才是 WorkspaceCommandError。默认会限制输出大小和运行时间,避免一个后台刷新不小心变成没完没了的大进程。

函数细节10
WorkspaceCommand::new54–63 ↗
fn new(argv: impl IntoIterator<Item = impl Into<String>>) -> Self

作用:创建一个要在工作区里执行的命令,并给它配上比较保守的默认值。调用者只需要先给出程序名和参数,剩下的超时、输出上限等安全设置会自动填好。

数据流:进去的是一串命令参数,比如程序名和后面的参数 → 它把这些内容收成 argv 列表,并设置默认工作目录为空、环境变量为空、超时 5 秒、输出最多 64KB、启用输出限制 → 出来的是一个可继续追加设置的 WorkspaceCommand。

调用关系:这是构造命令的起点。像 run_gh_command、run_git_command、run_probe 这类后台查询会先用它搭出命令雏形,之后可以再接 cwd、env、timeout 等方法补充细节,最后交给执行器去跑。

调用图:被 4 处调用(run_gh_command, run_git_command, run_probe, run_git_command);外部调用 3 个(from_secs, new, into_iter)。

WorkspaceCommand::cwd66–69 ↗
fn cwd(mut self, cwd: impl Into<PathBuf>) -> Self

作用:给命令指定工作目录,也就是让命令在哪个文件夹里运行。比如同样是 git status,在不同目录下结果可能不一样。

数据流:进去的是一个路径 → 它把路径保存到命令的 cwd 字段里 → 出来的是同一个命令对象,但已经带上了指定目录,方便继续链式设置其他选项。

调用关系:它通常接在 WorkspaceCommand::new 后面使用。调用者在知道命令必须从某个子目录运行时会加上它,之后这个目录会被 AppServerWorkspaceCommandRunner::run 放进发给 app-server 的请求里。

调用图:外部调用 1 个(into)。

WorkspaceCommand::env72–75 ↗
fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self

作用:给命令添加或覆盖一个环境变量。环境变量可以理解成运行命令时附带的小纸条,用来告诉程序一些额外设置。

数据流:进去的是变量名和值 → 它把这对内容放进命令的 env 表里,并标记为设置这个变量 → 出来的是更新后的命令对象,可以继续加别的变量或设置。

调用关系:它也是命令准备阶段的小工具,通常在 WorkspaceCommand::new 后使用。最终 AppServerWorkspaceCommandRunner::run 会把这些环境变量一起发给 app-server,让真正执行命令的那边按这些设置运行。

调用图:外部调用 1 个(into)。

WorkspaceCommand::timeout78–81 ↗
fn timeout(mut self, timeout: Duration) -> Self

作用:设置命令最多能跑多久。这样可以防止后台命令卡住,拖慢界面或一直占着资源。

数据流:进去的是一个时间长度 → 它替换命令里原本的默认超时时间 → 出来的是带有新超时限制的 WorkspaceCommand。

调用关系:调用者在知道某个命令可能需要更长或更短时间时会用它。AppServerWorkspaceCommandRunner::run 会把这个时间转换成毫秒,交给 app-server 作为取消命令的依据。

WorkspaceCommand::disable_output_cap84–87 ↗
fn disable_output_cap(mut self) -> Self

作用:关闭输出大小限制,让 app-server 返回完整的 stdout 和 stderr。stdout 是程序正常输出,stderr 是错误或诊断输出。

数据流:进去的是当前命令对象 → 它把 disable_output_cap 标记设为 true → 出来的是一个不再要求截断输出的命令对象。

调用关系:大多数后台探测不该用它,因为输出太大可能浪费资源。但像用户明确要看的完整内容,例如 diff 一类结果,就可以在运行前调用它;之后 AppServerWorkspaceCommandRunner::run 会把“不要限制输出”的要求传给 app-server。

WorkspaceCommandOutput::success103–105 ↗
fn success(&self) -> bool

作用:判断命令是不是成功结束。这里的成功标准很简单:退出码等于 0。

数据流:进去的是已经拿到的命令结果 → 它读取 exit_code → 如果 exit_code 是 0 就返回 true,否则返回 false,不改动任何数据。

调用关系:命令执行完成后,调用者会用它快速判断下一步怎么做。它只解释进程结果,不处理网络请求失败;请求失败会走 WorkspaceCommandError 那条路。

WorkspaceCommandError::new118–122 ↗
fn new(message: impl Into<String>) -> Self

作用:创建一个命令请求错误对象,保存一段给人看的错误信息。这里表示的是请求或传输层面出问题,而不是命令返回了非 0。

数据流:进去的是一段错误文字 → 它把文字存进 message 字段 → 出来的是一个 WorkspaceCommandError,之后可以显示给日志或上层调用者。

调用关系:AppServerWorkspaceCommandRunner::run 在向 app-server 发请求失败时会调用它,把底层错误转换成这个文件定义的统一错误类型,方便其他代码不用认识底层协议错误。

调用图:外部调用 1 个(into)。

WorkspaceCommandError::fmt126–128 ↗
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:决定这个错误被打印出来时长什么样。它直接把内部保存的错误信息写出去。

数据流:进去的是错误对象和一个格式化输出器 → 它读取 message 字段并写入输出器 → 出来的是格式化系统需要的结果,不改变错误本身。

调用关系:这是 Rust 的 Display 显示接口会自动调用的函数。上层如果把 WorkspaceCommandError 转成字符串、写日志或展示错误,最终就会走到这里。

调用图:外部调用 1 个(write_str)。

AppServerWorkspaceCommandRunner::new159–161 ↗
fn new(request_handle: AppServerRequestHandle) -> Self

作用:创建一个真正会把命令发给 app-server 的执行器。它需要拿到 app-server 的请求句柄,也就是一条能向 app-server 发消息的通道。

数据流:进去的是 AppServerRequestHandle → 它把这个句柄保存到结构体里 → 出来的是 AppServerWorkspaceCommandRunner,之后可以用它运行工作区命令。

调用关系:TUI 会话启动或准备运行后台命令时会调用它,把当前会话的 app-server 连接包装成 WorkspaceCommandExecutor。后续真正执行命令时,会调用它的 run 方法。

调用图:被 1 处调用(run)。

AppServerWorkspaceCommandRunner::run170–214 ↗
fn run(
        &self,
        command: WorkspaceCommand,
    ) -> Pin<
        Box<dyn Future<Output = Result<WorkspaceCommandOutput, WorkspaceCommandError>> + Send + '_>,
    >

作用:把 WorkspaceCommand 变成 app-server 能听懂的 command/exec 请求,发过去执行,并把返回结果整理成 WorkspaceCommandOutput。它是这个文件里真正跨到 app-server 那边的地方。

数据流:进去的是一个已经配置好的 WorkspaceCommand → 它把超时时间转成毫秒,整理环境变量,生成一个唯一请求编号,把 argv、cwd、输出限制等信息装进 CommandExecParams,然后通过 request_handle 发给 app-server → 如果请求成功,出来的是退出码、stdout、stderr;如果请求本身失败,出来的是 WorkspaceCommandError。它不会把非 0 退出码当作请求错误。

调用关系:这是 WorkspaceCommandExecutor 这个接口的具体实现。上层代码只管提交命令,不用知道 app-server 是本地嵌入的还是远端的;run 会负责发请求。它把真正的执行工作交给 request_typed,也就是 app-server 客户端的发请求功能,再把 app-server 的响应翻译回 TUI 这边使用的结果类型。

调用图:调用 1 个内部函数(request_typed);外部调用 4 个(pin, String, format!, try_from)。

核心执行基础

这些文件定义共享的执行请求桥接层,以及实际启动和管理命令执行的核心引擎。

core/src/exec.rs源码 ↗
orchestrationrequest handling

可以把这个文件理解成一个安全的“命令执行柜台”。外面递进来一条命令、工作目录、环境变量和权限要求,它先判断该用哪种沙箱(沙箱就是给程序划一块受限制的活动范围),再把命令改造成真正能启动的形式。启动后,它同时盯着三件事:进程有没有结束、有没有超时或被取消、标准输出和错误输出有没有数据。输出会被截断到安全大小,避免一个失控命令把内存塞满;但也能一边运行一边把输出片段发给前端看。Windows 上还要额外处理受限令牌和提权沙箱,因为不同后端能限制的文件读写能力不一样。最后,它把原始退出状态翻译成用户能理解的结果:成功、超时、被沙箱拦住,或普通错误。

函数细节36
windows_sandbox_uses_elevated_backend117–125 ↗
fn windows_sandbox_uses_elevated_backend(
    sandbox_level: WindowsSandboxLevel,
    proxy_enforced: bool,
) -> bool

作用:判断 Windows 执行时是不是必须走“提权沙箱”后端。提权沙箱不是让命令随便干,而是用更强的 Windows 机制来做限制。

数据流:输入 Windows 沙箱级别和网络是否由代理强制控制 → 检查配置是不是 Elevated,或者网络限制是否要求特定身份 → 输出 true 或 false,表示要不要用提权后端。

调用关系:build_exec_request 用它提前决定文件系统限制该怎么准备;exec_windows_sandbox 真正启动 Windows 沙箱前也会再用它确认走哪条执行路径。

调用图:被 2 处调用(build_exec_request, exec_windows_sandbox);外部调用 1 个(matches!)。

select_process_exec_tool_sandbox_type137–150 ↗
fn select_process_exec_tool_sandbox_type(
    file_system_sandbox_policy: &FileSystemSandboxPolicy,
    network_sandbox_policy: NetworkSandboxPolicy,
    windows_sandbox_level: codex_protocol::config_

作用:根据文件系统权限、网络权限和 Windows 设置,选出这次命令该用哪一种沙箱。

数据流:输入文件读写策略、网络策略、Windows 沙箱级别和是否强制网络代理 → 交给 SandboxManager 做统一判断 → 输出一个 SandboxType,也就是具体沙箱类型。

调用关系:build_exec_request 在把命令变成可执行请求前会先调用它;它不直接运行命令,只负责选路。

调用图:调用 1 个内部函数(new);被 1 处调用(build_exec_request)。

ExecExpiration::from182–184 ↗
fn from(timeout_ms: u64) -> Self

作用:把一个毫秒数转换成命令过期规则。外部只要给“多少毫秒后超时”,这里会包装成统一的 ExecExpiration。

数据流:输入一个 u64 毫秒数 → 转成 Duration(时间长度)→ 输出 ExecExpiration::Timeout,表示到点就该停。

调用关系:这是给调用方构造超时配置的便捷入口;后面 consume_output 会真正等待这个规则生效。

调用图:外部调用 2 个(from_millis, Timeout)。

ExecExpiration::wait_with_outcome189–214 ↗
async fn wait_with_outcome(self) -> ExecExpirationOutcome

作用:等待“超时”或“取消”发生,并说明到底是哪一种发生了。取消就是外部主动说“别跑了”。

数据流:输入一个 ExecExpiration → 如果是超时就睡到时间到,如果是取消就等取消信号,如果两者都有就谁先来算谁 → 输出 TimedOut 或 Cancelled。

调用关系:consume_output 在命令运行时调用它;它像一个闹钟,闹钟响了以后 consume_output 决定杀进程还是温和停止。

调用图:被 1 处调用(consume_output);外部调用 3 个(from_millis, select!, sleep)。

ExecExpiration::timeout_ms217–226 ↗
fn timeout_ms(&self) -> Option<u64>

作用:从过期规则里取出超时时间,单位是毫秒。没有超时、只有取消时就返回空。

数据流:输入一个 ExecExpiration → 看它是否包含 timeout → 输出 Some(毫秒数) 或 None。

调用关系:Windows 沙箱启动时需要把超时时间单独传给底层 Windows 代码,所以 exec_windows_sandbox 会用到这个信息。

ExecExpiration::cancellation_token229–237 ↗
fn cancellation_token(&self) -> Option<CancellationToken>

作用:从过期规则里取出取消令牌。取消令牌可以理解成一个共享开关,别人一按,正在等它的任务就知道要停。

数据流:输入一个 ExecExpiration → 如果里面有取消令牌就克隆一份 → 输出 Some(token);如果只有超时就输出 None。

调用关系:主要给 Windows 沙箱路径使用,因为 Windows 底层执行接口把“超时”和“取消”分开接收。

ExecExpiration::with_cancellation239–260 ↗
fn with_cancellation(self, cancellation: CancellationToken) -> Self

作用:给已有的过期规则再加一个取消开关。这样命令既可以按原来的超时停,也可以被新的取消信号提前叫停。

数据流:输入原有 ExecExpiration 和新的 CancellationToken → 如果原来已有取消,就用 cancel_when_either 合并两个取消信号;如果原来只有超时,就变成“超时或取消” → 输出新的 ExecExpiration。

调用关系:调用方在需要把全局取消信号接到某次命令上时会用它;合并取消信号的细活交给 cancel_when_either。

调用图:调用 1 个内部函数(cancel_when_either);外部调用 2 个(from_millis, Cancellation)。

cancel_when_either263–277 ↗
fn cancel_when_either(
    first: CancellationToken,
    second: CancellationToken,
) -> CancellationToken

作用:把两个取消开关合成一个:任意一个被按下,新开关也会被按下。

数据流:输入两个 CancellationToken → 创建一个新的 token,并启动后台任务同时等两个旧 token → 任意一个取消后,新 token 也取消。

调用关系:ExecExpiration::with_cancellation 用它合并取消来源;try_run_zsh_fork 也会用它把不同停止条件接在一起。

调用图:被 2 处调用(with_cancellation, try_run_zsh_fork);外部调用 3 个(new, select!, spawn)。

ExecCapturePolicy::retained_bytes_cap280–285 ↗
fn retained_bytes_cap(self) -> Option<usize>

作用:决定这次命令输出最多在内存里保留多少字节。普通 shell 工具有上限,可信内部工具可以完整保留。

数据流:输入捕获策略 → ShellTool 返回固定字节上限,FullBuffer 返回 None 表示不限制 → 输出可选的大小限制。

调用关系:consume_output 和 Windows 执行路径用它控制 stdout、stderr 和合并输出的内存占用。

调用图:被 1 处调用(consume_output)。

ExecCapturePolicy::io_drain_timeout287–289 ↗
fn io_drain_timeout(self) -> Duration

作用:给“等输出管道读完”设置一个兜底时间。这样即使子进程的孙进程还占着输出管道,也不会把整个程序卡死。

数据流:输入捕获策略 → 当前统一返回 2 秒左右的等待时间 → 输出 Duration。

调用关系:consume_output 在进程结束后等待 stdout/stderr 读取任务时调用它,超过时间就放弃读取并继续收尾。

调用图:被 1 处调用(consume_output);外部调用 1 个(from_millis)。

ExecCapturePolicy::uses_expiration291–296 ↗
fn uses_expiration(self) -> bool

作用:说明这个捕获策略是否启用超时和取消。普通 shell 命令会启用,完整缓冲的内部工具不会用这套过期逻辑。

数据流:输入捕获策略 → ShellTool 输出 true,FullBuffer 输出 false → 告诉执行流程是否需要等待过期事件。

调用关系:consume_output 用它决定是否启动超时/取消等待;exec_windows_sandbox 也用同样逻辑决定是否把超时和取消传给 Windows 沙箱。

调用图:被 1 处调用(consume_output)。

process_exec_tool_call307–327 ↗
async fn process_exec_tool_call(
    params: ExecParams,
    permission_profile: &PermissionProfile,
    sandbox_cwd: &AbsolutePathBuf,
    windows_sandbox_workspace_roots: &[AbsolutePathBuf],
    cod

作用:这是执行工具调用的高层入口。它接收用户想跑的命令,把它变成沙箱执行请求,然后交给统一执行通道。

数据流:输入 ExecParams、权限配置、工作区根目录、平台沙箱设置和可选输出流 → 调用 build_exec_request 生成 ExecRequest → 调用 execute_env 执行 → 输出 ExecToolCallOutput 或错误。

调用关系:测试和命令运行辅助函数会从这里进入;它自己不直接启动进程,而是先构造请求,再交给 sandboxing 模块。

调用图:调用 2 个内部函数(build_exec_request, execute_env);被 5 处调用(run_test_cmd, windows_elevated_enforces_deny_read_and_protects_setup_marker, windows_restricted_token_rejects_exact_and_glob_deny_read_policy, assert_network_blocked, run_cmd_result_with_permission_profile_for_cwd)。

build_exec_request331–438 ↗
fn build_exec_request(
    params: ExecParams,
    permission_profile: &PermissionProfile,
    sandbox_cwd: &AbsolutePathBuf,
    windows_sandbox_workspace_roots: &[AbsolutePathBuf],
    codex_linux_s

作用:把一条“可移植的命令请求”翻译成当前平台真正能执行的沙箱请求。它是命令执行前最重要的准备步骤。

数据流:输入命令、目录、环境变量、权限档案、网络代理和平台设置 → 选择沙箱类型,注入网络环境变量,拆出程序名和参数,调用 SandboxManager 转换命令 → 再计算 Windows 文件系统特殊覆盖规则 → 输出 ExecRequest。

调用关系:process_exec_tool_call 和 exec_one_off_command_inner 会调用它;它会询问 select_process_exec_tool_sandbox_type,并把 Windows 细节交给 resolve_windows_elevated_filesystem_overrides 或 resolve_windows_restricted_token_filesystem_overrides。

调用图:调用 7 个内部函数(resolve_windows_elevated_filesystem_overrides, resolve_windows_restricted_token_filesystem_overrides, select_process_exec_tool_sandbox_type, windows_sandbox_uses_elevated_backend, to_runtime_permissions, new, from_abs_path);被 2 处调用(exec_one_off_command_inner, process_exec_tool_call);外部调用 1 个(debug!)。

execute_exec_request440–494 ↗
async fn execute_exec_request(
    exec_request: ExecRequest,
    stdout_stream: Option<StdoutStream>,
    after_spawn: Option<Box<dyn FnOnce() + Send>>,
) -> Result<ExecToolCallOutput>

作用:真正执行已经准备好的 ExecRequest,并把原始运行结果整理成最终结果。

数据流:输入 ExecRequest、可选实时输出流和可选 after_spawn 回调 → 拆出命令参数,记录开始时间,调用 get_raw_output_result 获取原始退出状态和输出 → 计算耗时,再调用 finalize_exec_result 翻译结果 → 输出 ExecToolCallOutput 或沙箱错误。

调用关系:execute_env、execute_user_shell_command 等统一执行入口会调用它;它位于“请求已准备好”和“最终返回给上层”之间。

调用图:调用 2 个内部函数(finalize_exec_result, get_raw_output_result);被 3 处调用(execute_env, execute_exec_request_with_after_spawn, execute_user_shell_command);外部调用 1 个(now)。

get_raw_output_result497–524 ↗
async fn get_raw_output_result(
    params: ExecParams,
    network_sandbox_policy: NetworkSandboxPolicy,
    stdout_stream: Option<StdoutStream>,
    after_spawn: Option<Box<dyn FnOnce() + Send>>,

作用:按平台和沙箱类型选择实际执行方式。Windows 特定沙箱走专门通道,其它情况走普通进程启动通道。

数据流:输入 ExecParams、网络策略、输出流、回调、沙箱类型和 Windows 附加信息 → 如果在 Windows 且是受限令牌沙箱,就调用 exec_windows_sandbox;否则调用 exec → 输出 RawExecToolCallOutput。

调用关系:execute_exec_request 调用它获取未经最终解释的结果;它像分岔路口,决定命令由哪个执行引擎来跑。

调用图:调用 2 个内部函数(exec, exec_windows_sandbox);被 1 处调用(execute_exec_request)。

extract_create_process_as_user_error_code527–537 ↗
fn extract_create_process_as_user_error_code(err: &str) -> Option<String>

作用:从 Windows 启动失败的错误文本里抠出 CreateProcessAsUserW 的错误码。

数据流:输入一段错误字符串 → 查找固定提示文字,读取后面的数字 → 输出错误码字符串;找不到就输出 None。

调用关系:record_windows_sandbox_spawn_failure 用它提取可统计的错误码,方便后续观察 Windows 沙箱启动失败的原因。

调用图:被 1 处调用(record_windows_sandbox_spawn_failure)。

windowsapps_path_kind540–552 ↗
fn windowsapps_path_kind(path: &str) -> &'static str

作用:粗略判断一个 Windows 程序路径是不是来自 WindowsApps。WindowsApps 里的程序有包、别名等不同来源,启动失败时很有参考价值。

数据流:输入路径字符串 → 转小写并检查几个常见目录片段 → 输出 windowsapps_package、windowsapps_alias、windowsapps_other 或 other。

调用关系:record_windows_sandbox_spawn_failure 用它给失败指标打标签,让遥测数据能区分不同路径类型。

调用图:被 1 处调用(record_windows_sandbox_spawn_failure)。

record_windows_sandbox_spawn_failure555–590 ↗
fn record_windows_sandbox_spawn_failure(
    command_path: Option<&str>,
    windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel,
    err: &str,
)

作用:记录 Windows 沙箱启动失败的统计信息。它不会修复错误,但能让开发者知道失败集中在哪些错误码、路径和沙箱级别。

数据流:输入命令路径、Windows 沙箱级别和错误文本 → 提取错误码,判断程序名、路径类型和级别 → 如果遥测系统可用,就写入一个计数指标 → 不返回业务结果。

调用关系:exec_windows_sandbox 在底层 Windows 启动失败时调用它;它依赖 extract_create_process_as_user_error_code 和 windowsapps_path_kind 做分类。

调用图:调用 2 个内部函数(extract_create_process_as_user_error_code, windowsapps_path_kind);被 1 处调用(exec_windows_sandbox);外部调用 3 个(new, global, matches!)。

exec_windows_sandbox593–747 ↗
async fn exec_windows_sandbox(
    params: ExecParams,
    permission_profile: &PermissionProfile,
    windows_sandbox_policy_cwd: &AbsolutePathBuf,
    windows_sandbox_workspace_roots: &[AbsolutePath

作用:在 Windows 沙箱里运行命令并捕获输出。它处理 Windows 后端选择、文件读写覆盖规则、超时取消和输出截断。

数据流:输入 ExecParams、权限档案、Windows 工作目录和文件系统覆盖规则 → 注入网络环境,拆出超时/取消,准备工作区根目录和 codex_home → 在线程池里调用 Windows 沙箱库执行 → 把 stdout/stderr 截断到策略允许大小,合并输出 → 输出 RawExecToolCallOutput。

调用关系:get_raw_output_result 在 Windows 受限令牌沙箱场景下调用它;它失败时会调用 record_windows_sandbox_spawn_failure,成功后用 aggregate_output 合并输出。

调用图:调用 4 个内部函数(aggregate_output, record_windows_sandbox_spawn_failure, synthetic_exit_status, windows_sandbox_uses_elevated_backend);被 1 处调用(get_raw_output_result);外部调用 8 个(other, format!, Io, clone, spawn_blocking, is_empty, to_vec, vec!)。

finalize_exec_result749–807 ↗
fn finalize_exec_result(
    raw_output_result: std::result::Result<RawExecToolCallOutput, CodexErr>,
    sandbox_type: SandboxType,
    duration: Duration,
) -> Result<ExecToolCallOutput>

作用:把底层原始执行结果翻译成上层能直接使用的结果。这里会判断超时、信号退出和疑似沙箱拒绝。

数据流:输入 RawExecToolCallOutput 或错误、沙箱类型和耗时 → 如果是 Unix 信号退出就识别特殊情况;如果超时就改成标准超时代码;把字节输出转成文本;再检查是否像沙箱拦截 → 输出 ExecToolCallOutput,或返回 Timeout/Denied/Signal 等错误。

调用关系:execute_exec_request 在拿到原始结果后调用它;它会调用 is_likely_sandbox_denied 做“是不是被沙箱挡住”的判断。

调用图:调用 1 个内部函数(is_likely_sandbox_denied);被 1 处调用(execute_exec_request);外部调用 4 个(new, Sandbox, Signal, error!)。

is_likely_sandbox_denied814–869 ↗
fn is_likely_sandbox_denied(
    sandbox_type: SandboxType,
    exec_output: &ExecToolCallOutput,
) -> bool

作用:猜测一次命令失败是不是因为沙箱限制。因为系统无法百分百知道原因,所以这里用退出码和输出文字做保守判断。

数据流:输入沙箱类型和命令输出 → 如果没开沙箱或退出码为 0 就直接否定;否则检查 stdout/stderr/合并输出里有没有 permission denied、sandbox、landlock 等关键词,并排除一些常见非沙箱错误码 → 输出 true 或 false。

调用关系:finalize_exec_result 用它把普通失败升级成 SandboxErr::Denied;其它运行和结果映射代码也会复用这个判断。

调用图:被 4 处调用(finalize_exec_result, run, map_exec_result, check_for_sandbox_denial_with_text)。

append_capped881–888 ↗
fn append_capped(dst: &mut Vec<u8>, src: &[u8], max_bytes: usize)

作用:往缓冲区里追加字节,但绝不超过指定上限。它是防止输出无限占内存的小工具。

数据流:输入目标字节数组、来源字节片段和最大字节数 → 计算还剩多少空间,只复制能放下的部分 → 修改目标数组,不额外返回值。

调用关系:read_output 每读到一块 stdout/stderr 数据时调用它;这样即使继续读管道避免堵塞,内存里也只保留上限以内的内容。

调用图:被 1 处调用(read_output)。

aggregate_output890–932 ↗
fn aggregate_output(
    stdout: &StreamOutput<Vec<u8>>,
    stderr: &StreamOutput<Vec<u8>>,
    max_bytes: Option<usize>,
) -> StreamOutput<Vec<u8>>

作用:把 stdout 和 stderr 合成一份总输出。普通情况下就是前后拼起来;有大小上限时会尽量给错误输出更多空间。

数据流:输入 stdout、stderr 和可选最大字节数 → 如果无限制就直接拼接;如果有限制且总量超限,就按大致 stdout 三分之一、stderr 三分之二的比例截取,并把没用完的额度补给 stdout → 输出合并后的 StreamOutput。

调用关系:consume_output 和 exec_windows_sandbox 都用它生成 aggregated_output,方便上层只看一份综合输出。

调用图:被 2 处调用(consume_output, exec_windows_sandbox);外部调用 1 个(with_capacity)。

exec946–998 ↗
async fn exec(
    params: ExecParams,
    network_sandbox_policy: NetworkSandboxPolicy,
    stdout_stream: Option<StdoutStream>,
    after_spawn: Option<Box<dyn FnOnce() + Send>>,
) -> Result<RawExec

作用:在非 Windows 特殊沙箱路径下启动一个普通子进程。注意它本身不决定沙箱策略,调用它之前命令已经被包装好了。

数据流:输入 ExecParams、网络沙箱策略、实时输出流和 after_spawn 回调 → 注入网络环境变量,拆出程序名和参数,调用 spawn_child_async 启动子进程并重定向输出 → 执行回调,再交给 consume_output 收集结果 → 输出 RawExecToolCallOutput。

调用关系:get_raw_output_result 在常规路径调用它;它把“启动进程”交给 spawn_child_async,把“盯运行和读输出”交给 consume_output。

调用图:调用 2 个内部函数(consume_output, spawn_child_async);被 1 处调用(get_raw_output_result);外部调用 1 个(from)。

permission_profile_supports_windows_restricted_token_sandbox1001–1010 ↗
fn permission_profile_supports_windows_restricted_token_sandbox(
    permission_profile: &PermissionProfile,
) -> bool

作用:判断某个权限档案能不能被 Windows 受限令牌沙箱安全执行。

数据流:输入 PermissionProfile → Managed 档案会检查是否没有全盘写权限;Disabled 和 External 直接认为不支持 → 输出 true 或 false。

调用关系:两个 Windows 文件系统覆盖解析函数都会先用它做安全门槛;不支持时会拒绝运行,避免假装沙箱化其实没保护。

调用图:被 2 处调用(resolve_windows_elevated_filesystem_overrides, resolve_windows_restricted_token_filesystem_overrides)。

unsupported_windows_restricted_token_sandbox_reason1013–1036 ↗
fn unsupported_windows_restricted_token_sandbox_reason(
    sandbox: SandboxType,
    permission_profile: &PermissionProfile,
    sandbox_policy_cwd: &AbsolutePathBuf,
    windows_sandbox_level: Windo

作用:给调用方一个“为什么这个 Windows 沙箱组合不支持”的文字原因。它主要用于提前检查或测试。

数据流:输入沙箱类型、权限档案、策略工作目录和 Windows 沙箱级别 → 根据是否 Elevated 选择对应解析函数 → 如果解析失败就返回错误文字,成功就返回 None。

调用关系:它复用 resolve_windows_elevated_filesystem_overrides 和 resolve_windows_restricted_token_filesystem_overrides 的真实判断,避免检查逻辑和执行逻辑不一致。

调用图:调用 2 个内部函数(resolve_windows_elevated_filesystem_overrides, resolve_windows_restricted_token_filesystem_overrides)。

resolve_windows_restricted_token_filesystem_overrides1038–1172 ↗
fn resolve_windows_restricted_token_filesystem_overrides(
    sandbox: SandboxType,
    permission_profile: &PermissionProfile,
    sandbox_policy_cwd: &AbsolutePathBuf,
    windows_sandbox_level: Win

作用:为 Windows 非提权的受限令牌沙箱计算额外文件限制。这个后端能力有限,所以遇到不能安全执行的策略会直接拒绝。

数据流:输入沙箱类型、权限档案、策略目录和 Windows 沙箱级别 → 如果不是目标后端就返回 None;否则取出运行时文件/网络权限,检查是否支持、是否需要直接运行时强制、是否有读限制或复杂写根 → 能表达的只生成额外 deny-write 路径;不能表达的返回错误原因 → 输出可选 WindowsSandboxFilesystemOverrides。

调用关系:build_exec_request 在准备 Windows 请求时调用它;unsupported_windows_restricted_token_sandbox_reason 也用它解释失败原因;内部会用 normalize_windows_override_path 和 windows_policy_has_root_read_access 做路径与权限判断。

调用图:调用 6 个内部函数(normalize_windows_override_path, permission_profile_display_name, permission_profile_supports_windows_restricted_token_sandbox, windows_policy_has_root_read_access, to_runtime_permissions, as_path);被 2 处调用(build_exec_request, unsupported_windows_restricted_token_sandbox_reason);外部调用 4 个(new, compatibility_sandbox_policy_for_permission_profile, resolve_windows_deny_read_paths, format!)。

normalize_windows_override_path1174–1178 ↗
fn normalize_windows_override_path(path: &Path) -> std::result::Result<PathBuf, String>

作用:把 Windows 覆盖规则里的路径整理成干净的绝对路径。这样路径比较时不会因为多余的点、符号链接样式等细节误判。

数据流:输入 Path → 用 dunce 简化路径,再包装成 AbsolutePathBuf 确认它是绝对路径 → 输出标准化后的 PathBuf,失败则输出错误文字。

调用关系:resolve_windows_restricted_token_filesystem_overrides 在比较可写根和只读子路径时调用它,保证集合比较更可靠。

调用图:调用 1 个内部函数(from_absolute_path);被 1 处调用(resolve_windows_restricted_token_filesystem_overrides);外部调用 1 个(simplified)。

windows_policy_has_root_read_access1180–1188 ↗
fn windows_policy_has_root_read_access(
    file_system_sandbox_policy: &FileSystemSandboxPolicy,
    cwd: &AbsolutePathBuf,
) -> bool

作用:检查当前 Windows 文件策略是否允许从文件系统根部开始读取。根部可读和只开放少数目录,在沙箱配置上差别很大。

数据流:输入文件系统沙箱策略和当前工作目录 → 找到工作目录所在路径的根,再询问策略是否能读这个根 → 输出 true 或 false。

调用关系:受限令牌和提权两种 Windows 覆盖解析都会用它判断是否需要特殊 read_roots_override,或者是否必须拒绝执行。

调用图:调用 2 个内部函数(can_read_path_with_cwd, as_path);被 2 处调用(resolve_windows_elevated_filesystem_overrides, resolve_windows_restricted_token_filesystem_overrides)。

resolve_windows_elevated_filesystem_overrides1190–1317 ↗
fn resolve_windows_elevated_filesystem_overrides(
    sandbox: SandboxType,
    permission_profile: &PermissionProfile,
    sandbox_policy_cwd: &AbsolutePathBuf,
    use_windows_elevated_backend: bool

作用:为 Windows 提权沙箱计算更完整的文件读写覆盖规则。提权后端能力更强,可以表达读根、写根和额外拒绝路径。

数据流:输入沙箱类型、权限档案、策略目录和是否使用提权后端 → 如果不适用就返回 None;否则检查权限档案,计算 deny-read、可读根、可写根和额外 deny-write;遇到无法安全表达的“只读洞里又重新开放写入”会拒绝 → 输出可选 WindowsSandboxFilesystemOverrides。

调用关系:build_exec_request 在需要 Windows 提权后端时调用它;unsupported_windows_restricted_token_sandbox_reason 也用它生成不支持原因;它会调用 has_reopened_writable_descendant 等辅助检查。

调用图:调用 6 个内部函数(has_reopened_writable_descendant, permission_profile_display_name, permission_profile_supports_windows_restricted_token_sandbox, windows_policy_has_root_read_access, to_runtime_permissions, as_path);被 2 处调用(build_exec_request, unsupported_windows_restricted_token_sandbox_reason);外部调用 5 个(new, new, compatibility_sandbox_policy_for_permission_profile, resolve_windows_deny_read_paths, format!)。

permission_profile_display_name1319–1325 ↗
fn permission_profile_display_name(permission_profile: &PermissionProfile) -> &'static str

作用:把权限档案类型变成简短名字,方便写进错误信息。

数据流:输入 PermissionProfile → 匹配 Managed、Disabled 或 External → 输出对应的静态字符串。

调用关系:两个 Windows 文件系统覆盖解析函数在拒绝执行时调用它,让错误提示更清楚。

调用图:被 2 处调用(resolve_windows_elevated_filesystem_overrides, resolve_windows_restricted_token_filesystem_overrides)。

has_reopened_writable_descendant1327–1344 ↗
fn has_reopened_writable_descendant(
    writable_roots: &[codex_protocol::protocol::WritableRoot],
) -> bool

作用:检查是否存在一种复杂情况:某个目录被标成只读,但它下面的子目录又被重新开放可写。

数据流:输入可写根列表 → 遍历每个可写根的只读子路径,再看是否有别的可写根落在这个只读子路径下面 → 找到就输出 true,否则 false。

调用关系:resolve_windows_elevated_filesystem_overrides 用它判断策略能不能被 Windows 提权沙箱安全表达;不能表达时会拒绝运行。

调用图:被 1 处调用(resolve_windows_elevated_filesystem_overrides);外部调用 1 个(iter)。

consume_output1348–1485 ↗
async fn consume_output(
    mut child: Child,
    expiration: ExecExpiration,
    capture_policy: ExecCapturePolicy,
    stdout_stream: Option<StdoutStream>,
) -> Result<RawExecToolCallOutput>

作用:盯着子进程运行,并安全地收集 stdout 和 stderr。它同时处理正常退出、超时、取消、Ctrl-C 和输出管道卡住。

数据流:输入 Child 子进程、过期规则、捕获策略和可选实时输出流 → 取出 stdout/stderr 管道,启动两个 read_output 任务读取;同时等待进程结束、超时/取消或 Ctrl-C;必要时终止进程组;最后等待输出读取任务但设置排水超时 → 合并输出并返回 RawExecToolCallOutput。

调用关系:exec 启动子进程后立刻把它交给 consume_output;consume_output 再调用 read_output、aggregate_output,并依赖 ExecExpiration 和 ExecCapturePolicy 的小方法控制行为。

调用图:调用 6 个内部函数(io_drain_timeout, retained_bytes_cap, uses_expiration, wait_with_outcome, aggregate_output, read_output);被 1 处调用(exec);外部调用 4 个(new, pin!, select!, spawn)。

read_output1487–1541 ↗
async fn read_output(
    mut reader: R,
    stream: Option<StdoutStream>,
    is_stderr: bool,
    max_bytes: Option<usize>,
) -> io::Result<StreamOutput<Vec<u8>>>

作用:持续读取一条输出流,也就是 stdout 或 stderr。它既保存输出,也可以把实时片段发成事件给前端。

数据流:输入异步读取器、可选 StdoutStream、是否 stderr、最大保留字节数 → 循环读取固定大小块;如果有实时流且没超过事件数量上限,就发送 ExecCommandOutputDelta 事件;同时把内容追加到本地缓冲,必要时用 append_capped 截断保存 → 输出 StreamOutput<Vec<u8>>。

调用关系:consume_output 会分别为 stdout 和 stderr 启动一个 read_output 任务;它是实时输出和最终输出的共同来源。

调用图:调用 1 个内部函数(append_capped);被 1 处调用(consume_output);外部调用 3 个(read, with_capacity, ExecCommandOutputDelta)。

synthetic_exit_status1556–1561 ↗
fn synthetic_exit_status(code: i32) -> ExitStatus

作用:构造一个假的 ExitStatus,用来表示超时、被杀掉或 Windows 沙箱库返回的退出码。ExitStatus 是系统表示进程退出状态的类型。

数据流:输入一个整数退出码或原始状态码 → 按当前平台的规则转换成 ExitStatus → 输出这个合成的退出状态。

调用关系:exec_windows_sandbox 用它把 Windows 沙箱捕获结果接到统一流程里;consume_output 在超时或 Ctrl-C 时也需要类似的合成状态。

调用图:被 2 处调用(exec_windows_sandbox, synthetic_exit_status_for_code);外部调用 1 个(from_raw)。

synthetic_exit_status_for_code1564–1566 ↗
fn synthetic_exit_status_for_code(code: i32) -> ExitStatus

作用:按“普通退出码”的语义构造 ExitStatus。它和 synthetic_exit_status 的区别主要体现在 Unix 上原始状态位的编码方式。

数据流:输入普通退出码 → Unix 上会左移成系统 raw status,Windows 上直接复用 synthetic_exit_status → 输出 ExitStatus。

调用关系:consume_output 在取消命令后用它表示一个普通的失败退出;在 Windows 实现里它会转交给 synthetic_exit_status。

调用图:调用 1 个内部函数(synthetic_exit_status);外部调用 1 个(from_raw)。

core/src/sandboxing/mod.rs源码 ↗
orchestrationrequest handling

这个文件夹在“沙箱规则”和“真正执行命令”之间。沙箱可以理解成给程序套上的安全围栏,限制它能看哪些文件、能不能联网。别的模块会先决定该用什么沙箱、怎么改造命令;这里负责把这些结果整理成 ExecRequest,也就是一次执行所需的完整说明书。ExecRequest 里有命令、工作目录、环境变量、超时设置、输出收集方式、网络代理、Windows 沙箱选项、权限配置等。它还会做一些关键补充,比如发现网络被禁用时,往环境变量里塞一个标记,让子进程知道自己处在“不能联网”的环境里。最后,execute_env 和 execute_exec_request_with_after_spawn 只是薄薄的转交层,把整理好的请求交给真正的执行函数 execute_exec_request。

函数细节4
ExecRequest::new65–101 ↗
fn new(
        command: Vec<String>,
        cwd: AbsolutePathBuf,
        env: HashMap<String, String>,
        network: Option<NetworkProxy>,
        expiration: ExecExpiration,
        capture_pol

作用:创建一份新的执行请求,把命令、目录、环境、权限、超时等信息装进同一个对象里。调用者用它来明确告诉系统:这次命令应该在什么限制下运行。

数据流:进去的是命令列表、当前目录、环境变量、网络设置、过期时间、输出捕获方式、沙箱类型、Windows 沙箱参数、权限配置和可选的 arg0。它会复制当前目录作为 Windows 沙箱的策略目录,并把权限配置转换成运行时真正会用的文件系统权限和网络权限。出来的是一个完整的 ExecRequest;它不会启动进程,只是把所有执行所需的信息准备好。

调用关系:它是手动组装执行请求的入口。测试和一些 Windows 沙箱相关流程会直接用它,例如 cancellation_expiration_keeps_process_alive_until_terminated、timeout_or_cancellation_reports_cancellation_without_timeout_exit_code、windows_sandbox_exec_request 和 test_exec_request。它内部会调用 to_runtime_permissions,把比较抽象的权限档案变成执行时可检查的具体规则。

调用图:调用 1 个内部函数(to_runtime_permissions);被 4 处调用(cancellation_expiration_keeps_process_alive_until_terminated, timeout_or_cancellation_reports_cancellation_without_timeout_exit_code, windows_sandbox_exec_request, test_exec_request);外部调用 1 个(clone)。

ExecRequest::from_sandbox_exec_request103–155 ↗
fn from_sandbox_exec_request(
        request: SandboxExecRequest,
        options: ExecOptions,
        windows_sandbox_workspace_roots: Vec<AbsolutePathBuf>,
    ) -> Self

作用:把沙箱模块已经处理过的请求,转换成核心执行器能直接使用的 ExecRequest。它像把外部门禁系统给出的通行单,改写成本楼执行部门能读懂的格式。

数据流:进去的是 SandboxExecRequest、执行选项 ExecOptions,以及 Windows 沙箱的工作区根目录列表。它拆开沙箱请求,取出命令、目录、环境变量、网络、沙箱类型、权限策略等信息,再补上超时和输出捕获设置。如果网络沙箱策略表示不能联网,它会在环境变量里加入禁网标记;在 macOS 且使用 Seatbelt 沙箱时,还会加入对应的沙箱标记。出来的是一份可以交给执行器的 ExecRequest。

调用关系:它主要服务于沙箱准备流程。prepare_sandboxed_exec 会在选好沙箱和改造好命令后调用它,把结果转成核心执行请求;env_for 也会用它来得到执行环境。它不负责决定沙箱策略,只负责把已经决定好的内容安全、完整地搬到执行请求里。

调用图:被 2 处调用(prepare_sandboxed_exec, env_for)。

execute_env158–163 ↗
async fn execute_env(
    exec_request: ExecRequest,
    stdout_stream: Option<StdoutStream>,
) -> codex_protocol::error::Result<ExecToolCallOutput>

作用:执行一份 ExecRequest,并可选择把标准输出实时传出去。它是常用的简化入口:调用者不用关心额外的进程启动回调,只要把请求交进来即可。

数据流:进去的是 ExecRequest 和可选的 StdoutStream,StdoutStream 可以理解成一条把程序输出往外送的管道。它不改写请求,只是把请求、输出管道和一个空的 after_spawn 回调交给 execute_exec_request。出来的是执行结果 ExecToolCallOutput,里面包含命令运行后的输出、状态等信息;如果失败,则返回错误。

调用关系:它是上层运行流程调用执行器的便捷通道。start、process_exec_tool_call 和 run 会在需要真正跑命令时调用它。它自己不做底层执行,而是把活交给 execute_exec_request。

调用图:调用 1 个内部函数(execute_exec_request);被 3 处调用(start, process_exec_tool_call, run)。

execute_exec_request_with_after_spawn165–171 ↗
async fn execute_exec_request_with_after_spawn(
    exec_request: ExecRequest,
    stdout_stream: Option<StdoutStream>,
    after_spawn: Option<Box<dyn FnOnce() + Send>>,
) -> codex_protocol::error::R

作用:执行一份 ExecRequest,并允许调用者在子进程刚启动后立刻做一件额外的事。这个“after_spawn”回调适合放一些必须等进程出生后才能做的收尾或记录动作。

数据流:进去的是 ExecRequest、可选的输出流,以及可选的 after_spawn 函数。它把这三样原封不动交给 execute_exec_request。执行完成后,出来的是 ExecToolCallOutput 或错误;实际的进程启动、等待、输出收集都由下层函数完成。

调用关系:它给 run 这类更复杂的流程使用,因为这些流程有时需要在进程启动后插入一个额外动作。它和 execute_env 的区别是多传了 after_spawn;真正干活的仍然是 execute_exec_request。

调用图:调用 1 个内部函数(execute_exec_request);被 1 处调用(run)。

Shell 任务与共享 Shell 编排

这些文件涵盖直接用户 shell 任务,以及用于将类 shell 工具请求转换为已批准执行的通用处理器和运行时路径。

core/src/tasks/user_shell.rs源码 ↗
orchestrationrequest handling

这个文件解决的是一个很实际的问题:用户想绕过模型,自己在项目目录里跑一条命令时,系统要安全、清楚、可追踪地执行它。它先确认当前会话有没有可用的 shell,也就是命令解释器;再准备工作目录、环境变量、PATH 路径和可选的 shell 快照。然后它发出“命令开始”的事件,调用底层执行器真正启动进程,并持续把输出流给客户端。命令结束后,它会发“命令结束”事件,带上退出码、标准输出、错误输出和格式化后的结果。如果用户取消、环境不可用或执行器出错,它也会转成明确的错误事件。一个重要点是:/shell 被当作用户主动要求的“全权限出口”,所以这里不套普通沙箱,也会去掉系统托管代理,避免命令偷偷继承受控网络设置。

函数细节9
UserShellCommandTask::new65–67 ↗
fn new(command: String) -> Self

作用:创建一个“用户 shell 命令任务”。别人只要把用户输入的命令字符串交给它,它就包装成后面可以排队执行的任务。

数据流:进去的是一段命令文本 → 函数把它存进 UserShellCommandTask 的 command 字段 → 出来的是一个新的任务对象,还没有真正执行命令。

调用关系:它会被 run_user_shell_command 用来把用户请求变成任务。之后任务系统会调用这个对象的 UserShellCommandTask::run,才进入真正执行命令的流程。

调用图:被 1 处调用(run_user_shell_command)。

UserShellCommandTask::kind71–73 ↗
fn kind(&self) -> TaskKind

作用:告诉任务系统:这个任务是普通任务。这样调度器知道它应该按常规会话任务来处理。

数据流:不需要额外输入,只读取这个任务本身的类型信息 → 返回 TaskKind::Regular → 不改动任何状态。

调用关系:它是 SessionTask 接口的一部分。任务调度器在安排或分类任务时会问它“你是哪类任务”,它用这个答案配合整个任务生命周期。

UserShellCommandTask::span_name75–77 ↗
fn span_name(&self) -> &'static str

作用:给这类任务起一个固定的追踪名字。追踪名字可以理解成日志和性能记录里的标签,方便排查“这段时间在跑什么”。

数据流:不读取外部数据 → 直接返回字符串 session_task.user_shell → 不改动会话或命令。

调用关系:它同样属于 SessionTask 接口。运行框架在记录 tracing(追踪日志,用来串起一次操作的前因后果)时会用这个名字标记 shell 任务。

UserShellCommandTask::run79–95 ↗
async fn run(
        self: Arc<Self>,
        session: Arc<SessionTaskContext>,
        turn_context: Arc<TurnContext>,
        _input: Vec<TurnInput>,
        cancellation_token: CancellationToken,

作用:这是任务真正开始干活的入口。它把保存好的命令交给公共执行函数,并说明这是一个独立的 shell 回合。

数据流:进去的是当前会话、当前回合上下文、输入列表和取消令牌(一种通知任务停止的开关)→ 它取出自己的 command,调用 execute_user_shell_command 执行 → 最后返回 None,表示这个任务本身不额外产出一段文字结果。

调用关系:任务系统调度到 UserShellCommandTask 时会调用它。它自己不展开执行细节,而是把会话、上下文、命令和取消控制都交给 execute_user_shell_command。

调用图:调用 1 个内部函数(execute_user_shell_command)。

execute_user_shell_command98–358 ↗
async fn execute_user_shell_command(
    session: Arc<Session>,
    turn_context: Arc<TurnContext>,
    command: String,
    cancellation_token: CancellationToken,
    mode: UserShellCommandMode,
)

作用:这是本文件的核心流程:把用户命令放进当前 shell 环境执行,并把全过程同步给客户端和会话历史。它处理正常完成、失败、取消、环境不可用等各种情况。

数据流:进去的是会话、回合上下文、原始命令、取消令牌和运行模式 → 它记录遥测计数,必要时发送 TurnStarted;检查本地 shell 和工作目录;用 create_env 生成环境变量;去掉托管代理;用 prepare_user_shell_exec_command 准备最终启动命令;发送 ExecCommandBegin;调用 execute_exec_request 真正跑进程并串流输出 → 出来没有返回值,但会发送开始/结束/错误事件,并通过 persist_user_shell_output 把结果写进会话记录或注入当前回合。

调用关系:UserShellCommandTask::run 会把活交给它。它像总指挥一样串起 create_env、prepare_user_shell_exec_command、execute_exec_request、format_exec_output_str、persist_user_shell_output 和 send_user_shell_error;底层执行完成后,它决定该发成功事件、失败事件还是取消事件。

调用图:调用 10 个内部函数(execute_exec_request, create_env, persist_user_shell_output, prepare_user_shell_exec_command, send_user_shell_error, format_exec_output_str, strip_managed_proxy_env, now_unix_timestamp_ms, new, parse_command);被 1 处调用(run);外部调用 7 个(new, new_v4, error!, format!, ExecCommandBegin, ExecCommandEnd, TurnStarted)。

send_user_shell_error360–370 ↗
async fn send_user_shell_error(session: &Session, turn_context: &TurnContext, message: &str)

作用:把 shell 命令无法开始运行时的错误告诉客户端。比如当前会话没有 shell,或者工作目录不能映射到本机路径。

数据流:进去的是会话、回合上下文和一段错误文字 → 它把文字包装成 ErrorEvent → 通过 session.send_event 发出去;没有返回业务结果,也不执行命令。

调用关系:execute_user_shell_command 在前置检查失败时会调用它。它负责把“还没跑起来就失败了”的情况转成统一的错误事件,避免主流程里到处重复写发送错误的代码。

调用图:被 1 处调用(execute_user_shell_command);外部调用 2 个(send_event, Error)。

prepare_user_shell_exec_command372–405 ↗
fn prepare_user_shell_exec_command(
    display_command: &[String],
    shell: &Shell,
    shell_snapshot: Option<&AbsolutePathBuf>,
    shell_environment_set: &HashMap<String, String>,
    exec_env_m

作用:准备最终要交给操作系统启动的命令数组。它会根据系统类型处理 PATH 和 shell 快照,让命令尽量在用户期待的 shell 环境里运行。

数据流:进去的是展示用命令、shell 类型、可选的 shell 快照路径、用户显式设置的环境变量、以及可修改的执行环境变量表 → 在 Unix 系统上交给 prepare_user_shell_exec_command_with_path_prepend;在非 Unix 系统上直接调用 maybe_wrap_shell_lc_with_snapshot → 出来的是最终启动进程用的命令参数列表,同时可能改动 exec_env_map 里的环境变量。

调用关系:execute_user_shell_command 在真正调用 execute_exec_request 之前会调用它。它是“命令文字”和“可执行进程请求”之间的适配层,必要时把工作交给 Unix 专用的 prepare_user_shell_exec_command_with_path_prepend 或通用的 shell 快照包装函数。

调用图:调用 2 个内部函数(prepare_user_shell_exec_command_with_path_prepend, maybe_wrap_shell_lc_with_snapshot);被 1 处调用(execute_user_shell_command);外部调用 1 个(default)。

prepare_user_shell_exec_command_with_path_prepend413–432 ↗
fn prepare_user_shell_exec_command_with_path_prepend(
    display_command: &[String],
    shell: &Shell,
    shell_snapshot: Option<&AbsolutePathBuf>,
    shell_environment_set: &HashMap<String, Strin

作用:这是 Unix 系统专用的命令准备步骤,重点是先加入运行时自己需要的 PATH 路径,再考虑 shell 快照。PATH 可以理解成系统找可执行文件时会搜索的目录清单。

数据流:进去的是命令、shell、可选快照、用户设置的环境变量、可修改的环境变量表,以及一个负责追加运行时 PATH 的回调函数 → 它复制用户显式环境设置,创建 RuntimePathPrepends 记录运行时加过哪些路径,调用回调修改环境,再调用 maybe_wrap_shell_lc_with_snapshot 生成最终命令 → 出来的是最终命令参数列表,环境变量表也可能已经带上新的 PATH。

调用关系:prepare_user_shell_exec_command 在 Unix 平台会调用它。它再把结果交给 maybe_wrap_shell_lc_with_snapshot,是为了在恢复用户 shell 快照后,仍然能把运行时必须的路径补回去。

调用图:调用 1 个内部函数(maybe_wrap_shell_lc_with_snapshot);被 1 处调用(prepare_user_shell_exec_command);外部调用 1 个(default)。

persist_user_shell_output434–456 ↗
async fn persist_user_shell_output(
    session: &Session,
    turn_context: &TurnContext,
    raw_command: &str,
    exec_output: &ExecToolCallOutput,
    mode: UserShellCommandMode,
)

作用:把用户 shell 命令的结果保存到会话里,让之后的上下文能知道用户刚刚跑了什么、结果是什么。不同运行模式下,它保存的位置不一样。

数据流:进去的是会话、回合上下文、原始命令、执行输出和运行模式 → 它先用 user_shell_command_record_item 把命令和输出做成一条会话记录 → 如果是独立回合,就 record_conversation_items 写入历史并 ensure_rollout_materialized 确保持久化材料生成;如果是已有回合里的辅助命令,就 inject_no_new_turn 注入当前回合但不新开一个回合。

调用关系:execute_user_shell_command 在命令成功、失败或被取消后都会调用它。它把一次临时的进程输出变成会话系统能长期理解的记录,同时避免辅助模式错误地产生新的回合边界。

调用图:调用 1 个内部函数(user_shell_command_record_item);被 1 处调用(execute_user_shell_command);外部调用 5 个(ensure_rollout_materialized, inject_no_new_turn, record_conversation_items, from_ref, vec!)。

core/src/tools/handlers/shell.rs源码 ↗
orchestrationrequest handling

这个文件解决的是一个很现实的问题:AI 不能想跑什么系统命令就直接跑,否则可能删文件、访问网络、改系统设置,风险很大。它就像命令执行前的“安检口”。先从工具调用里读出命令,再检查当前会话有没有可用的运行环境;接着处理权限,比如沙箱权限(把命令关在一个受限制的环境里)、额外权限、用户是否已经批准;如果命令其实是在打补丁,还会优先走专门的补丁流程,而不是当普通 shell 命令跑。确认可以继续后,它会发出“开始执行”的事件,准备好运行请求,交给 ShellRuntime 真正执行,最后再发出“执行结束”的事件,并把结果整理成工具输出返回给模型。没有这个文件,shell 工具就缺少统一的安全检查、审批、事件记录和执行调度,命令执行会更乱也更危险。

函数细节2
shell_command_payload_command35–43 ↗
fn shell_command_payload_command(payload: &ToolPayload) -> Option<String>

作用:这个函数从一次工具调用的载荷里,尝试取出用户或模型要执行的 shell 命令。它只做一件小事:如果这确实是函数调用,并且参数格式对,就把里面的 command 字段拿出来。

数据流:输入是一份 ToolPayload,也就是工具调用带来的原始数据。它先确认这份数据是不是“函数调用”类型;如果不是,就返回空。然后它把 arguments 按 ShellCommandToolCallParams 这个格式解析,解析成功就取出 command 字符串,解析失败也返回空。最后输出的是 Option<String>:有命令就给出命令,没有就给出 None。

调用关系:它处在 shell 工具流程的最前面,适合在需要快速判断“这次工具调用到底想跑什么命令”时使用。它会把参数解析工作交给 parse_arguments;如果解析成功,后面的执行流程才可能拿到真正的命令文本。

run_exec_like60–238 ↗
async fn run_exec_like(args: RunExecLikeArgs) -> Result<FunctionToolOutput, FunctionCallError>

作用:这个函数是执行 shell 类命令的主流程。它负责把“我要运行这条命令”变成一次完整的、安全受控的执行:检查环境、处理权限、拦截特殊补丁命令、发事件、请求审批、调用运行器、整理结果。

数据流:输入是一包 RunExecLikeArgs,里面有命令参数、取消令牌、会话、当前回合、权限信息、调用编号、shell 后端等。它先取当前可用的运行环境;没有环境就直接返回错误,告诉模型这个会话不能用 shell。然后它读取环境变量策略和功能开关,合并本回合已经批准过的权限,并把额外权限规范化,确保命令不会绕过审批规则。如果命令申请了不该申请的提权,它会拒绝。接着它检查这条命令是不是 apply_patch;如果是,就交给补丁专用流程并直接返回。否则它创建 shell 执行事件,向执行策略服务询问这条命令是否需要审批,再组装 ShellRequest。之后它创建 ToolOrchestrator 和 ShellRuntime,把请求交给运行器执行。执行完成后,它把输出格式化成模型能读的内容,发送结束事件,并返回 FunctionToolOutput。

调用关系:它是这个文件的核心调度函数。它会调用 apply_granted_turn_permissions 来应用本回合已批准的权限,调用 implicit_granted_permissions 和 normalize_and_validate_additional_permissions 来整理额外权限,调用 intercept_apply_patch 来把补丁命令分流到专门处理器。普通 shell 命令则由 ToolOrchestrator 驱动 ShellRuntime::for_shell_command 创建出的运行时来执行。ToolEmitter::shell 负责在执行前后发事件,让外部能看到命令开始、结束和结果。

调用图:调用 7 个内部函数(shell, new, apply_granted_turn_permissions, intercept_apply_patch, implicit_granted_permissions, new, for_shell_command);外部调用 4 个(format!, matches!, RespondToModel, vec!)。

core/src/tools/runtimes/shell.rs源码 ↗
orchestrationrequest handling

可以把这个文件想成一个“命令执行安检口”。外部想跑一条 shell 命令时,不是直接丢给系统跑,而是先包装成 ShellRequest,里面有命令、当前目录、环境变量、权限、是否需要联网、超时时间等信息。ShellRuntime 会先判断这条命令是否需要用户或守护服务批准,并用 ApprovalKey 避免同样的请求反复打扰用户。真正执行时,它会根据沙箱权限整理环境变量,必要时给命令加上 shell 快照、PowerShell 的特殊处理,Unix 上还可能尝试 zsh-fork 后端;条件不满足就退回普通执行。最后它把命令交给沙箱执行,并把输出流回会话。这个文件重要,是因为 shell 命令能力很强,既能帮用户完成事,也可能破坏文件或访问网络,所以这里集中做“能不能跑、怎么安全地跑”。

函数细节11
ShellRuntime::for_shell_command103–105 ↗
fn for_shell_command(backend: ShellRuntimeBackend) -> Self

作用:创建一个给 shell_command 工具使用的 ShellRuntime,并指定它走哪种后端。后端可以是传统执行方式,也可以是 Unix 上优先尝试 zsh-fork 的方式。

数据流:进去的是一个 ShellRuntimeBackend,表示想用哪种执行路线;函数把它放进 ShellRuntime 结构里;出来的是一个可以执行 shell 请求的运行器实例。

调用关系:run_exec_like 会在准备执行类似命令行的工具时调用它,先把运行器造出来。之后真正的审批和执行,会由这个运行器的其他方法继续完成。

调用图:被 1 处调用(run_exec_like)。

ShellRuntime::stdout_stream107–113 ↗
fn stdout_stream(ctx: &ToolCtx) -> Option<crate::exec::StdoutStream>

作用:准备一个“标准输出流”的通道,让正在运行的命令可以把输出实时发回前端或会话。标准输出就是命令正常打印出来的文字。

数据流:进去的是 ToolCtx,里面有当前回合编号、调用编号和事件发送通道;函数把这些信息装成 StdoutStream;出来的是一个可选的输出流配置,供执行命令时使用。

调用关系:它是 ShellRuntime 内部的小帮手。run 在真正调用 execute_env 执行命令时,会用它把命令输出接到当前这次工具调用上。

ShellRuntime::sandbox_preference117–119 ↗
fn sandbox_preference(&self) -> SandboxablePreference

作用:告诉外层系统:这个运行器倾向于自动决定是否使用沙箱。沙箱可以理解成一个隔离房间,命令在里面运行,不能随便碰外面的东西。

数据流:不需要额外输入;函数直接返回 SandboxablePreference::Auto;结果是外层调度器会按当前配置和权限自动选择合适的沙箱方式。

调用关系:这是 Sandboxable 接口的一部分。外层在安排工具执行环境时会问它的沙箱偏好,然后再决定后续 SandboxAttempt 怎么创建。

ShellRuntime::escalate_on_failure120–122 ↗
fn escalate_on_failure(&self) -> bool

作用:告诉外层系统:如果命令因为权限太低失败,可以尝试升级权限后再跑。这里的“升级”不是无限制放开,而是走审批和更高权限的沙箱流程。

数据流:不读取请求内容;函数直接返回 true;外层据此知道失败后可以进入“申请更多权限再试一次”的路线。

调用关系:这是 Sandboxable 接口的一部分。它配合审批逻辑使用,让命令在被沙箱拦住时,不是直接结束,而是有机会解释原因并请求许可。

ShellRuntime::approval_keys128–135 ↗
fn approval_keys(&self, req: &ShellRequest) -> Vec<Self::ApprovalKey>

作用:为一次 shell 请求生成“审批缓存钥匙”。如果同样的命令、目录和权限之前已经批过,就可以识别出来,避免重复弹审批。

数据流:进去的是 ShellRequest;函数先把命令规范化,减少同义写法造成的重复,再连同当前目录、沙箱权限、额外权限组成 ApprovalKey;出来的是一个 ApprovalKey 列表。

调用关系:start_approval_async 会先调用它拿到缓存钥匙,然后交给 with_cached_approval 判断是否已有批准记录。它是审批流程里“判断是不是同一件事”的步骤。

调用图:被 1 处调用(start_approval_async);外部调用 1 个(vec!)。

ShellRuntime::start_approval_async137–190 ↗
fn start_approval_async(
        &'a mut self,
        req: &'a ShellRequest,
        ctx: ApprovalCtx<'a>,
    ) -> BoxFuture<'a, ReviewDecision>

作用:启动一次异步审批,也就是命令真正执行前,去问用户或守护服务能不能跑。异步的意思是它可以等待外部回应,不会卡死整个程序。

数据流:进去的是 ShellRequest 和 ApprovalCtx,里面有命令、目录、理由、会话、调用编号等;函数先生成审批钥匙,再决定是走 Guardian 守护审批,还是走普通会话审批,并用缓存避免重复询问;出来的是 ReviewDecision,表示批准、拒绝或其他审批结果。

调用关系:它先调用 approval_keys 准备缓存标识;如果已有 guardian_review_id,就把请求交给 review_approval_request;否则用 with_cached_approval 包住普通的 request_command_approval。它位于命令执行前的安全闸门。

调用图:调用 2 个内部函数(approval_keys, with_cached_approval);外部调用 2 个(pin, review_approval_request)。

ShellRuntime::exec_approval_requirement192–194 ↗
fn exec_approval_requirement(&self, req: &ShellRequest) -> Option<ExecApprovalRequirement>

作用:把这次请求自带的“执行审批要求”交给外层。这样外层知道这条命令按策略到底需不需要审批,或者需要哪种级别的审批。

数据流:进去的是 ShellRequest;函数读取 req.exec_approval_requirement 并复制出来;出来的是一个可选的 ExecApprovalRequirement。

调用关系:这是 Approvable 接口的一部分。审批框架会调用它,结合系统策略决定是否进入 start_approval_async。

ShellRuntime::permission_request_payload196–201 ↗
fn permission_request_payload(&self, req: &ShellRequest) -> Option<PermissionRequestPayload>

作用:生成给权限申请系统看的说明材料,告诉它这次是一个 bash 风格的命令请求,以及用户或模型给出的理由。

数据流:进去的是 ShellRequest;函数取出 hook_command 和 justification,并用 PermissionRequestPayload::bash 包成统一格式;出来的是一个权限申请载荷。

调用关系:这是 Approvable 接口的一部分。需要更高权限时,外层会用它把“要跑什么、为什么要跑”说明清楚,方便审批方判断。

调用图:调用 1 个内部函数(bash)。

ShellRuntime::sandbox_permissions203–205 ↗
fn sandbox_permissions(&self, req: &ShellRequest) -> SandboxPermissions

作用:告诉外层这次 shell 请求希望拥有哪组沙箱权限。比如能读哪些文件、能不能写、是否有更宽的访问范围。

数据流:进去的是 ShellRequest;函数直接取出 req.sandbox_permissions;出来的是 SandboxPermissions,供沙箱和审批逻辑继续使用。

调用关系:这是 Approvable 接口的一部分。外层在检查权限、构造 SandboxAttempt 或展示审批信息时,会向运行器询问这份权限要求。

ShellRuntime::network_approval_spec209–236 ↗
fn network_approval_spec(
        &self,
        req: &ShellRequest,
        ctx: &ToolCtx,
    ) -> Option<NetworkApprovalSpec>

作用:如果这条命令可能需要受控网络访问,这个函数会生成一份“网络审批说明”。网络审批说明会告诉系统:谁要联网、用什么命令、在哪个目录、为什么。

数据流:进去的是 ShellRequest 和 ToolCtx;函数先结合当前文件沙箱策略修正权限,保留被拒绝读取的限制,再判断是否存在可管理的网络代理;如果需要,就把命令、目录、工具名、权限、理由等打包成 NetworkApprovalSpec;如果不需要或无法管理,就返回空。

调用关系:它会调用 sandbox_permissions_preserving_denied_reads 保留文件限制,调用 managed_network_for_sandbox_permissions 判断网络是否能被管控,还会用 flat_tool_name 生成易读的工具名。它在执行前为网络访问准备单独的审批触发器。

调用图:调用 3 个内部函数(flat_tool_name, managed_network_for_sandbox_permissions, sandbox_permissions_preserving_denied_reads)。

ShellRuntime::run238–326 ↗
async fn run(
        &mut self,
        req: &ShellRequest,
        attempt: &SandboxAttempt<'_>,
        ctx: &ToolCtx,
    ) -> Result<ExecToolCallOutput, ToolError>

作用:真正把 shell 命令跑起来。它负责把用户给的命令加工成适合当前系统、当前 shell、当前沙箱和当前权限的形式,然后交给执行器运行并返回输出。

数据流:进去的是 ShellRequest、一次 SandboxAttempt 和 ToolCtx;函数会选择实际使用的 shell,整理沙箱权限和网络代理,过滤或补充环境变量,必要时加包路径、zsh-fork 路径、shell 快照、PowerShell UTF-8 前缀和 Windows 沙箱特殊设置;如果 zsh-fork 后端可用,就先尝试它,否则构造普通沙箱命令,设置超时和取消令牌,最后执行并收集 ExecToolCallOutput;过程中会改造即将执行的命令和环境,但不会直接改用户请求本身。

调用关系:它是这个文件的主流程。它会调用 exec_env_for_sandbox_permissions 准备环境,调用 apply_package_path_prepend 和 apply_zsh_fork_path_prepend 调整 PATH,调用 maybe_wrap_shell_lc_with_snapshot 和 disable_powershell_profile_for_elevated_windows_sandbox 包装命令,调用 build_sandbox_command 生成沙箱命令,再通过 attempt.env_for 得到最终执行环境,最后交给 execute_env 执行。若配置为 ShellCommandZshFork,它会先把机会交给 zsh_fork_backend::maybe_run_shell_command,失败或条件不满足才回到普通流程。

调用图:调用 11 个内部函数(execute_env, apply_package_path_prepend, apply_zsh_fork_path_prepend, build_sandbox_command, disable_powershell_profile_for_elevated_windows_sandbox, exec_env_for_sandbox_permissions, maybe_wrap_shell_lc_with_snapshot, env_for, managed_network_for_sandbox_permissions, sandbox_permissions_preserving_denied_reads (+1 more));外部调用 5 个(stdout_stream, default, matches!, warn!, maybe_run_shell_command)。

core/src/tools/runtimes/shell/zsh_fork_backend.rs源码 ↗
orchestrationrequest handling / spawn setup

普通 shell 执行命令时,通常直接启动一个 shell 进程。但有些 Unix 场景下,系统需要用 zsh-fork 这条特殊通道:先准备一个“提权会话”(可以理解成临时开了一扇受控的小门),再让真正的命令通过这扇门启动。这个文件不自己干复杂活,而是判断“该不该走这条特殊通道”,然后把请求转交给 Unix 提权模块。它还准备了一个启动生命周期对象,用来告诉统一执行器:哪些文件描述符(一种进程间传东西的小号码)要传给子进程,以及子进程启动后要把客户端 socket(进程间通信连接)关掉,避免资源泄漏。在非 Unix 系统上,这条路完全不启用,直接返回 None,意思是“别管我,走普通路线”。

函数细节6
maybe_run_shell_command21–28 ↗
async fn maybe_run_shell_command(
    req: &ShellRequest,
    attempt: &SandboxAttempt<'_>,
    ctx: &ToolCtx,
    command: &[String],
) -> Result<Option<ExecToolCallOutput>, ToolError>

作用:这是 shell 命令执行入口处的试探函数。它检查当前请求是否可能用 zsh-fork 特殊后端来跑;如果不能,就明确告诉调用方继续走普通 shell 路线。

数据流:进去的是 shell 请求、沙箱尝试信息、工具上下文和命令参数 → 它不直接处理命令,而是把这些东西交给平台相关的内部实现 → 出来的是“已经执行出的结果”,或者 None,表示没有接手,外面应继续用默认执行方式。

调用关系:它位于普通 shell 执行流程和 zsh-fork 特殊流程之间,像一个门卫。调用方先问它能不能处理;它再把问题转给内部的 maybe_run_shell_command 实现,由那里决定是否调用真正的 zsh-fork 执行逻辑。

调用图:外部调用 1 个(maybe_run_shell_command)。

maybe_prepare_unified_exec36–44 ↗
async fn maybe_prepare_unified_exec(
    req: &UnifiedExecRequest,
    attempt: &SandboxAttempt<'_>,
    ctx: &ToolCtx,
    exec_request: ExecRequest,
    zsh_fork_config: &ZshForkConfig,
) -> Result<

作用:这是统一执行器启动进程前的准备函数。它会尝试把一个普通启动请求改造成 zsh-fork 需要的启动请求,并附上一套启动前后要做的清理规则。

数据流:进去的是统一执行请求、沙箱信息、工具上下文、原始 ExecRequest,以及 zsh-fork 配置 → 它把这些交给内部实现判断和改写 → 出来的是 PreparedUnifiedExecSpawn,里面有改好的执行请求和生命周期钩子;如果不适合 zsh-fork,就返回 None。

调用关系:它服务于 unified exec,也就是项目里更统一的一套进程启动通道。外部启动器在真正 spawn(启动子进程)前调用它;如果成功,它会让后续启动器按 zsh-fork 的要求传递连接和做清理。

调用图:外部调用 1 个(maybe_prepare_unified_exec)。

imp::ZshForkSpawnLifecycle::inherited_fds60–67 ↗
fn inherited_fds(&self) -> Vec<i32>

作用:这个函数告诉进程启动器:启动子进程时,需要把哪个文件描述符传进去。文件描述符可以理解成系统分配的小号码,代表一个打开的连接或文件。

数据流:进去的是 ZshForkSpawnLifecycle 自己保存的提权会话 → 它从会话环境变量里找出提权 socket 对应的文件描述符号码,并把文本转成整数 → 出来的是一个整数列表;如果没有找到或格式不对,就给出空列表。

调用关系:它会在统一执行器准备启动子进程时被调用。它依赖提权会话的 env 信息,目的是让子进程继承那条必须保留的通信通道,否则 zsh-fork 后端就可能联系不上提权服务。

调用图:调用 1 个内部函数(env)。

imp::ZshForkSpawnLifecycle::after_spawn69–71 ↗
fn after_spawn(&mut self)

作用:这个函数在子进程已经启动后做收尾。它关闭父进程这边不再需要的客户端 socket,防止连接一直占着资源。

数据流:进去的是保存着提权会话的生命周期对象 → 它调用会话的关闭方法,把父进程里的客户端连接关掉 → 没有返回值,但会改变会话内部状态,让这条连接不再被父进程持有。

调用关系:它是启动生命周期的一部分,由统一执行器在 spawn 完成后调用。前面的 inherited_fds 负责“该传的传进去”,它则负责“传完后该关的关掉”,两者配合避免资源泄漏。

调用图:调用 1 个内部函数(close_client_socket)。

imp::maybe_run_shell_command116–124 ↗
async fn maybe_run_shell_command(
        req: &ShellRequest,
        attempt: &SandboxAttempt<'_>,
        ctx: &ToolCtx,
        command: &[String],
    ) -> Result<Option<ExecToolCallOutput>, ToolE

作用:这是 Unix 平台上的实际分派函数。它把 shell 命令交给 zsh-fork 的尝试执行逻辑,看看这次能不能由特殊后端直接跑完。

数据流:进去的是 shell 请求、沙箱尝试、工具上下文和命令参数 → 它调用 try_run_zsh_fork,让 Unix 提权模块判断命令形态、准备环境并尝试执行 → 出来的是执行结果,或者 None,表示这条特殊路不适用。

调用关系:它是外层 maybe_run_shell_command 的真正 Unix 实现。外层只负责统一接口;这里把活交给 unix_escalation::try_run_zsh_fork,真正的判断和运行细节在那里完成。

调用图:调用 1 个内部函数(try_run_zsh_fork)。

imp::maybe_prepare_unified_exec126–135 ↗
async fn maybe_prepare_unified_exec(
        req: &UnifiedExecRequest,
        attempt: &SandboxAttempt<'_>,
        ctx: &ToolCtx,
        exec_request: ExecRequest,
        zsh_fork_config: &ZshFork

作用:这是 Unix 平台上为统一执行器准备 zsh-fork 启动的核心函数。它把普通执行请求改造成能通过 zsh-fork 和提权会话启动的请求。

数据流:进去的是统一执行请求、沙箱信息、工具上下文、原始执行请求和 zsh-fork 配置路径 → 它调用 prepare_unified_exec_zsh_fork,用配置里的 zsh 路径和 execve 包装程序路径去准备新请求和提权会话 → 如果成功,出来的是 PreparedUnifiedExecSpawn;其中执行请求被替换成准备后的版本,生命周期对象也被新建出来保存提权会话。

调用关系:它是外层 maybe_prepare_unified_exec 在 Unix 上的实际工作者。它先把复杂准备交给 unix_escalation::prepare_unified_exec_zsh_fork;拿到结果后,再创建 ZshForkSpawnLifecycle,让后续统一执行器知道启动时要继承哪些连接、启动后要如何清理。

调用图:调用 1 个内部函数(prepare_unified_exec_zsh_fork);外部调用 1 个(new)。

统一 exec 工具流程

这些文件定义共享的 unified-exec 参数模型、具体工具处理器,以及用于启动统一执行会话的 PTY 型运行时。

core/src/tools/handlers/unified_exec.rs源码 ↗
orchestrationrequest handling

这个文件解决的是:同一个“执行命令”工具,可能在本机跑,也可能在远程环境跑;可能用普通 shell,也可能用特殊的 zsh-fork 模式;还要兼顾登录 shell、伪终端、超时输出等默认值。它先定义命令输入长什么样,比如 cmd、shell、workdir、sandbox 权限等;再提供几个默认值,避免调用方没填时行为不稳定。最重要的是 get_command:它像翻译员,把“我想执行这段命令”翻译成操作系统真正要启动的参数列表。另一个关键点是 post_unified_exec_tool_use_payload,它把执行结果整理成“工具用完了”的通知,给钩子系统使用。整体上,它不直接跑命令,而是把参数、shell 选择、环境差异这些容易出错的事先理顺。

函数细节6
default_exec_yield_time_ms60–62 ↗
fn default_exec_yield_time_ms() -> u64

作用:给执行命令工具提供默认的等待时间。调用方没指定多久返回一次输出时,就用这个值。

数据流:进去没有参数 → 它直接给出固定数字 10000 → 出来的是 10000 毫秒,也就是 10 秒,不改动任何外部状态。

调用关系:它配合 ExecCommandArgs 的反序列化默认值使用。也就是说,外部请求少填了 yield_time_ms 时,这个小函数会补上默认值,避免执行过程没有明确的节奏。

default_write_stdin_yield_time_ms64–66 ↗
fn default_write_stdin_yield_time_ms() -> u64

作用:给“往正在运行的命令里写输入”这个动作提供默认等待时间。这个等待更短,因为写入标准输入通常希望很快看到反馈。

数据流:进去没有参数 → 它直接返回固定数字 250 → 出来的是 250 毫秒,不读取也不修改别的数据。

调用关系:它服务于 write_stdin 相关处理器。和执行命令的默认 10 秒不同,这里只等 0.25 秒,适合交互式输入后的快速响应。

default_tty68–70 ↗
fn default_tty() -> bool

作用:决定命令默认不启用 TTY。TTY 可以理解成“像真人打开终端一样”的交互环境,但不是所有命令都需要。

数据流:进去没有参数 → 它返回 false → 出来表示默认不创建伪终端,也不改变任何状态。

调用关系:它给 ExecCommandArgs 里的 tty 字段兜底。调用方没明确说要终端交互时,系统就按普通非交互命令来处理,行为更可预测。

post_unified_exec_tool_use_payload78–95 ↗
fn post_unified_exec_tool_use_payload(
    invocation: &ToolInvocation,
    result: &dyn ToolOutput,
) -> Option<PostToolUsePayload>

作用:把一次命令工具调用和它的结果,整理成钩子系统能看懂的“工具已使用”消息。钩子可以理解成事后通知机制,方便别的模块在命令执行后做检查或记录。

数据流:进去的是一次工具调用 invocation 和一个执行结果 result → 它先确认这次调用确实是函数式工具调用,不是别的类型;然后从结果里取出钩子需要的输入、调用编号和响应内容 → 出来可能是一个 PostToolUsePayload;如果资料不够或类型不对,就出来 None。

调用关系:它位于命令执行完成之后、钩子被触发之前。它会用 HookToolName::bash() 标明这类执行在钩子里按 bash 工具看待,并调用结果对象的 post_tool_use_input、post_tool_use_id、post_tool_use_response 来拼出完整通知。

调用图:调用 4 个内部函数(bash, post_tool_use_id, post_tool_use_input, post_tool_use_response)。

get_command97–142 ↗
fn get_command(
    args: &ExecCommandArgs,
    session_shell: Arc<Shell>,
    shell_mode: &UnifiedExecShellMode,
    allow_login_shell: bool,
) -> Result<ResolvedCommand, String>

作用:把用户给的命令字符串,转换成真正要启动的进程参数。它还负责检查登录 shell 是否被允许,以及在不同 shell 模式下选择正确的运行方式。

数据流:进去的是命令参数 args、当前会话默认 shell、这轮对话指定的 shell 模式、以及是否允许登录 shell → 它先决定要不要用登录 shell;如果配置禁止但用户强行要求,就返回错误;接着按模式处理:Direct 模式会使用用户指定 shell 或会话默认 shell,ZshFork 模式会固定拼出 zsh 的启动参数 → 出来是 ResolvedCommand,里面有参数列表和 shell 类型;遇到不支持的组合就出来错误字符串。

调用关系:它是执行命令前的关键准备步骤,像出发前把地址翻译成导航路线。后面的真正执行器会拿它产出的 command 去启动进程。它在 ZshFork 分支里构造参数列表时使用 vec!,并且会阻止本地 zsh-fork 模式下再额外指定 shell,因为那会让执行方式变得含混。

调用图:外部调用 1 个(vec!)。

shell_mode_for_environment144–153 ↗
fn shell_mode_for_environment(
    turn_shell_mode: &UnifiedExecShellMode,
    environment: &Environment,
) -> UnifiedExecShellMode

作用:根据目标环境决定该用哪种 shell 模式。简单说:远程环境一律用 Direct,本地环境才保留当前配置的模式。

数据流:进去的是当前这轮配置的 shell 模式和一个环境对象 → 它询问环境是不是远程的;如果是远程,就返回 Direct;如果不是,就复制原来的模式返回 → 出来的是最终要使用的 UnifiedExecShellMode,不改动原环境。

调用关系:它通常在选定执行环境之后、解析命令之前使用。它调用 environment.is_remote() 判断远近;本地时调用 clone 复制原模式。这样可以避免把本地专用的 zsh-fork 模式错误地带到远程机器上。

调用图:调用 1 个内部函数(is_remote);外部调用 1 个(clone)。

core/src/tools/handlers/unified_exec/exec_command.rs源码 ↗
orchestrationrequest handling

这个文件解决的是“模型想运行一条 shell 命令时,系统怎么安全、正确地执行”的问题。没有它,模型给出的命令可能不知道该在哪个工作目录跑、用哪个环境跑、能不能申请更高权限,也没法被钩子改写或记录指标。它的核心是 ExecCommandHandler。它先声明自己是 exec_command 这个工具,并给外部提供工具规格。真正执行时,它会从调用里取出命令参数,找到对应的运行环境,算出工作目录,解析 shell 命令,处理权限申请和已批准的权限。如果发现命令其实是在打补丁,它会走专门的补丁拦截路径。否则,它把整理好的请求交给 UnifiedExecProcessManager,也就是统一的进程执行管家。执行失败时,它会把沙箱拒绝这类结果转成模型能读懂的输出;其他错误则直接告诉模型。它还支持工具使用前后的钩子:比如执行前把命令交给 hook 看一眼,或让 hook 改写命令。

函数细节12
ExecCommandHandler::default56–65 ↗
fn default() -> Self

作用:创建一个默认的命令执行处理器,使用一组保守的默认开关。测试里经常用它来得到一个标准版本的处理器。

数据流:进去没有外部输入 → 它填好默认选项:不允许登录 shell、不启用执行权限审批、不带环境 ID、但保留 shell 参数 → 出来一个可以处理 exec_commandExecCommandHandler

调用关系:它主要被多组测试直接使用,用来验证权限拒绝、工具前后钩子、输出处理等行为。它不调用别的项目函数,只是把默认配置装进处理器。

调用图:被 6 处调用(guardian_allows_unified_exec_additional_permissions_requests_past_policy_validation, unified_exec_rejects_escalated_permissions_when_policy_not_on_request, exec_command_post_tool_use_payload_skips_running_sessions, exec_command_post_tool_use_payload_uses_output_for_interactive_completion, exec_command_post_tool_use_payload_uses_output_for_noninteractive_one_shot_commands, exec_command_pre_tool_use_payload_uses_raw_command)。

ExecCommandHandler::new69–71 ↗
fn new(options: ExecCommandHandlerOptions) -> Self

作用:按调用方给定的选项创建命令执行处理器。外部在注册 shell 工具时会用它来定制这个处理器的能力。

数据流:进去是一份 ExecCommandHandlerOptions 配置 → 它原样保存这些开关 → 出来一个带指定行为的 ExecCommandHandler

调用关系:它被 add_shell_tools 调用,通常发生在系统把各种工具注册进工具箱的时候。后续这个处理器会用这些选项生成工具说明,并决定命令调用时允许哪些参数。

调用图:被 1 处调用(add_shell_tools)。

ExecCommandHandler::tool_name75–77 ↗
fn tool_name(&self) -> ToolName

作用:告诉工具系统:这个处理器对应的工具名叫 exec_command。工具注册和分发时靠这个名字找到它。

数据流:进去是处理器自身 → 它调用 plain 生成一个普通工具名 → 出来是 exec_command 这个 ToolName

调用关系:工具系统会在识别和登记工具时调用它。它把具体名字交给 plain 来包装,自己不做复杂判断。

调用图:调用 1 个内部函数(plain)。

ExecCommandHandler::spec79–88 ↗
fn spec(&self) -> ToolSpec

作用:生成 exec_command 这个工具对外暴露的参数说明。模型要知道能传哪些字段,就靠这份规格。

数据流:进去是处理器保存的选项,比如是否允许登录 shell、是否带环境 ID、是否带 shell 参数 → 它把这些开关放进 CommandToolOptions → 交给 create_exec_command_tool_with_environment_id 生成完整工具规格 → 出来是 ToolSpec

调用关系:工具注册或向模型公布工具能力时会调用它。它不自己拼完整 schema,而是把开关交给专门创建命令工具规格的函数。

调用图:调用 1 个内部函数(create_exec_command_tool_with_environment_id)。

ExecCommandHandler::supports_parallel_tool_calls90–92 ↗
fn supports_parallel_tool_calls(&self) -> bool

作用:声明这个工具允许并行调用,也就是多个命令请求可以同时存在。这样系统不必强行一个一个排队。

数据流:进去是处理器自身 → 它直接返回 true → 出来是“支持并行”的判断结果。

调用关系:工具调度器会看这个结果来决定能不能同时发起多个 exec_command。它不调用其他函数,只提供一个明确的能力声明。

ExecCommandHandler::handle94–96 ↗
fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_>

作用:这是工具系统真正收到调用时进入的门口。它把同步接口包装成异步任务,然后交给更大的 handle_call 去做细活。

数据流:进去是一份 ToolInvocation,里面有会话、轮次、调用 ID、参数等信息 → 它调用 handle_call,并用 pin 把异步任务固定好,方便运行时调度 → 出来是一个将来会完成的工具执行结果。

调用关系:工具框架在模型调用 exec_command 时会调用它。它自己不解析命令,只负责把活儿转交给 ExecCommandHandler::handle_call

调用图:调用 1 个内部函数(handle_call);外部调用 1 个(pin)。

ExecCommandHandler::handle_call100–327 ↗
async fn handle_call(
        &self,
        invocation: ToolInvocation,
    ) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError>

作用:这是本文件最核心的流程:把一次 exec_command 调用从原始参数变成安全可执行的进程请求。它负责环境选择、路径计算、权限检查、补丁拦截、指标记录和结果包装。

数据流:进去是一份工具调用,包含会话、当前轮次、调用编号、追踪器和 JSON 参数 → 它先确认参数类型正确,再解析环境 ID 和工作目录,找到要使用的执行环境;接着解析命令、决定 shell 模式、生成显示用命令;然后合并本轮已批准权限,检查是否允许申请沙箱外权限,并规范化额外权限;如果命令是补丁操作,就交给补丁拦截器处理并直接返回结果;否则记录是否使用 TTY 的指标,把命令请求交给统一执行进程管理器 → 出来是包装好的工具输出;如果沙箱拒绝,会返回带拒绝输出和退出码的结果;如果是其他错误,会返回给模型看的错误信息。它也会在出错或提前返回时释放已分配的进程 ID。

调用关系:它由 ExecCommandHandler::handle 调用,是一次命令执行的主干流程。它会调用 parse_argumentsparse_arguments_with_base_path 读懂参数,调用 resolve_tool_environment 找环境,调用 apply_granted_turn_permissionsimplicit_granted_permissions 和权限规范化函数处理权限,调用 intercept_apply_patch 处理特殊的补丁命令,调用 emit_unified_exec_tty_metric 记指标,最后把真正运行命令的工作交给 UnifiedExecProcessManager::exec_command

调用图:调用 11 个内部函数(boxed_tool_output, apply_granted_turn_permissions, intercept_apply_patch, implicit_granted_permissions, parse_arguments, parse_arguments_with_base_path, resolve_tool_environment, emit_unified_exec_tty_metric, new, generate_chunk_id (+1 more));被 1 处调用(handle);外部调用 9 个(clone, new, approx_token_count, maybe_emit_implicit_skill_invocation, format!, matches!, get_command, shell_mode_for_environment, RespondToModel)。

ExecCommandHandler::matches_kind331–333 ↗
fn matches_kind(&self, payload: &ToolPayload) -> bool

作用:判断传进来的工具载荷是不是这个处理器能处理的类型。这里它只接受函数式调用参数。

数据流:进去是一份 ToolPayload → 它检查这个载荷是不是 Function 形式 → 出来是 truefalse

调用关系:核心工具运行时会用它来判断某个调用能不能交给这个处理器。它只做类型把关,不解析里面的命令内容。

调用图:外部调用 1 个(matches!)。

ExecCommandHandler::pre_tool_use_payload335–346 ↗
fn pre_tool_use_payload(&self, invocation: &ToolInvocation) -> Option<PreToolUsePayload>

作用:在命令真正执行前,准备一份给 hook 用的输入。hook 可以理解成“执行前检查员”,比如用来审查或记录即将运行的命令。

数据流:进去是一份工具调用 → 它先确认载荷是函数参数,再尝试解析成 ExecCommandArgs → 如果成功,就取出原始命令 cmd,包装成 hook 认识的 bash 工具输入;如果失败或类型不对,就返回空 → 出来可能是一份 PreToolUsePayload

调用关系:工具运行时在执行前置 hook 时会调用它。它为后续 hook 提供最关键的信息:将要执行的命令文本。

ExecCommandHandler::with_updated_hook_input348–367 ↗
fn with_updated_hook_input(
        &self,
        mut invocation: ToolInvocation,
        updated_input: serde_json::Value,
    ) -> Result<ToolInvocation, FunctionCallError>

作用:把 hook 修改后的命令写回原来的工具调用里。也就是说,如果执行前检查员改了命令,这个函数负责把新命令塞回去。

数据流:进去是原始工具调用和 hook 返回的新输入 → 它确认原始载荷是函数参数,调用 updated_hook_command 从 hook 输入里取出新命令,再用 rewrite_function_string_argument 替换 JSON 参数里的 cmd 字段 → 出来是更新后的 ToolInvocation;如果载荷类型不对或新命令不合法,就返回给模型看的错误。

调用关系:它在前置 hook 允许改写输入时被工具运行时调用。它把解析新命令的工作交给 updated_hook_command,把改写 JSON 参数的工作交给 rewrite_function_string_argument

调用图:调用 2 个内部函数(rewrite_function_string_argument, updated_hook_command);外部调用 1 个(RespondToModel)。

ExecCommandHandler::post_tool_use_payload369–375 ↗
fn post_tool_use_payload(
        &self,
        invocation: &ToolInvocation,
        result: &dyn crate::tools::context::ToolOutput,
    ) -> Option<PostToolUsePayload>

作用:在命令执行结束后,准备一份给后置 hook 用的结果信息。后置 hook 可以用来记录命令结果,或根据输出做收尾动作。

数据流:进去是原始工具调用和已经产生的工具输出 → 它把两者交给 post_unified_exec_tool_use_payload → 出来可能是一份后置 hook 载荷;如果当前结果不适合生成 hook 信息,就返回空。

调用关系:工具运行时在执行后置 hook 时会调用它。它不自己拆输出,而是复用统一执行工具的后置载荷生成逻辑。

调用图:外部调用 1 个(post_unified_exec_tool_use_payload)。

emit_unified_exec_tty_metric378–384 ↗
fn emit_unified_exec_tty_metric(session_telemetry: &SessionTelemetry, tty: bool)

作用:记录一次 exec_command 调用是否使用了 TTY。TTY 可以简单理解成“像真实终端一样交互的运行方式”。

数据流:进去是会话遥测对象和一个 tty 布尔值 → 它给统一执行工具调用指标加 1,并附带标签 tty=truetty=false → 出来没有业务结果,但遥测计数器被更新了。

调用关系:它只被 ExecCommandHandler::handle_call 调用,发生在命令即将交给执行管理器之前。它把计数工作交给遥测系统的 counter 方法。

调用图:调用 1 个内部函数(counter);被 1 处调用(handle_call)。

core/src/tools/handlers/shell/shell_command.rs源码 ↗
orchestrationrequest handling

可以把这个文件想成“命令执行前台接待员”。模型只给出一段命令和一些选项,但系统真正运行前,还要确认工作目录在哪、能不能用登录 shell(登录 shell 就像打开一个完整用户终端,会读取更多用户配置)、环境变量怎么带、网络和沙箱权限怎么限制,以及执行前后要不要通知钩子(钩子就是可插入的检查或改写步骤)。ShellCommandHandler 就是这个接待员:它先识别请求是不是函数式工具调用,再把 JSON 参数解析成清楚的执行参数,必要时发出“隐式技能调用”提示,最后交给通用的 run_exec_like 去真正执行。它还支持两种后端:传统 shell 命令后端和 zsh fork 后端,并把执行前、执行后给钩子看的数据整理成统一格式。

函数细节16
ShellCommandHandler::new53–59 ↗
fn new(options: ShellCommandHandlerOptions) -> Self

作用:创建一个 shell 命令处理器,并根据配置选定实际使用哪种命令运行后端。别人要把 shell_command 工具注册进系统时,会先用它造出处理器。

数据流:进去的是 ShellCommandHandlerOptions,里面有后端选择、是否允许登录 shell、是否启用执行权限审批等开关;函数把外部配置里的后端名字转换成内部使用的 ShellCommandBackend;出来的是一个带好后端和选项的 ShellCommandHandler。

调用关系:add_shell_tools 在添加 shell 相关工具时会调用它。它是这个处理器生命周期的起点,后面的 spec、handle_call 等方法都依赖这里保存下来的配置。

调用图:被 1 处调用(add_shell_tools)。

ShellCommandHandler::shell_runtime_backend61–66 ↗
fn shell_runtime_backend(&self) -> ShellRuntimeBackend

作用:把这个处理器内部记住的后端选择,翻译成执行层能理解的运行时后端名字。简单说,就是告诉真正跑命令的机器:你该用传统方式还是 zsh fork 方式。

数据流:进去的是处理器自身保存的 backend;函数做一次对应关系转换;出来的是 ShellRuntimeBackend,比如 ShellCommandClassic 或 ShellCommandZshFork。

调用关系:handle_call 在准备把命令交给 run_exec_like 前会调用它。它连接了“工具处理器的配置”和“底层执行器实际采用的实现”。

调用图:被 1 处调用(handle_call)。

ShellCommandHandler::resolve_use_login_shell68–79 ↗
fn resolve_use_login_shell(
        login: Option<bool>,
        allow_login_shell: bool,
    ) -> Result<bool, FunctionCallError>

作用:决定这次命令到底要不要用登录 shell,并阻止用户在配置禁止时强行打开。登录 shell 会读取用户启动配置,能力更强,也更容易带来不可预期行为,所以这里要把关。

数据流:进去的是调用参数里的 login 值,以及全局配置 allow_login_shell;如果配置不允许但请求明确要 login=true,它返回给模型看的错误;否则返回最终布尔值,没显式写 login 时就按配置默认值走。

调用关系:to_exec_params 会在拼执行参数时用它决定 shell 启动方式。测试 shell_command_handler_rejects_login_when_disallowed 专门验证禁止登录 shell 时会拒绝。

调用图:被 1 处调用(shell_command_handler_rejects_login_when_disallowed);外部调用 1 个(RespondToModel)。

ShellCommandHandler::base_command81–83 ↗
fn base_command(shell: &Shell, command: &str, use_login_shell: bool) -> Vec<String>

作用:把用户写的一行命令,变成系统进程启动时需要的参数列表。比如不是直接丢一整串文字,而是整理成 shell 程序和它的参数。

数据流:进去的是当前用户使用的 Shell、原始 command 字符串、是否用登录 shell;函数调用 shell 的 derive_exec_args 来生成启动参数;出来的是 Vec<String>,也就是进程执行时要用的一组命令参数。

调用关系:to_exec_params 用它生成 ExecParams 里的 command。测试 shell_command_handler_respects_explicit_login_flag 用它确认显式 login 开关会影响生成出来的命令形式。

调用图:调用 1 个内部函数(derive_exec_args);被 1 处调用(shell_command_handler_respects_explicit_login_flag)。

ShellCommandHandler::to_exec_params85–114 ↗
fn to_exec_params(
        params: &ShellCommandToolCallParams,
        session: &crate::session::session::Session,
        turn_context: &TurnContext,
        thread_id: ThreadId,
        allow_login

作用:把 shell_command 工具的参数,整理成底层执行器能直接使用的 ExecParams。它是“模型请求”到“可执行任务单”的转换点。

数据流:进去的是工具参数、当前会话、当前轮次上下文、线程 ID、是否允许登录 shell;它读取用户 shell,判断登录 shell,生成基础命令,解析工作目录,设置超时、输出捕获策略、环境变量、网络权限、沙箱权限和理由说明;出来的是完整 ExecParams,供真正执行命令使用。

调用关系:handle_call 在解析完参数后会调用它。它内部会用 create_env、resolve_path、base_command、resolve_use_login_shell 和 user_shell,把会话信息和轮次配置合成一张执行任务单;相关测试会检查默认登录 shell 行为以及是否正确使用 session 和 turn context。

调用图:调用 2 个内部函数(create_env, resolve_path);被 2 处调用(shell_command_handler_defaults_to_non_login_when_disallowed, shell_command_handler_to_exec_params_uses_session_shell_and_turn_context);外部调用 3 个(base_command, resolve_use_login_shell, user_shell)。

ShellCommandHandler::from118–124 ↗
fn from(backend_config: ShellCommandBackendConfig) -> Self

作用:提供一个简便入口:只给一个后端配置,也能快速造出 ShellCommandHandler。适合测试或旧代码只关心后端、不想填写完整选项的场景。

数据流:进去的是 ShellCommandBackendConfig;函数补上默认选项:不允许登录 shell,也不启用执行权限审批;然后调用 new;出来的是 ShellCommandHandler。

调用关系:多个权限和钩子相关测试会用它快速创建处理器。它把简单配置接到 ShellCommandHandler::new,避免每个调用点重复写默认值。

调用图:被 6 处调用(guardian_allows_shell_command_additional_permissions_requests_past_policy_validation, shell_command_allows_sticky_turn_permissions_without_inline_request_permissions_feature, strict_auto_review_turn_grant_forces_guardian_for_shell_command_policy_skip, rejects_escalated_permissions_when_policy_not_on_request, build_post_tool_use_payload_uses_tool_output_wire_value, shell_command_pre_tool_use_payload_uses_raw_command);外部调用 1 个(new)。

ShellCommandHandler::tool_name128–130 ↗
fn tool_name(&self) -> ToolName

作用:告诉系统这个处理器对应的工具名叫 shell_command。工具名就像柜台号码,系统靠它把请求分给正确处理器。

数据流:进去的是处理器自身;函数构造一个普通 ToolName,内容是 shell_command;出来的是 ToolName。

调用关系:handle_call 会用它生成当前工具名,出错提示和 run_exec_like 都需要这个名字。工具注册表也会依靠这个 trait 方法识别工具。

调用图:调用 1 个内部函数(plain);被 1 处调用(handle_call)。

ShellCommandHandler::spec132–137 ↗
fn spec(&self) -> ToolSpec

作用:生成这个工具对外公布的说明书,也就是模型能看到的参数格式和能力开关。这样模型才知道 shell_command 能填哪些字段、哪些功能被允许。

数据流:进去的是处理器保存的选项;函数把是否允许登录 shell、是否启用执行权限审批放进 CommandToolOptions;出来的是 ToolSpec,也就是工具规格说明。

调用关系:工具系统在向模型暴露工具时会调用它。它把运行配置传给 create_shell_command_tool,由那里生成最终的工具定义。

调用图:调用 1 个内部函数(create_shell_command_tool)。

ShellCommandHandler::supports_parallel_tool_calls139–141 ↗
fn supports_parallel_tool_calls(&self) -> bool

作用:声明这个工具支持并行调用。也就是说,系统可以在合适时同时处理多个 shell_command 请求,而不是必须一个等一个。

数据流:进去的是处理器自身;函数不读取复杂状态,直接返回 true;出来的是“可以并行”的判断结果。

调用关系:工具调度器会看这个回答来决定能不能并发安排调用。它本身不执行命令,只给调度层一个能力声明。

ShellCommandHandler::handle143–145 ↗
fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_>

作用:这是工具执行接口的入口,把一次工具调用包装成异步任务。异步任务的意思是命令可能跑一段时间,系统不用卡死等它结束。

数据流:进去的是 ToolInvocation,里面有会话、轮次、参数、取消令牌等信息;函数把真正工作交给 handle_call,并用 Box::pin 包成可等待的 future;出来的是一个异步执行对象。

调用关系:工具运行框架收到 shell_command 调用时会走到这里。它不做细活,只负责把标准接口转接到 handle_call。

调用图:调用 1 个内部函数(handle_call);外部调用 1 个(pin)。

ShellCommandHandler::handle_call149–207 ↗
async fn handle_call(
        &self,
        invocation: ToolInvocation,
    ) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError>

作用:真正处理一次 shell_command 调用:检查请求形状、解析参数、准备执行环境,然后交给通用执行流程去跑命令。这是本文件最核心的流水线。

数据流:进去的是 ToolInvocation;它拆出 session、turn、取消令牌、调用 ID 和 payload,确认 payload 是函数参数,解析工作目录和 ShellCommandToolCallParams,发出可能的隐式技能提示,构造 ExecParams,取出 shell 类型和后端;最后调用 run_exec_like;出来的是工具输出,或返回一个给模型看的错误。

调用关系:handle 会直接调用它。它把多块零件串起来:parse_arguments_with_base_path 负责读参数,resolve_workdir_base_path 负责定目录,to_exec_params 负责造任务单,shell_runtime_backend 负责选后端,run_exec_like 负责真正执行和处理审批、取消、输出等通用流程。

调用图:调用 4 个内部函数(parse_arguments_with_base_path, resolve_workdir_base_path, shell_runtime_backend, tool_name);被 1 处调用(handle);外部调用 5 个(to_exec_params, maybe_emit_implicit_skill_invocation, format!, run_exec_like, RespondToModel)。

ShellCommandHandler::matches_kind211–213 ↗
fn matches_kind(&self, payload: &ToolPayload) -> bool

作用:判断一个工具请求的 payload 类型是不是这个处理器能处理的。shell_command 只接受函数式参数,不接受别的 payload 形态。

数据流:进去的是 ToolPayload;函数检查它是否是 ToolPayload::Function;出来的是 true 或 false。

调用关系:核心工具运行时会用它来筛选请求该不该交给这个处理器。它像门口验票员,只看票的类型对不对。

调用图:外部调用 1 个(matches!)。

ShellCommandHandler::waits_for_runtime_cancellation215–217 ↗
fn waits_for_runtime_cancellation(&self) -> bool

作用:告诉系统:如果用户取消命令,这个处理器会等待底层运行时完成取消收尾。这样可以避免命令还在后台乱跑,或者输出状态提前结束。

数据流:进去的是处理器自身;函数直接返回 true;出来的是“取消时要等运行时收尾”的声明。

调用关系:工具运行框架在处理取消请求时会参考它。它配合 handle_call 传入的 cancellation_token,让取消流程更可靠。

ShellCommandHandler::pre_tool_use_payload219–224 ↗
fn pre_tool_use_payload(&self, invocation: &ToolInvocation) -> Option<PreToolUsePayload>

作用:在命令执行前,给钩子系统准备一份“将要运行什么命令”的数据。钩子可以用它做审查、记录,甚至后续改写输入。

数据流:进去的是 ToolInvocation;函数从 payload 里取出原始 command;如果取不到就返回 None,取到就包装成 PreToolUsePayload,tool_name 使用 bash,tool_input 里放 command;出来的是可选的执行前钩子数据。

调用关系:CoreToolRuntime 的执行前钩子流程会调用它。它依赖 shell_command_payload_command 抽取命令,把 shell_command 映射成钩子层熟悉的 bash 工具名。

调用图:外部调用 1 个(shell_command_payload_command)。

ShellCommandHandler::with_updated_hook_input226–245 ↗
fn with_updated_hook_input(
        &self,
        mut invocation: ToolInvocation,
        updated_input: serde_json::Value,
    ) -> Result<ToolInvocation, FunctionCallError>

作用:把钩子改写后的命令塞回原来的工具调用里。也就是说,如果执行前钩子决定把 command 改一下,这里负责安全地更新调用参数。

数据流:进去的是原 ToolInvocation 和 updated_input;函数先确认 payload 是函数参数,不是就返回给模型看的错误;然后从 updated_input 里取出更新后的 command,并调用 rewrite_function_string_argument 只替换 command 字段;出来的是 payload 已更新的新 ToolInvocation。

调用关系:钩子系统在执行前改写输入后会调用它。它把 updated_hook_command 提取出的新命令交给 rewrite_function_string_argument,避免手写 JSON 时误改其他字段。

调用图:调用 2 个内部函数(rewrite_function_string_argument, updated_hook_command);外部调用 1 个(RespondToModel)。

ShellCommandHandler::post_tool_use_payload247–261 ↗
fn post_tool_use_payload(
        &self,
        invocation: &ToolInvocation,
        result: &dyn crate::tools::context::ToolOutput,
    ) -> Option<PostToolUsePayload>

作用:在命令执行后,给钩子系统准备一份结果报告。里面既有原来运行的命令,也有工具输出,方便做审计、日志或后续处理。

数据流:进去的是 ToolInvocation 和本次工具输出 result;函数先把输出转换成钩子可读的 tool_response,再从 payload 取出 command;如果成功,就组装 PostToolUsePayload,带上 bash 工具名、调用 ID、命令输入和执行结果;出来的是可选的执行后钩子数据。

调用关系:CoreToolRuntime 的执行后钩子流程会调用它。它依赖 result.post_tool_use_response 生成响应内容,也依赖 shell_command_payload_command 取回原始命令。

调用图:调用 2 个内部函数(bash, post_tool_use_response);外部调用 2 个(json!, shell_command_payload_command)。

core/src/tools/runtimes/unified_exec.rs源码 ↗
orchestrationrequest handling

这个文件像一个命令执行前的“安检口”。外部想跑一条 shell 命令时,不是直接启动进程,而是先变成 UnifiedExecRequest,里面带着命令、工作目录、环境变量、是否需要终端、沙箱权限、网络代理等信息。UnifiedExecRuntime 会先告诉系统它偏好自动选择沙箱,并且失败时可以升级权限再试。需要审批时,它会把命令规范化,避免同一条命令换个写法就绕过缓存;然后要么走 Guardian 审核,要么走普通命令审批。真正运行时,它会按权限过滤环境变量,接入受控网络,必要时包一层 shell 快照,给 PowerShell 加 UTF-8 前缀,并可尝试 zsh-fork 这种更快的启动方式。最后它构造沙箱命令,把启动请求交给 UnifiedExecProcessManager。文件底部的测试确保关键安全行为没有被改坏。

函数细节20
unified_exec_options100–111 ↗
fn unified_exec_options(
    network_denial_cancellation_token: Option<CancellationToken>,
) -> ExecOptions

作用:给一次命令执行准备通用选项,主要是超时规则和输出捕获方式。它还可以把“网络被拒绝就取消”的信号合进超时规则里。

数据流:进去的是一个可选的 CancellationToken(取消令牌,可以理解成别人按下的停止按钮)→ 函数从默认超时开始,如果有取消令牌,就改成“到时间或收到取消都停”→ 出来的是 ExecOptions,里面固定使用 ShellTool 的输出捕获策略。

调用关系:UnifiedExecRuntime::run 在真正启动命令前调用它,把执行规则塞进沙箱环境;测试 tests::unified_exec_options_combines_default_timeout_with_network_denial_cancellation 专门检查取消令牌确实被带进去。

调用图:被 2 处调用(run, unified_exec_options_combines_default_timeout_with_network_denial_cancellation)。

UnifiedExecRuntime::new115–120 ↗
fn new(manager: &'a UnifiedExecProcessManager, shell_mode: UnifiedExecShellMode) -> Self

作用:创建一个统一执行运行器,把它绑定到共享的进程管理器,并记录要用直接执行还是 zsh-fork 这类 shell 启动模式。

数据流:进去的是 UnifiedExecProcessManager 的引用和 UnifiedExecShellMode→ 函数只是把这两样保存起来→ 出来的是 UnifiedExecRuntime,之后可以用它审批和运行命令。

调用关系:open_session_with_sandbox 会在需要统一执行命令时创建它;多个测试也用它搭出运行器,检查不同模式下的安全策略是否保持一致。

调用图:被 5 处调用(unified_exec_uses_the_trusted_sandbox_cwd, zsh_fork_execpolicy_allow_preserves_parent_sandbox_override, zsh_fork_first_attempt_preserves_additional_permissions_request, zsh_fork_first_attempt_preserves_parent_sandbox_override, open_session_with_sandbox)。

UnifiedExecRuntime::sandbox_preference124–126 ↗
fn sandbox_preference(&self) -> SandboxablePreference

作用:告诉沙箱框架:这个运行器希望系统自动判断该不该用沙箱。也就是说,它不强行开或关,而是交给上层策略决定。

数据流:进去的是运行器自身状态,但这里不读取具体字段→ 直接返回 SandboxablePreference::Auto→ 不改动任何东西。

调用关系:这是 Sandboxable 接口的一部分;沙箱调度流程会用它判断默认执行路线,后续再结合审批和权限决定实际怎么跑。

UnifiedExecRuntime::escalate_on_failure128–130 ↗
fn escalate_on_failure(&self) -> bool

作用:告诉沙箱框架:如果沙箱里执行失败,可以尝试走权限更高的重试流程。这样可以先安全尝试,失败后再让用户或策略决定是否放宽。

数据流:进去的是运行器自身→ 函数固定返回 true→ 不产生其他副作用。

调用关系:这是 Sandboxable 接口给统一执行工具的策略提示;当第一次沙箱尝试受限失败时,上层流程会据此考虑升级权限。

UnifiedExecRuntime::approval_keys136–144 ↗
fn approval_keys(&self, req: &UnifiedExecRequest) -> Vec<Self::ApprovalKey>

作用:为一次命令审批生成“缓存钥匙”。同一条命令、同一目录、同一沙箱和附加权限,下次就可以复用之前的审批结果。

数据流:进去的是 UnifiedExecRequest→ 函数把命令先规范化,连同 cwd、tty、沙箱权限、附加权限组成 UnifiedExecApprovalKey,并放进一个列表→ 出来的是可用于审批缓存的 key 列表。

调用关系:UnifiedExecRuntime::start_approval_async 会先调用它拿缓存钥匙;它内部用 vec! 生成列表,并依赖命令规范化来降低“换写法绕审批”的风险。

调用图:被 1 处调用(start_approval_async);外部调用 1 个(vec!)。

UnifiedExecRuntime::start_approval_async146–200 ↗
fn start_approval_async(
        &'b mut self,
        req: &'b UnifiedExecRequest,
        ctx: ApprovalCtx<'b>,
    ) -> BoxFuture<'b, ReviewDecision>

作用:发起命令审批。它决定是把请求交给 Guardian 审核,还是走普通的命令审批,并尽量复用缓存过的审批结果。

数据流:进去的是执行请求和 ApprovalCtx(审批上下文,里面有会话、当前轮次、调用编号、重试原因等)→ 函数先生成 approval keys,再整理命令、目录、理由等审批材料;如果已有 Guardian 审核编号,就提交 GuardianApprovalRequest,否则用 with_cached_approval 包住普通审批请求→ 出来的是异步得到的 ReviewDecision,也就是同意、拒绝或其他审核决定。

调用关系:这是 Approvable 接口的核心步骤;它调用 UnifiedExecRuntime::approval_keys 准备缓存键,可能调用 review_approval_request 走 Guardian,也可能调用 with_cached_approval 包住 session.request_command_approval,避免重复打扰用户。

调用图:调用 2 个内部函数(approval_keys, with_cached_approval);外部调用 2 个(pin, review_approval_request)。

UnifiedExecRuntime::exec_approval_requirement202–207 ↗
fn exec_approval_requirement(
        &self,
        req: &UnifiedExecRequest,
    ) -> Option<ExecApprovalRequirement>

作用:把请求里已经算好的执行审批要求交还给框架。上层可以据此知道这条命令是跳过审批、必须审批,还是带有策略修改建议。

数据流:进去的是 UnifiedExecRequest→ 函数克隆其中的 exec_approval_requirement 并包成 Some→ 出来的是审批要求,不改请求本身。

调用关系:这是 Approvable 接口的一部分;测试 tests::zsh_fork_execpolicy_allow_preserves_parent_sandbox_override 会确认 zsh-fork 模式不会偷偷改掉父流程给出的绕过沙箱决定。

UnifiedExecRuntime::permission_request_payload209–217 ↗
fn permission_request_payload(
        &self,
        req: &UnifiedExecRequest,
    ) -> Option<PermissionRequestPayload>

作用:把这次命令整理成一个权限申请说明,方便系统向用户说明“要为哪条 bash 命令申请权限、理由是什么”。

数据流:进去的是 UnifiedExecRequest→ 函数取出 hook_command 和 justification,用 PermissionRequestPayload::bash 包装→ 出来的是一个可展示的权限申请内容。

调用关系:这是 Approvable 接口的一部分;当命令需要额外权限时,上层审批界面或策略逻辑会读取它,向用户展示更接近原始 shell 命令的内容。

调用图:调用 1 个内部函数(bash)。

UnifiedExecRuntime::sandbox_permissions219–221 ↗
fn sandbox_permissions(&self, req: &UnifiedExecRequest) -> SandboxPermissions

作用:返回请求声明的沙箱权限,不擅自降级或升级。这样父流程传来的安全意图能被保留下来。

数据流:进去的是 UnifiedExecRequest→ 函数直接读取 sandbox_permissions→ 出来的是同一个权限值,不改任何状态。

调用关系:这是 Approvable 接口的一部分;测试 tests::zsh_fork_first_attempt_preserves_parent_sandbox_override 和 tests::zsh_fork_first_attempt_preserves_additional_permissions_request 都在确认它不会因为执行模式不同而改变权限。

UnifiedExecRuntime::sandbox_cwd225–227 ↗
fn sandbox_cwd(&self, req: &'b UnifiedExecRequest) -> Option<&'b AbsolutePathBuf>

作用:告诉沙箱框架真正应该信任的沙箱工作目录。它避免只看普通 cwd,从而跑到不该跑的位置。

数据流:进去的是 UnifiedExecRequest→ 函数取出 sandbox_cwd 的引用→ 出来的是 Some(&sandbox_cwd),没有复制目录也不改请求。

调用关系:这是 ToolRuntime 接口的一部分;测试 tests::unified_exec_uses_the_trusted_sandbox_cwd 专门检查它使用的是 sandbox_cwd,而不是普通 cwd。

UnifiedExecRuntime::network_approval_spec229–256 ↗
fn network_approval_spec(
        &self,
        req: &UnifiedExecRequest,
        ctx: &ToolCtx,
    ) -> Option<NetworkApprovalSpec>

作用:如果这次沙箱执行可能需要网络,它会准备一份“延后审批”的网络申请说明。这样命令只有真正触网时才触发网络审批。

数据流:进去的是 UnifiedExecRequest 和 ToolCtx(工具上下文,包含当前会话、调用编号、工具名等)→ 函数先取当前文件系统沙箱策略,保留被拒读的限制,再根据沙箱权限判断是否有受控网络;如果没有可管理网络就返回 None;如果有,就把命令、目录、权限、理由、tty 等信息打包成 NetworkApprovalSpec→ 出来的是可选的网络审批规格。

调用关系:这是 ToolRuntime 在执行前询问网络策略时用的;它调用 sandbox_permissions_preserving_denied_reads 保住读限制,调用 managed_network_for_sandbox_permissions 找网络代理,并用 flat_tool_name 给触发记录写上工具名。

调用图:调用 3 个内部函数(flat_tool_name, managed_network_for_sandbox_permissions, sandbox_permissions_preserving_denied_reads)。

UnifiedExecRuntime::run258–419 ↗
async fn run(
        &mut self,
        req: &UnifiedExecRequest,
        attempt: &SandboxAttempt<'_>,
        ctx: &ToolCtx,
    ) -> Result<UnifiedExecProcess, ToolError>

作用:真正把一条通过审批和沙箱计算的命令跑起来。它不是自己 fork 进程,而是把命令、环境、沙箱、网络等都组装好,再交给 UnifiedExecProcessManager 开会话。

数据流:进去的是 UnifiedExecRequest、一次 SandboxAttempt(沙箱尝试,带着本次权限和运行环境)以及 ToolCtx→ 函数选择 shell,计算 shell 快照,按沙箱权限过滤环境变量,接入受控网络;在本地环境会补 PATH 和可能包 shell 快照,在 Windows 提权沙箱里会禁用 PowerShell profile,并给 PowerShell 脚本加 UTF-8 前缀;如果配置了 zsh-fork,会先尝试用 zsh-fork 后端准备更特殊的启动请求,失败或条件不满足再回退直接执行;最后构造沙箱命令和 ExecOptions,生成执行环境,交给 manager.open_session_with_exec_env→ 出来的是 UnifiedExecProcess;如果沙箱拒绝,会转换成上层能理解的 ToolError。

调用关系:这是整个文件的主干流程。它调用 unified_exec_options 准备超时和取消规则,调用 exec_env_for_sandbox_permissions、managed_network_for_sandbox_permissions、build_sandbox_command 等拼执行环境;可选地把活交给 zsh_fork_backend::maybe_prepare_unified_exec;最终统一交给进程管理器启动。

调用图:调用 11 个内部函数(apply_package_path_prepend, apply_zsh_fork_path_prepend, build_sandbox_command, disable_powershell_profile_for_elevated_windows_sandbox, exec_env_for_sandbox_permissions, maybe_wrap_shell_lc_with_snapshot, unified_exec_options, env_for, managed_network_for_sandbox_permissions, sandbox_permissions_preserving_denied_reads (+1 more));外部调用 7 个(new, default, Rejected, open_session_with_exec_env, matches!, warn!, maybe_prepare_unified_exec)。

tests::test_turn_environment435–442 ↗
fn test_turn_environment(cwd: AbsolutePathBuf) -> TurnEnvironment

作用:给测试造一个假的 TurnEnvironment,也就是“这一轮执行发生在哪个环境里”的上下文。测试不用真的连远程环境,也能模拟本地执行。

数据流:进去的是一个绝对路径 cwd→ 函数创建默认测试环境,把 cwd 转成 PathUri,再调用 TurnEnvironment::new→ 出来的是可给请求使用的 TurnEnvironment。

调用关系:这是测试辅助函数;tests::unified_exec_uses_the_trusted_sandbox_cwd 和 tests::test_request 会用它快速搭建统一执行请求。

调用图:调用 3 个内部函数(new, default_for_tests, from_abs_path);外部调用 1 个(new)。

tests::unified_exec_options_combines_default_timeout_with_network_denial_cancellation445–464 ↗
fn unified_exec_options_combines_default_timeout_with_network_denial_cancellation()

作用:测试 unified_exec_options 会同时保留默认超时和网络拒绝取消信号。也就是既不能无限跑,也能在网络被拒时马上停。

数据流:进去没有外部输入→ 测试创建一个 CancellationToken,传给 unified_exec_options,然后检查输出捕获策略、超时时长和取消令牌;最后主动 cancel,确认返回选项里的令牌也变成已取消→ 出来的是测试通过或 panic 失败。

调用关系:它直接覆盖 unified_exec_options 的关键安全行为,防止后续修改时不小心丢掉网络拒绝取消机制。

调用图:调用 1 个内部函数(unified_exec_options);外部调用 4 个(new, assert!, assert_eq!, panic!)。

tests::unified_exec_uses_the_trusted_sandbox_cwd467–501 ↗
async fn unified_exec_uses_the_trusted_sandbox_cwd()

作用:测试运行器报告给沙箱的目录必须是 sandbox_cwd,而不是普通 cwd。这个区别能防止命令在错误的信任边界里执行。

数据流:进去没有外部输入→ 测试创建两个临时目录,一个当普通 cwd,一个当 sandbox_cwd;再构造 UnifiedExecRuntime 和 UnifiedExecRequest→ 调用 runtime.sandbox_cwd 后检查返回值确实指向 sandbox_cwd。

调用关系:它调用 UnifiedExecRuntime::new、tests::test_turn_environment 和临时目录工具,专门保护 UnifiedExecRuntime::sandbox_cwd 的行为。

调用图:调用 3 个内部函数(new, default, try_from);外部调用 5 个(new, assert_eq!, test_turn_environment, tempdir, vec!)。

tests::zsh_fork_first_attempt_preserves_parent_sandbox_override504–526 ↗
async fn zsh_fork_first_attempt_preserves_parent_sandbox_override()

作用:测试 zsh-fork 模式不会吞掉父流程要求的“需要提权沙箱”设置。执行模式换了,安全权限不能跟着变味。

数据流:进去没有外部输入→ 测试用 RequireEscalated 权限造请求,再分别创建直接执行和 zsh-fork 两种运行器→ 读取 sandbox_permissions,并断言两边都还是 RequireEscalated。

调用关系:它调用 tests::test_request、tests::zsh_fork_mode 和 UnifiedExecRuntime::new,覆盖 UnifiedExecRuntime::sandbox_permissions 在不同 shell 模式下的一致性。

调用图:调用 2 个内部函数(new, default);外部调用 3 个(assert_eq!, test_request, zsh_fork_mode)。

tests::zsh_fork_first_attempt_preserves_additional_permissions_request529–545 ↗
async fn zsh_fork_first_attempt_preserves_additional_permissions_request()

作用:测试 zsh-fork 模式会保留“带附加权限但仍在沙箱内”的请求。也就是说,额外权限申请不能被误变成完全绕过沙箱。

数据流:进去没有外部输入→ 测试用 WithAdditionalPermissions 造请求,创建 zsh-fork 运行器→ 调用 sandbox_permissions 并检查返回值仍是 WithAdditionalPermissions。

调用关系:它依赖 tests::test_request 和 tests::zsh_fork_mode 构造场景,保护 UnifiedExecRuntime::sandbox_permissions 对附加权限请求的原样传递。

调用图:调用 2 个内部函数(new, default);外部调用 3 个(assert_eq!, test_request, zsh_fork_mode)。

tests::zsh_fork_execpolicy_allow_preserves_parent_sandbox_override548–567 ↗
async fn zsh_fork_execpolicy_allow_preserves_parent_sandbox_override()

作用:测试如果执行策略已经允许绕过沙箱,zsh-fork 模式不能把这个决定改掉。它保护的是上游策略决定的权威性。

数据流:进去没有外部输入→ 测试用 ExecApprovalRequirement::Skip 且 bypass_sandbox 为 true 构造请求,再创建 zsh-fork 运行器→ 调用 exec_approval_requirement 并检查返回值完整一致。

调用关系:它调用 tests::test_request、tests::zsh_fork_mode 和 UnifiedExecRuntime::new,重点覆盖 UnifiedExecRuntime::exec_approval_requirement 不篡改策略。

调用图:调用 2 个内部函数(new, default);外部调用 3 个(assert_eq!, test_request, zsh_fork_mode)。

tests::test_request569–595 ↗
fn test_request(
        sandbox_permissions: SandboxPermissions,
        exec_approval_requirement: ExecApprovalRequirement,
    ) -> UnifiedExecRequest

作用:给多项测试快速造一个统一执行请求。它把命令、目录、环境、权限和审批要求都填成可用的默认测试值。

数据流:进去的是 SandboxPermissions 和 ExecApprovalRequirement→ 函数读取当前目录作为 cwd 和 sandbox_cwd,构造一条 zsh -c 'echo hi' 的请求,并填入空环境、无网络、无附加权限等默认值→ 出来的是 UnifiedExecRequest。

调用关系:这是测试里的请求工厂;zsh-fork 相关测试都用它减少重复代码,再各自检查权限或审批要求是否被运行器保留。

调用图:调用 1 个内部函数(try_from);外部调用 4 个(new, test_turn_environment, current_dir, vec!)。

tests::zsh_fork_mode597–604 ↗
fn zsh_fork_mode() -> UnifiedExecShellMode

作用:给测试造一个 zsh-fork 启动模式配置。它不真的要求这些程序存在,只需要形成绝对路径,让运行器进入对应模式。

数据流:进去没有外部输入→ 函数读取当前目录,把 current_dir/zsh 和 current_dir/execve-wrapper 转成绝对路径→ 出来的是 UnifiedExecShellMode::ZshFork 配置。

调用关系:zsh-fork 相关测试调用它来创建 UnifiedExecRuntime,确保测试覆盖的是 zsh-fork 模式下的策略保留行为。

调用图:调用 1 个内部函数(try_from);外部调用 2 个(current_dir, ZshFork)。