app-server 和 daemon 传输启动
这一阶段像给 app-server 接上电源和电话线,主要是启动和幕后支撑。daemon 管启动、停止、重启,用 PID 文件防止重复开。server 总开关装好配置、日志、数据库和退出。传输层提供标准输入输出、本机 socket、WebSocket,并做进门鉴权。远程控制负责登记本机、决定开关、断线重连和转发消息。客户端门面让 CLI、TUI 用同一套方式发请求、收事件,初始化和发信站完成握手与回信。
守护进程生命周期与进程后端
这些文件定义守护进程的顶层编排、其托管的 app-server 进程后端,以及用于监督服务器就绪状态的低层探测客户端。
app-server-daemon/src/lib.rs源码 ↗
app-server 是一个在后台跑的服务,别的程序通过一个本地 socket(可以理解成电脑里的专用小门)和它说话。这个文件负责守住这扇门背后的生命周期:服务没跑就启动,已经跑了就复用,需要重启就先停再起。它还会记录 pid 文件(进程号码的小纸条)、设置文件和锁文件。锁文件像厕所门上的“有人”牌,防止两个命令同时启动或停止同一个服务。文件里最核心的是 Daemon,它把 socket 路径、pid 路径、设置路径、托管安装的 Codex 程序路径放在一起,然后调用 backend、client、settings、update_loop 等模块完成实际工作。重要的是,它会区分“我管理的服务”和“别人启动的服务”:如果发现 socket 上有服务,但不是这个守护进程管理的,它会拒绝乱停乱重启,避免误伤用户自己的进程。
probe_app_server_version79–81 ↗
async fn probe_app_server_version(socket_path: &Path) -> Result<String>
作用:去问一个已经存在的 app-server:“你是什么版本?”常用于不想启动新服务,只想确认当前服务状态的时候。
数据流:输入一个 socket 路径 → 通过 client::probe 连过去询问 → 返回服务报告的 app-server 版本字符串;它不改动文件,也不启动或停止进程。
调用关系:它把探测工作交给 probe,自己只取回结果里的版本字段,是外部代码快速查看服务版本的轻量入口。
调用图:调用 1 个内部函数(probe)。
RemoteControlMode::is_enabled131–133 ↗
fn is_enabled(self) -> bool
作用:把“远程控制模式”翻译成一个简单的是/否。有人要保存设置或判断是否开启时会用它。
数据流:输入 Enabled 或 Disabled → 判断是不是 Enabled → 输出 true 或 false,不改动任何状态。
调用关系:set_remote_control_locked 在修改设置前会调用它,把枚举选项变成 settings 里能保存的布尔值。
调用图:被 1 处调用(set_remote_control_locked);外部调用 1 个(matches!)。
run190–193 ↗
async fn run(command: LifecycleCommand) -> Result<LifecycleOutput>
作用:这是外部执行启动、停止、重启、查版本这些生命周期命令的统一入口。
数据流:输入一个 LifecycleCommand 命令 → 先确认当前系统支持,再从环境里组装 Daemon → 交给 Daemon 执行并返回结果。
调用关系:它先调用 ensure_supported_platform 做平台检查,再调用 Daemon::from_environment 准备路径和配置,是命令行或上层接口进入守护进程控制逻辑的大门。
调用图:调用 2 个内部函数(from_environment, ensure_supported_platform)。
bootstrap195–198 ↗
async fn bootstrap(options: BootstrapOptions) -> Result<BootstrapOutput>
作用:做一次完整初始化:准备托管版 Codex、写入设置、启动 app-server 和更新循环。
数据流:输入 BootstrapOptions,例如是否启用远程控制 → 检查平台并创建 Daemon → 返回包含 socket、版本、是否自动更新等信息的 BootstrapOutput。
调用关系:它和 run 一样先过 ensure_supported_platform,再用 Daemon::from_environment 找到运行环境,之后进入 Daemon 的 bootstrap 流程。
调用图:调用 2 个内部函数(from_environment, ensure_supported_platform)。
ensure_remote_control_started200–205 ↗
async fn ensure_remote_control_started() -> Result<RemoteControlStartOutput>
作用:确保 app-server 已经以“远程控制可用”的方式启动。没初始化就初始化,已初始化就打开远程控制并启动服务。
数据流:没有额外输入 → 检查平台、创建 Daemon → 返回 RemoteControlStartOutput,说明是刚 bootstrap 起来的,还是已有服务被启动或复用。
调用关系:它调用 ensure_supported_platform 和 Daemon::from_environment,是远程控制启动流程的顶层入口。
调用图:调用 2 个内部函数(from_environment, ensure_supported_platform)。
ensure_remote_control_ready207–212 ↗
async fn ensure_remote_control_ready() -> Result<RemoteControlReadyOutput>
作用:不只确保远程控制服务启动,还会真正连上去把远程控制打开到可用状态。
数据流:没有额外输入 → 检查平台、创建 Daemon → 返回 daemon 启动结果和远程控制连接状态。
调用关系:它先走 ensure_supported_platform 和 Daemon::from_environment,然后进入 Daemon::ensure_remote_control_ready;适合上层想“一步到位可连接”的场景。
调用图:调用 2 个内部函数(from_environment, ensure_supported_platform)。
enable_remote_control_on_socket214–226 ↗
async fn enable_remote_control_on_socket(
socket_path: &Path,
connect_timeout: Duration,
connect_retry_delay: Duration,
) -> Result<RemoteControlReadyStatus>
作用:对指定 socket 上的 app-server 打开远程控制,并允许连接失败时短暂重试。
数据流:输入 socket 路径、最长等待时间、重试间隔 → 检查平台后反复尝试连接并发送开启远程控制的请求 → 返回远程控制是否连好的状态。
调用关系:它调用 enable_remote_control_with_connect_retry,把网络/本地 socket 的重试细节交给 remote_control_client。
调用图:调用 2 个内部函数(ensure_supported_platform, enable_remote_control_with_connect_retry)。
set_remote_control228–231 ↗
async fn set_remote_control(mode: RemoteControlMode) -> Result<RemoteControlOutput>
作用:把远程控制设置成开启或关闭。它会同时处理设置文件和正在运行的服务。
数据流:输入 Enabled 或 Disabled → 检查平台、创建 Daemon → 返回 RemoteControlOutput,说明状态是刚改变还是本来如此。
调用关系:它是外部改远程控制开关的入口,先调用 ensure_supported_platform 和 Daemon::from_environment,再进入 Daemon::set_remote_control。
调用图:调用 2 个内部函数(from_environment, ensure_supported_platform)。
run_pid_update_loop233–236 ↗
async fn run_pid_update_loop() -> Result<()>
作用:运行基于 pid 的更新循环。这个循环通常用于后台检查托管安装的程序是否需要刷新。
数据流:没有输入 → 检查平台 → 调用 update_loop::run 开始更新循环;正常情况下返回空结果,出错就返回错误。
调用关系:它是更新子进程或更新模式的入口,先确保平台支持,再把长期运行的工作交给 update_loop。
调用图:调用 2 个内部函数(ensure_supported_platform, run)。
ensure_supported_platform244–248 ↗
fn ensure_supported_platform() -> Result<()>
作用:确认当前系统能不能使用这个守护进程生命周期功能。这里主要限制在 Unix 类系统上。
数据流:不读取业务输入 → 如果是支持的平台就返回成功;如果不是 Unix,就返回一条说明“不支持”的错误。
调用关系:run、bootstrap、远程控制相关入口、更新循环入口都会先调用它,避免在不支持的平台上做一半才失败。
调用图:被 7 处调用(bootstrap, enable_remote_control_on_socket, ensure_remote_control_ready, ensure_remote_control_started, run, run_pid_update_loop, set_remote_control);外部调用 1 个(anyhow!)。
Daemon::from_environment260–274 ↗
fn from_environment() -> Result<Self>
作用:根据当前用户环境,拼出守护进程需要用到的所有关键路径。
数据流:读取 CODEX_HOME 或默认 Codex 主目录 → 生成 socket、pid 文件、更新 pid 文件、锁文件、设置文件、托管 Codex 可执行文件路径 → 返回一个 Daemon 对象。
调用关系:run、bootstrap、set_remote_control、远程控制入口以及 update_once 会用它创建“带路径的控制器”;它调用 find_codex_home、app_server_control_socket_path 和 managed_codex_bin。
调用图:调用 1 个内部函数(managed_codex_bin);被 6 处调用(bootstrap, ensure_remote_control_ready, ensure_remote_control_started, run, set_remote_control, update_once);外部调用 2 个(app_server_control_socket_path, find_codex_home)。
Daemon::run276–292 ↗
async fn run(&self, command: LifecycleCommand) -> Result<LifecycleOutput>
作用:根据用户给的命令,把活儿分派给启动、重启、停止或查版本的具体步骤。
数据流:输入 LifecycleCommand → 对会改状态的命令先拿操作锁 → 调用 start、restart、stop 或 version → 返回统一格式的 LifecycleOutput。
调用关系:它是 Daemon 内部的命令分发器;外层 run 会进入这里,它再按命令把工作交给各个具体函数。
调用图:调用 5 个内部函数(acquire_operation_lock, restart, start, stop, version)。
Daemon::start294–330 ↗
async fn start(&self) -> Result<LifecycleOutput>
作用:启动 app-server;如果它已经在跑,就不重复启动,而是返回“已经在运行”。
数据流:读取设置文件和 socket 状态 → 如果 socket 可探测,直接返回已有版本;如果后台进程正在启动,就等它准备好;否则确认托管 Codex 存在、启动新进程、等待 ready → 返回启动结果和版本。
调用关系:Daemon::run 和 Daemon::ensure_remote_control_started 会调用它;它会用 client::probe 检查服务,用 backend 启动进程,并通过 output 组装返回值。
调用图:调用 8 个内部函数(ensure_managed_codex_bin, load_settings, output, running_backend, running_backend_instance, start_managed_backend, wait_until_ready, probe);被 2 处调用(ensure_remote_control_started, run)。
Daemon::restart332–357 ↗
async fn restart(&self) -> Result<LifecycleOutput>
作用:重启由这个守护进程管理的 app-server。它会拒绝重启别人启动的服务。
数据流:读取设置并探测 socket → 如果有服务但不是自己管理的就报错 → 确认托管 Codex 存在,停掉旧后端,启动新后端,等待 ready → 返回 Restarted 结果。
调用关系:Daemon::run 在收到 Restart 时调用它;它依赖 running_backend、running_backend_instance 判断归属,再用 start_managed_backend 和 wait_until_ready 完成重启。
调用图:调用 8 个内部函数(ensure_managed_codex_bin, load_settings, output, running_backend, running_backend_instance, start_managed_backend, wait_until_ready, probe);被 1 处调用(run);外部调用 1 个(anyhow!)。
Daemon::try_restart_if_running360–403 ↗
async fn try_restart_if_running(
&self,
mode: RestartMode,
updater_refresh_mode: UpdaterRefreshMode,
managed_codex_bin: &Path,
) -> Result<RestartIfRunningOutcome>
作用:更新器用它“温和地”重启正在运行的 app-server:能拿到锁且确实需要重启才动手。
数据流:输入重启模式、更新器刷新模式、要使用的托管 Codex 路径 → 尝试锁文件,读取设置,检查是否有自己管理的后端和当前版本 → 决定忙、不运行、未 ready、已是最新版或已重启;必要时还会重新执行更新器。
调用关系:它调用 open_operation_lock_file、try_lock_file、restart_decision、should_reexec_updater 等;主要服务于自动更新流程,而不是普通用户命令。
调用图:调用 11 个内部函数(load_settings, open_operation_lock_file, running_backend_instance, start_managed_backend_with_bin, wait_until_ready, probe, managed_codex_version, restart_decision, should_reexec_updater, try_lock_file (+1 more));外部调用 1 个(anyhow!)。
Daemon::stop405–433 ↗
async fn stop(&self) -> Result<LifecycleOutput>
作用:停止由这个守护进程管理的 app-server。它不会随便杀掉不属于自己的服务。
数据流:读取设置 → 如果找到自己管理的后端,就停止它并返回 Stopped;如果 socket 上有服务但不是自己管理的,返回错误;如果本来没跑,返回 NotRunning。
调用关系:Daemon::run 在收到 Stop 时调用它;它通过 running_backend_instance 找自己管理的进程,通过 probe 识别是否存在外部服务。
调用图:调用 4 个内部函数(load_settings, output, running_backend_instance, probe);被 1 处调用(run);外部调用 1 个(anyhow!)。
Daemon::version435–446 ↗
async fn version(&self) -> Result<LifecycleOutput>
作用:查询当前正在运行的 app-server 版本,并说明它是不是由守护进程管理。
数据流:读取设置 → 通过 socket 探测服务版本 → 判断后端类型 → 返回 Running 状态和 app-server 版本。
调用关系:Daemon::run 在收到 Version 时调用它;它依赖 client::probe 拿版本,用 running_backend 判断归属,最后用 output 包装结果。
调用图:调用 4 个内部函数(load_settings, output, running_backend, probe);被 1 处调用(run)。
Daemon::wait_until_ready448–463 ↗
async fn wait_until_ready(&self) -> Result<client::ProbeInfo>
作用:启动或重启后,反复等待 app-server 真的能响应,而不是刚创建进程就算成功。
数据流:读取 socket 路径和当前时间 → 每隔很短时间 probe 一次,最多等 10 秒 → 成功返回 ProbeInfo,超时则附加诊断信息后报错。
调用关系:start、restart、bootstrap_locked、set_remote_control_locked、try_restart_if_running 都会在启动后调用它;失败时它请 app_server_not_ready_context 生成更好懂的错误上下文。
调用图:调用 2 个内部函数(app_server_not_ready_context, probe);被 5 处调用(bootstrap_locked, restart, set_remote_control_locked, start, try_restart_if_running);外部调用 2 个(now, sleep)。
Daemon::app_server_not_ready_context465–473 ↗
async fn app_server_not_ready_context(&self) -> String
作用:当 app-server 启动后一直没准备好时,生成一段有用的排错说明。
数据流:读取 socket 路径、托管 Codex 信息和 stderr 日志尾部 → 拼成一段文字 → 返回给错误信息使用。
调用关系:wait_until_ready 超时后调用它;它再调用 append_daemon_app_server_context 和 append_stderr_log_tail_context,把“用的是哪个程序”和“最后报了什么错”放进去。
调用图:调用 2 个内部函数(append_daemon_app_server_context, append_stderr_log_tail_context);被 1 处调用(wait_until_ready);外部调用 1 个(format!)。
Daemon::append_daemon_app_server_context475–484 ↗
async fn append_daemon_app_server_context(&self, context: &mut String)
作用:把守护进程使用的 app-server 程序路径和版本追加到错误说明里。
数据流:输入一段可修改的文字 → 尝试读取托管 Codex 版本,失败就写 unknown → 把路径和版本追加到文字末尾。
调用关系:app_server_not_ready_context 会调用它;它把 managed_codex_version_best_effort 的结果变成用户能看懂的诊断信息。
调用图:调用 1 个内部函数(managed_codex_version_best_effort);被 1 处调用(app_server_not_ready_context);外部调用 1 个(format!)。
Daemon::bootstrap486–489 ↗
async fn bootstrap(&self, options: BootstrapOptions) -> Result<BootstrapOutput>
作用:Daemon 层的初始化入口,会先拿锁,再执行真正的初始化。
数据流:输入 BootstrapOptions → 获取操作锁,防止同时初始化 → 调用 bootstrap_locked → 返回 BootstrapOutput。
调用关系:外层 bootstrap 最终会走到这里;它把并发保护做好后,把具体步骤交给 bootstrap_locked。
调用图:调用 2 个内部函数(acquire_operation_lock, bootstrap_locked)。
Daemon::ensure_remote_control_started491–508 ↗
async fn ensure_remote_control_started(&self) -> Result<RemoteControlStartOutput>
作用:在 Daemon 内部确保远程控制模式下的服务已经启动。
数据流:读取设置和更新循环状态 → 如果已经 bootstrap,就打开远程控制设置并启动服务;如果还没 bootstrap,就用远程控制开启的选项初始化 → 返回 Start 或 Bootstrap 类型的结果。
调用关系:Daemon::ensure_remote_control_ready 会调用它;它自己会用 acquire_operation_lock 防止并发,再调用 is_bootstrapped、set_remote_control_locked、start 或 bootstrap_locked。
调用图:调用 6 个内部函数(acquire_operation_lock, bootstrap_locked, is_bootstrapped, load_settings, set_remote_control_locked, start);被 1 处调用(ensure_remote_control_ready);外部调用 2 个(Bootstrap, Start)。
Daemon::ensure_remote_control_ready510–518 ↗
async fn ensure_remote_control_ready(&self) -> Result<RemoteControlReadyOutput>
作用:确保远程控制不只是配置好了,而且真的能连上并启用。
数据流:先得到 daemon 启动结果 → 再通过 socket 发送启用远程控制的请求 → 返回 daemon 信息和远程控制连接状态。
调用关系:外层 ensure_remote_control_ready 会进入这里;它先调用 Daemon::ensure_remote_control_started,再把连接动作交给 remote_control_client::enable_remote_control。
调用图:调用 2 个内部函数(ensure_remote_control_started, enable_remote_control)。
Daemon::set_remote_control520–523 ↗
async fn set_remote_control(&self, mode: RemoteControlMode) -> Result<RemoteControlOutput>
作用:Daemon 层修改远程控制开关的安全入口。
数据流:输入远程控制模式 → 先拿操作锁 → 调用 set_remote_control_locked 完成读取设置、保存设置、必要时重启等工作 → 返回结果。
调用关系:外层 set_remote_control 会调用它;它只负责加锁和转交,真正细节在 set_remote_control_locked。
调用图:调用 2 个内部函数(acquire_operation_lock, set_remote_control_locked)。
Daemon::set_remote_control_locked525–582 ↗
async fn set_remote_control_locked(
&self,
mode: RemoteControlMode,
) -> Result<RemoteControlOutput>
作用:真正执行远程控制开启或关闭。它会更新设置,并在服务正在运行时重启服务让新设置生效。
数据流:读取旧设置和当前后端 → 把目标模式转成布尔值;如果状态没变,就尽量对运行中的服务发送开启/关闭请求并返回 Already 状态;如果状态改变,就保存设置,必要时停止旧服务、启动新服务、等待 ready → 返回 RemoteControlOutput。
调用关系:Daemon::set_remote_control 和 Daemon::ensure_remote_control_started 会调用它;它用 RemoteControlMode::is_enabled、already_remote_control_status、remote_control_status 和 remote_control_output 组织状态。
调用图:调用 12 个内部函数(ensure_managed_codex_bin, load_settings, remote_control_output, running_backend_instance, start_managed_backend, wait_until_ready, is_enabled, already_remote_control_status, probe, disable_remote_control (+2 more));被 2 处调用(ensure_remote_control_started, set_remote_control);外部调用 1 个(anyhow!)。
Daemon::bootstrap_locked584–624 ↗
async fn bootstrap_locked(&self, options: BootstrapOptions) -> Result<BootstrapOutput>
作用:在已经拿到锁的前提下,完成守护进程的完整初始化和启动。
数据流:确认托管 Codex 可执行文件存在 → 写入 settings.json → 如果已有自己管理的后端就停掉 → 启动 app-server 后端,再启动更新循环后端 → 等待服务 ready,收集版本和路径信息 → 返回 BootstrapOutput。
调用关系:Daemon::bootstrap 和 Daemon::ensure_remote_control_started 会调用它;它连接 settings、backend、update_loop 和 client 探测,是初始化流程的核心。
调用图:调用 9 个内部函数(backend_paths, ensure_managed_codex_bin, managed_codex_version_best_effort, running_backend, running_backend_instance, wait_until_ready, pid_backend, pid_update_loop_backend, probe);被 2 处调用(bootstrap, ensure_remote_control_started);外部调用 3 个(clone, anyhow!, env!)。
Daemon::running_backend626–631 ↗
async fn running_backend(&self, settings: &DaemonSettings) -> Result<Option<BackendKind>>
作用:回答一个简单问题:现在有没有这个守护进程管理的后端在跑?有的话返回后端类型。
数据流:输入当前设置 → 调用 running_backend_instance 找实际后端对象 → 如果找到就转成 BackendKind::Pid,否则返回 None。
调用关系:start、restart、version、bootstrap_locked 会用它做归属判断;它是 running_backend_instance 的简化包装。
调用图:调用 1 个内部函数(running_backend_instance);被 4 处调用(bootstrap_locked, restart, start, version)。
Daemon::running_backend_instance633–642 ↗
async fn running_backend_instance(
&self,
settings: &DaemonSettings,
) -> Result<Option<backend::PidBackend>>
作用:找出当前是否有基于 pid 文件管理的 app-server 后端正在启动或运行。
数据流:输入设置 → 组装 BackendPaths,创建 PidBackend → 检查它是否正在启动或运行 → 返回 Some 后端对象或 None。
调用关系:start、restart、stop、set_remote_control_locked、try_restart_if_running 等都会调用它;它是判断“是不是我管理的进程”的关键。
调用图:调用 2 个内部函数(backend_paths, pid_backend);被 7 处调用(bootstrap_locked, restart, running_backend, set_remote_control_locked, start, stop, try_restart_if_running)。
Daemon::start_managed_backend644–647 ↗
async fn start_managed_backend(&self, settings: &DaemonSettings) -> Result<Option<u32>>
作用:用默认的托管 Codex 程序启动 app-server 后端。
数据流:输入设置 → 使用 Daemon 自己记录的 managed_codex_bin → 转交给 start_managed_backend_with_bin → 返回可能的 pid。
调用关系:start、restart、set_remote_control_locked 会调用它;它是默认启动路径的便捷包装。
调用图:调用 1 个内部函数(start_managed_backend_with_bin);被 3 处调用(restart, set_remote_control_locked, start)。
Daemon::start_managed_backend_with_bin649–657 ↗
async fn start_managed_backend_with_bin(
&self,
settings: &DaemonSettings,
managed_codex_bin: &Path,
) -> Result<Option<u32>>
作用:用指定的 Codex 可执行文件启动 app-server,给自动更新场景留出替换程序路径的能力。
数据流:输入设置和 Codex 程序路径 → 生成 BackendPaths,创建 PidBackend → 调用后端启动 → 返回新进程 pid 或空值。
调用关系:start_managed_backend 和 try_restart_if_running 会调用它;实际启动动作交给 backend::pid_backend 创建出来的后端。
调用图:调用 2 个内部函数(backend_paths_with_bin, pid_backend);被 2 处调用(start_managed_backend, try_restart_if_running)。
Daemon::is_bootstrapped659–662 ↗
async fn is_bootstrapped(&self, settings: &DaemonSettings) -> Result<bool>
作用:判断守护进程是否已经完成过初始化,依据是更新循环后端是否在启动或运行。
数据流:输入设置 → 组装路径,创建更新循环后端 → 检查它是否正在启动或运行 → 返回 true 或 false。
调用关系:Daemon::ensure_remote_control_started 用它决定是走已有安装的启动流程,还是走第一次 bootstrap。
调用图:调用 2 个内部函数(backend_paths, pid_update_loop_backend);被 1 处调用(ensure_remote_control_started)。
Daemon::ensure_managed_codex_bin664–677 ↗
fn ensure_managed_codex_bin(&self) -> Result<()>
作用:确认固定位置上的托管版 Codex 程序真的存在。没有它,守护进程就不知道该启动哪个 app-server。
数据流:检查 managed_codex_bin 路径是否是文件 → 是就成功返回;不是就返回带安装命令的错误提示。
调用关系:start、restart、bootstrap_locked、set_remote_control_locked 在需要启动或重启服务前都会调用它,避免后面报更难懂的错误。
调用图:被 4 处调用(bootstrap_locked, restart, set_remote_control_locked, start);外部调用 3 个(display, is_file, anyhow!)。
Daemon::managed_codex_version_best_effort685–687 ↗
async fn managed_codex_version_best_effort(&self) -> Option<String>
作用:尽力读取托管 Codex 的版本;读不到也不让主流程失败。
数据流:读取 managed_codex_bin 路径 → 调用 managed_codex_version → 成功返回版本,失败返回 None。
调用关系:output、bootstrap_locked、append_daemon_app_server_context 会用它补充展示信息和诊断信息。
调用图:调用 1 个内部函数(managed_codex_version);被 3 处调用(append_daemon_app_server_context, bootstrap_locked, output)。
Daemon::backend_paths689–691 ↗
fn backend_paths(&self, settings: &DaemonSettings) -> BackendPaths
作用:用默认托管 Codex 路径生成后端需要的一组路径和开关。
数据流:输入设置 → 把默认 managed_codex_bin 和设置一起交给 backend_paths_with_bin → 返回 BackendPaths。
调用关系:bootstrap_locked、is_bootstrapped、running_backend_instance 会调用它;它是创建后端对象前的路径打包器。
调用图:调用 1 个内部函数(backend_paths_with_bin);被 3 处调用(bootstrap_locked, is_bootstrapped, running_backend_instance)。
Daemon::backend_paths_with_bin693–704 ↗
fn backend_paths_with_bin(
&self,
settings: &DaemonSettings,
managed_codex_bin: &Path,
) -> BackendPaths
作用:把启动后端所需的路径集中打包,包括 Codex 程序、pid 文件、更新 pid 文件和远程控制开关。
数据流:输入设置和 Codex 程序路径 → 复制相关路径和布尔开关 → 输出 BackendPaths,不启动任何进程。
调用关系:backend_paths 和 start_managed_backend_with_bin 会调用它;后续 backend::pid_backend 会用这些路径真正控制进程。
调用图:被 2 处调用(backend_paths, start_managed_backend_with_bin);外部调用 2 个(to_path_buf, clone)。
Daemon::load_settings706–708 ↗
async fn load_settings(&self) -> Result<DaemonSettings>
作用:从 settings.json 读取守护进程设置,比如远程控制是否开启。
数据流:读取 settings_file 路径 → 调用 DaemonSettings::load → 返回设置对象。
调用关系:start、restart、stop、version、set_remote_control_locked、try_restart_if_running、ensure_remote_control_started 都要先读设置再决定怎么做。
调用图:调用 1 个内部函数(load);被 7 处调用(ensure_remote_control_started, restart, set_remote_control_locked, start, stop, try_restart_if_running, version)。
Daemon::acquire_operation_lock710–723 ↗
async fn acquire_operation_lock(&self) -> Result<tokio::fs::File>
作用:拿到操作锁,保证同一时间只有一个启动、停止、重启或改设置操作在进行。
数据流:打开锁文件 → 循环尝试加锁,最多等 75 秒 → 成功返回持有锁的文件句柄;超时返回错误。
调用关系:Daemon::run、Daemon::bootstrap、Daemon::set_remote_control、Daemon::ensure_remote_control_started 会调用它;它使用 open_operation_lock_file 和 try_lock_file。
调用图:调用 2 个内部函数(open_operation_lock_file, try_lock_file);被 4 处调用(bootstrap, ensure_remote_control_started, run, set_remote_control);外部调用 3 个(anyhow!, now, sleep)。
Daemon::open_operation_lock_file725–746 ↗
async fn open_operation_lock_file(&self) -> Result<tokio::fs::File>
作用:准备锁文件本身:确保目录存在,并打开 daemon.lock。
数据流:读取 operation_lock_file 路径 → 创建父目录 → 打开或创建锁文件 → 返回文件句柄。
调用关系:acquire_operation_lock 和 try_restart_if_running 会调用它;真正加锁由 try_lock_file 完成。
调用图:被 2 处调用(acquire_operation_lock, try_restart_if_running);外部调用 3 个(parent, new, create_dir_all)。
Daemon::output748–766 ↗
async fn output(
&self,
status: LifecycleStatus,
backend: Option<BackendKind>,
pid: Option<u32>,
app_server_version: Option<String>,
) -> LifecycleOutput
作用:把生命周期操作的结果整理成统一的返回结构,方便外部显示或转成 JSON。
数据流:输入状态、后端类型、pid、app-server 版本 → 补充托管 Codex 路径、托管版本、socket 路径、CLI 版本 → 返回 LifecycleOutput。
调用关系:start、restart、stop、version 都用它生成结果;它会调用 managed_codex_version_best_effort 尽量补版本。
调用图:调用 1 个内部函数(managed_codex_version_best_effort);被 4 处调用(restart, start, stop, version);外部调用 2 个(clone, env!)。
Daemon::remote_control_output768–783 ↗
fn remote_control_output(
&self,
status: RemoteControlStatus,
backend: Option<BackendKind>,
remote_control_enabled: bool,
app_server_version: Option<String>,
作用:把远程控制开关操作的结果整理成统一返回结构。
数据流:输入远程控制状态、后端、开关布尔值、app-server 版本 → 补上 socket 路径和 CLI 版本 → 返回 RemoteControlOutput。
调用关系:set_remote_control_locked 在各种分支结束时调用它,保证外部拿到的格式一致。
调用图:被 1 处调用(set_remote_control_locked);外部调用 2 个(clone, env!)。
remote_control_status786–791 ↗
fn remote_control_status(mode: RemoteControlMode) -> RemoteControlStatus
作用:把目标远程控制模式转换成“刚刚开启”或“刚刚关闭”的状态。
数据流:输入 Enabled 或 Disabled → 输出 RemoteControlStatus::Enabled 或 RemoteControlStatus::Disabled。
调用关系:set_remote_control_locked 在设置确实发生变化后调用它,用来描述这次操作的结果。
调用图:被 1 处调用(set_remote_control_locked)。
already_remote_control_status793–798 ↗
fn already_remote_control_status(mode: RemoteControlMode) -> RemoteControlStatus
作用:把目标远程控制模式转换成“本来就已经开启”或“本来就已经关闭”的状态。
数据流:输入 Enabled 或 Disabled → 输出 AlreadyEnabled 或 AlreadyDisabled。
调用关系:set_remote_control_locked 在发现设置没有变化时调用它,让返回结果准确表达“没有新改动”。
调用图:被 1 处调用(set_remote_control_locked)。
restart_decision801–815 ↗
fn restart_decision(
mode: RestartMode,
info: Option<&client::ProbeInfo>,
managed_version: Option<&str>,
) -> RestartDecision
作用:自动更新时判断是否需要重启 app-server。
数据流:输入重启模式、当前探测信息、托管程序版本 → 如果要求只在版本变化时重启但服务没 ready,则返回 NotReady;如果版本相同则 AlreadyCurrent;其他情况返回 Restart。
调用关系:try_restart_if_running 调用它决定下一步是跳过、等待、还是停旧起新。
调用图:被 1 处调用(try_restart_if_running)。
should_reexec_updater818–824 ↗
fn should_reexec_updater(
updater_refresh_mode: UpdaterRefreshMode,
outcome: RestartIfRunningOutcome,
) -> bool
作用:判断更新器自己是否应该重新执行,以便使用新版本的托管程序。
数据流:输入更新器刷新模式和重启结果 → 只有模式要求“托管程序变化后重执行”且刚刚成功重启时,才返回 true。
调用关系:try_restart_if_running 在完成重启判断后调用它;如果返回 true,就会触发 update_loop 里的 reexec。
调用图:被 1 处调用(try_restart_if_running)。
try_lock_file843–845 ↗
fn try_lock_file(_file: &tokio::fs::File) -> Result<bool>
作用:尝试给锁文件加独占锁。独占锁就是“一次只能一个人拿”的锁。
数据流:输入一个打开的文件 → 在 Unix 上调用系统 flock 尝试非阻塞加锁 → 成功返回 true,被别人占用返回 false,其他系统错误则报错。
调用关系:acquire_operation_lock 会反复调用它直到拿到锁或超时;try_restart_if_running 也用它快速判断更新器现在能不能动手。
调用图:被 2 处调用(acquire_operation_lock, try_restart_if_running);外部调用 3 个(as_raw_fd, last_os_error, flock)。
tests::remote_control_status_uses_camel_case_json869–874 ↗
fn remote_control_status_uses_camel_case_json()
作用:测试远程控制状态转成 JSON 时使用 camelCase,也就是 alreadyEnabled 这种前小后大的写法。
数据流:把 RemoteControlStatus::AlreadyEnabled 序列化成 JSON 字符串 → 和期望的 "alreadyEnabled" 比较 → 不一致测试失败。
调用关系:它验证对外输出格式,保护依赖 JSON 字段名字的客户端不被无意改坏。
调用图:外部调用 1 个(assert_eq!)。
tests::updater_reexec_waits_for_validated_restart877–891 ↗
fn updater_reexec_waits_for_validated_restart()
作用:测试更新器只有在确认 app-server 已成功重启后,才会重新执行自己。
数据流:把多种 RestartIfRunningOutcome 输入 should_reexec_updater → 收集布尔结果 → 和期望数组比较。
调用关系:它覆盖 should_reexec_updater 的关键安全规则,避免忙、未 ready、没运行等情况误触发更新器重启。
调用图:外部调用 1 个(assert_eq!)。
tests::unchanged_updater_never_reexecs894–906 ↗
fn unchanged_updater_never_reexecs()
作用:测试当刷新模式是 None 时,不管重启结果是什么,更新器都不应该重新执行。
数据流:用 UpdaterRefreshMode::None 搭配多种结果调用 should_reexec_updater → 期望全部为 false。
调用关系:它补充验证 should_reexec_updater 的另一半规则:没有明确要求刷新,就绝不 reexec。
调用图:外部调用 1 个(assert_eq!)。
tests::restart_decision_preserves_forced_refreshes909–940 ↗
fn restart_decision_preserves_forced_refreshes()
作用:测试重启决策不会吞掉“强制刷新”的要求。
数据流:构造当前版本信息 → 用不同 RestartMode、探测信息和托管版本调用 restart_decision → 比较是否得到 AlreadyCurrent、NotReady 或 Restart。
调用关系:它保护 restart_decision 的行为:只在 IfVersionChanged 且版本相同时跳过;Always 模式即使版本相同也要重启。
调用图:外部调用 1 个(assert_eq!)。
tests::remote_control_start_output_serializes_inner_output_without_tag943–1004 ↗
fn remote_control_start_output_serializes_inner_output_without_tag()
作用:测试 RemoteControlStartOutput 转 JSON 时不会额外包一层标签,而是直接输出里面的启动或初始化结果。
数据流:分别构造 LifecycleOutput 和 BootstrapOutput → 包成 Start 或 Bootstrap → 转成 JSON → 确认和内部对象的 JSON 完全一样。
调用关系:它验证 RemoteControlStartOutput 的 untagged 序列化行为,保证外部 API 返回格式简单稳定。
调用图:外部调用 3 个(Bootstrap, Start, assert_eq!)。
tests::not_ready_context_reports_daemon_app_server_before_stderr1007–1033 ↗
async fn not_ready_context_reports_daemon_app_server_before_stderr()
作用:测试 app-server 没 ready 时的错误说明顺序:先说守护进程用了哪个程序,再展示 stderr 日志。
数据流:创建临时目录和假的 Daemon,写入一段 stderr 日志 → 调用 app_server_not_ready_context → 和期望的完整错误文字比较。
调用关系:它覆盖 wait_until_ready 失败时依赖的诊断文本,确保用户看到的排错信息清楚且顺序合理。
调用图:外部调用 3 个(new, assert_eq!, write)。
app-server-daemon/src/backend/mod.rs源码 ↗
这个文件解决的是“怎么找到并操控被托管的 app-server 进程”的问题。PID 是进程编号,可以理解成操作系统给每个正在运行程序发的门牌号;PID 文件就是把这个门牌号写进一个文件,方便下次再找到它。这里定义了后端类型 BackendKind,目前只有 Pid 这一种;也定义了 BackendPaths,把启动程序的位置、普通 PID 文件、更新循环用的 PID 文件、是否允许远程控制等信息放在一起。两个创建函数像装配工:拿到这些路径后,交给 pid 模块真正造出 PidBackend。最后的 append_stderr_log_tail_context 用来在服务没准备好时,把最近的错误日志补到提示文字里,让排查问题时不只是看到“没启动”,还能看到可能失败在哪里。
pid_backend24–30 ↗
fn pid_backend(paths: BackendPaths) -> PidBackend
作用:用一组路径和开关创建普通的 PID 后端。别人想启动、连接或检查被托管的 app-server 时,会先通过它拿到一个 PidBackend 对象。
数据流:输入是一份 BackendPaths,里面有 codex 可执行文件路径、普通 PID 文件路径,以及是否开启远程控制。它取出这些信息,调用 PidBackend::new 组装后端。输出是一个 PidBackend;它本身不启动进程,只是把后续需要的工具准备好。
调用关系:它是外层流程创建普通后端的入口。bootstrap_locked、running_backend_instance 和 start_managed_backend_with_bin 在需要准备或使用被托管服务时会调用它;它再把真正的构造工作交给 pid 模块里的 new。
调用图:调用 1 个内部函数(new);被 3 处调用(bootstrap_locked, running_backend_instance, start_managed_backend_with_bin)。
pid_update_loop_backend32–34 ↗
fn pid_update_loop_backend(paths: BackendPaths) -> PidBackend
作用:创建专门给“更新循环”使用的 PID 后端。更新循环可以理解成后台定期检查或维护程序状态的那条工作线,它用的是单独的 PID 文件。
数据流:输入同样是 BackendPaths,但它只拿 codex 可执行文件路径和 update_pid_file。然后调用 PidBackend::new_update_loop,得到一个面向更新循环的 PidBackend。输出是这个后端对象,不会直接修改文件或启动循环。
调用关系:bootstrap_locked 和 is_bootstrapped 在判断或准备更新循环相关状态时会用到它。它不自己做复杂判断,只负责把正确的路径交给 new_update_loop,让 pid 模块生成合适的后端。
调用图:调用 1 个内部函数(new_update_loop);被 2 处调用(bootstrap_locked, is_bootstrapped)。
append_stderr_log_tail_context36–46 ↗
async fn append_stderr_log_tail_context(pid_file: &Path, context: &mut String)
作用:在报错说明里追加 app-server 最近的标准错误日志。标准错误日志就是程序专门写失败原因、警告和崩溃信息的地方;追加“尾巴”能让用户更快看到最近发生了什么。
数据流:输入是一个 PID 文件路径和一段可修改的文字 context。它先根据 PID 文件去读取最近的 stderr 日志尾部:如果读到了,就把日志内容追加进 context;如果没有日志,就什么也不加;如果读取失败,就把“读取日志失败”的说明和错误原因追加进去。输出没有单独返回值,但会改动传入的 context 字符串。
调用关系:当 app_server_not_ready_context 需要生成“服务还没准备好”的解释时,会调用它补充上下文。它把读取日志的具体工作交给 pid::read_stderr_log_tail;遇到读取错误时,用格式化文本把错误写进最终说明。
调用图:调用 1 个内部函数(read_stderr_log_tail);被 1 处调用(app_server_not_ready_context);外部调用 1 个(format!)。
app-server-daemon/src/backend/pid.rs源码 ↗
后台服务通常看不见、摸不着,所以这里用 pid 文件记录它的进程号和启动时间。pid 是操作系统给进程的编号,但编号可能被后来别的进程重复使用,所以文件里还存启动时间,防止认错人。启动时,它先抢一把锁,像排队取号,确保两个命令不会同时创建同一个后台服务;然后启动子进程,把错误输出写进单独日志,再把 pid 记录写入文件。检查时,它会读 pid 文件,看服务是不存在、正在启动,还是已经运行;如果记录过期,就清掉。停止时,它先温和地发送结束信号,等一段时间,超时后再强制杀掉。这个文件主要面向 Unix 系统;非 Unix 平台上相关启动和停止会直接报“不支持”。
PidLogTail::append_to_context51–60 ↗
fn append_to_context(&self, context: &mut String)
作用:把后台服务最近的错误日志追加到一段错误说明里。这样报错时,用户不只看到“失败了”,还能看到服务自己最后吐出的几行线索。
数据流:进去的是一个已有的文字说明和这份日志尾巴对象,里面有日志路径和内容。它先写上日志文件位置,再把每一行日志缩进后追加进去。出来的是被补充过的同一个说明字符串。
调用关系:它是错误诊断里的小帮手。别的地方读到 stderr 日志尾巴后,会用它把日志塞进错误上下文,让启动失败之类的问题更容易查。
调用图:外部调用 1 个(format!)。
PidBackend::new78–88 ↗
fn new(codex_bin: PathBuf, pid_file: PathBuf, remote_control_enabled: bool) -> Self
作用:创建一个用于管理普通 app-server 的 pid 后端对象。调用者给它程序路径、pid 文件位置,以及是否开启远程控制,它就准备好之后启动、检查、停止服务所需的信息。
数据流:进去的是 codex 可执行文件路径、pid 文件路径和远程控制开关。它根据 pid 文件路径顺手算出一个锁文件路径,并记下这是普通 app-server 模式。出来的是一个 PidBackend 实例。
调用关系:这是普通后台服务管理的入口构造函数。测试和上层创建 pid_backend 时会先调用它,之后再用这个对象执行 start、stop 或 is_starting_or_running。
调用图:被 8 处调用(pid_backend, app_server_disabled_remote_control_uses_compatible_args_and_runtime_env, app_server_remote_control_uses_runtime_flag, locked_empty_pid_file_is_treated_as_active_reservation, stale_record_cleanup_preserves_replacement_record, start_retries_stale_empty_pid_file_under_its_own_lock, stop_waits_for_live_reservation_to_resolve, unlocked_empty_pid_file_is_treated_as_stale_reservation);外部调用 1 个(with_extension)。
PidBackend::new_update_loop90–98 ↗
fn new_update_loop(codex_bin: PathBuf, pid_file: PathBuf) -> Self
作用:创建一个专门管理更新循环进程的 pid 后端对象。更新循环和普通 app-server 启动参数、强制停止方式略有不同,所以单独标记。
数据流:进去的是 codex 可执行文件路径和 pid 文件路径。它生成对应锁文件路径,并把命令类型记为 UpdateLoop。出来的是一个 PidBackend 实例。
调用关系:它由 pid_update_loop_backend 这类上层创建逻辑使用。后续 start、stop 会根据这个模式选择更新循环的命令参数和停止策略。
调用图:被 1 处调用(pid_update_loop_backend);外部调用 1 个(with_extension)。
PidBackend::is_starting_or_running100–116 ↗
async fn is_starting_or_running(&self) -> Result<bool>
作用:判断这个后台服务是不是正在启动,或者已经活着。它用来回答“现在还需要再启动一个吗?”这个问题。
数据流:进去的是 PidBackend 里保存的 pid 文件和锁文件位置。它读取 pid 文件状态:没有就返回 false,正在启动就返回 true,已有记录就再检查对应进程是否真的还活着;如果记录过期,就清理后重新判断。出来的是一个布尔值。
调用关系:它把读 pid 文件、检查进程、清理过期记录几个步骤串起来。上层想知道服务状态时调用它,它内部会交给 read_pid_file_state、record_is_active 和 refresh_after_stale_record。
调用图:调用 3 个内部函数(read_pid_file_state, record_is_active, refresh_after_stale_record)。
PidBackend::start236–238 ↗
async fn start(&self) -> Result<Option<u32>>
作用:启动由 pid 文件管理的后台进程。它会尽量保证同一时间只启动一个,并且把启动成功的进程身份写进 pid 文件。
数据流:进去的是 PidBackend 中保存的程序路径、pid 文件、锁文件和命令模式。它先创建目录、抢锁、预留 pid 文件;如果发现已有活进程就返回 None;否则打开错误日志、组装命令、启动子进程、读取子进程启动时间,把 pid 和启动时间写成 JSON 后发布到 pid 文件。出来的是新进程 pid,或表示已经有服务在跑;失败时会清理临时文件并尽量终止刚启动的进程。
调用关系:这是启动流程的核心。它会调用 acquire_reservation_lock 防止并发启动,用 command_args 和 command_env 组装命令,用 open_stderr_log 接住错误日志,用 read_process_start_time 记录身份,用 record_is_active 避免重复启动。非 Unix 平台上这个功能会报不支持。
调用图:调用 8 个内部函数(acquire_reservation_lock, command_args, command_env, open_stderr_log, read_pid_file_state_with_lock_held, record_is_active, terminate_process, read_process_start_time);外部调用 15 个(parent, with_extension, from, null, bail!, new, format!, new, create_dir_all, remove_file (+5 more))。
PidBackend::stop240–275 ↗
async fn stop(&self) -> Result<()>
作用:停止 pid 文件指向的后台服务。它先礼貌地请进程退出,等太久还不走时再强制结束。
数据流:进去的是 PidBackend 保存的 pid 文件和命令模式。它先等待“正在启动”的预留状态结束,拿到真正的 pid 记录;如果记录已经无效就清理;如果进程还活着,就发送终止信号,然后反复检查,超过宽限期后发送强制终止信号,超过总超时时间仍没停就报错。出来通常是成功停止并清掉过期记录,或返回超时错误。
调用关系:它是关闭流程的总指挥。它先用 wait_for_pid_start 等启动完成,再用 record_is_active 确认对象,用 terminate_process 和 force_terminate_process 执行停止,并在发现旧记录时交给 refresh_after_stale_record。
调用图:调用 5 个内部函数(force_terminate_process, record_is_active, refresh_after_stale_record, terminate_process, wait_for_pid_start);外部调用 3 个(bail!, now, sleep)。
PidBackend::wait_for_pid_start277–294 ↗
async fn wait_for_pid_start(&self) -> Result<Option<PidRecord>>
作用:在停止服务前,等一个“正在启动”的 pid 文件预留状态变成真正记录。这样不会在服务刚启动一半时误判。
数据流:进去的是 pid 文件位置和一个最长等待时间。它不断读取 pid 文件状态:没有文件就返回 None,有完整记录就返回记录,仍在启动就短暂睡眠后重试;等太久还没完成就报错。出来是可停止的 PidRecord,或表示没有服务。
调用关系:它只被 stop 使用。stop 需要先确认启动流程有没有完成,再决定是直接返回、清理旧记录,还是向真实进程发停止信号。
调用图:调用 1 个内部函数(read_pid_file_state);被 1 处调用(stop);外部调用 3 个(bail!, now, sleep)。
PidBackend::read_pid_file_state296–326 ↗
async fn read_pid_file_state(&self) -> Result<PidFileState>
作用:读取 pid 文件,并把它翻译成三种白话状态:没有服务、正在启动、已有运行记录。
数据流:进去的是 pid 文件和锁文件路径。它尝试读 pid 文件;文件不存在时再看锁是否被别人拿着,来判断是不是正在启动;文件为空时进一步检查这是不是活跃预留还是遗留垃圾;文件有内容时把 JSON 解析成 pid 记录。出来是 Missing、Starting 或 Running。
调用关系:它是状态判断的基础。is_starting_or_running 和 wait_for_pid_start 都依赖它;它会调用 reservation_lock_is_active 和 inspect_empty_pid_reservation 来处理启动中或异常中断留下的空文件。
调用图:调用 2 个内部函数(inspect_empty_pid_reservation, reservation_lock_is_active);被 2 处调用(is_starting_or_running, wait_for_pid_start);外部调用 3 个(Running, read_to_string, from_str)。
PidBackend::read_pid_file_state_with_lock_held328–347 ↗
async fn read_pid_file_state_with_lock_held(&self) -> Result<PidFileState>
作用:在调用者已经拿到锁的情况下读取 pid 文件。因为已经独占了这段流程,它可以更大胆地清理空的旧预留文件。
数据流:进去的是 pid 文件路径,以及“锁已经在手”的前提。它读文件;不存在就说 Missing;空文件就删除并说 Missing;有内容就解析成 Running 记录。出来是当前 pid 文件状态。
调用关系:它被 start 和 refresh_after_stale_record 使用。start 用它判断已有 pid 文件是不是别人留下的;refresh_after_stale_record 用它在安全加锁后确认旧记录是否还能删除。
调用图:被 2 处调用(refresh_after_stale_record, start);外部调用 4 个(Running, read_to_string, remove_file, from_str)。
PidBackend::refresh_after_stale_record349–360 ↗
async fn refresh_after_stale_record(&self, expected: &PidRecord) -> Result<PidFileState>
作用:发现 pid 文件里的进程记录已经过期后,安全地再检查一次并清理它。这样避免删掉别人刚写进去的新记录。
数据流:进去的是一个被认为过期的 PidRecord。它先拿 pid 锁,再重新读取 pid 文件;如果文件里还是同一条旧记录,就删除 pid 文件并返回 Missing;如果已经变成别的状态,就保留并返回那个新状态。出来是刷新后的状态。
调用关系:它被 is_starting_or_running 和 stop 调用。它的关键作用是“先锁门再打扫”,避免在多个命令同时操作时误删新启动的服务记录。
调用图:调用 2 个内部函数(acquire_reservation_lock, read_pid_file_state_with_lock_held);被 2 处调用(is_starting_or_running, stop);外部调用 1 个(remove_file)。
PidBackend::acquire_reservation_lock362–383 ↗
async fn acquire_reservation_lock(&self) -> Result<fs::File>
作用:拿到 pid 启动/清理用的锁。锁就像门口的一把钥匙,谁拿到谁才能改 pid 文件的关键状态。
数据流:进去的是锁文件路径。它打开或创建锁文件,然后反复尝试加独占锁;如果锁被别人拿着,就短暂等待;等到超时还拿不到就报错。出来的是一个文件句柄,只要这个句柄还活着,锁就保持着。
调用关系:start 用它防止两个进程同时启动同一个服务,refresh_after_stale_record 用它防止清理时撞车。它内部把真正加锁动作交给 try_lock_file。
调用图:调用 1 个内部函数(try_lock_file);被 2 处调用(refresh_after_stale_record, start);外部调用 4 个(bail!, new, now, sleep)。
PidBackend::open_stderr_log386–400 ↗
async fn open_stderr_log(&self) -> Result<fs::File>
作用:打开后台服务的错误日志文件,让新启动的服务把 stderr,也就是错误输出,写进去。
数据流:进去的是 pid 文件路径。它把扩展名换成 stderr.log 得到日志路径,然后创建或清空这个日志文件并以写入方式打开。出来是可交给子进程使用的文件。
调用关系:它只在 start 中使用。start 在真正启动子进程前调用它,把子进程的错误输出接到这个文件,后面出错时 read_stderr_log_tail 可以读最后几行帮助诊断。
调用图:调用 1 个内部函数(stderr_log_file_for_pid_file);被 1 处调用(start);外部调用 1 个(new)。
PidBackend::command_args403–413 ↗
PidBackend::command_env416–426 ↗
fn command_env(&self) -> Option<(&'static str, &'static str)>
作用:决定启动子进程时是否需要额外设置环境变量。这里主要用于明确关闭远程控制。
数据流:进去的是命令类型和远程控制开关。普通 app-server 且远程控制关闭时,它返回一个环境变量键值对;其他情况返回 None。出来的是可选的环境变量设置。
调用关系:它由 start 调用。start 组装 Command 时会把这里返回的环境变量放进去,让子进程按预期启用或禁用远程控制。
调用图:被 1 处调用(start)。
PidBackend::terminate_process428–433 ↗
fn terminate_process(&self, pid: u32) -> Result<()>
作用:对当前后端管理的进程发送普通终止请求。普通终止通常给进程机会自己收尾。
数据流:进去的是 pid。它根据后端类型选择底层终止函数,目前普通 app-server 和更新循环都会走普通 terminate_process。出来是成功或错误;如果进程已经不存在,底层会把它当作成功。
调用关系:start 在启动后记录失败时会用它清理刚启动的子进程,stop 在正式关闭时也会先用它发送温和的结束信号。
调用图:调用 1 个内部函数(terminate_process);被 2 处调用(start, stop)。
PidBackend::force_terminate_process435–440 ↗
fn force_terminate_process(&self, pid: u32) -> Result<()>
作用:在进程不肯正常退出时强制杀掉它。对更新循环,它会杀整个进程组,避免留下子进程。
数据流:进去的是 pid 和后端命令类型。普通 app-server 会对单个 pid 发送强制结束信号;更新循环会对以该 pid 为首的进程组发送强制结束信号。出来是成功或错误。
调用关系:它只被 stop 在等待超过宽限期后调用。它是关闭流程里的最后手段,避免后台服务长时间卡住。
调用图:调用 2 个内部函数(force_terminate_process, force_terminate_process_group);被 1 处调用(stop)。
PidBackend::record_is_active442–444 ↗
async fn record_is_active(&self, record: &PidRecord) -> Result<bool>
作用:确认 pid 文件里的记录是不是还对应一个真实的、同一个后台进程。它不是只看 pid,而是连启动时间一起核对。
数据流:进去的是 PidRecord,里面有 pid 和启动时间。它把检查工作交给 process_matches_record:先看进程是否存在,再读当前进程启动时间比对。出来是 true 或 false。
调用关系:is_starting_or_running、start 和 stop 都会用它避免认错进程。它是 pid 文件记录和操作系统真实进程之间的核验关口。
调用图:调用 1 个内部函数(process_matches_record);被 3 处调用(is_starting_or_running, start, stop)。
read_stderr_log_tail447–453 ↗
async fn read_stderr_log_tail(pid_file: &Path) -> Result<Option<PidLogTail>>
作用:读取某个 pid 文件对应的错误日志最后一小段。它用于报错时给用户展示最近发生了什么。
数据流:进去的是 pid 文件路径。它先算出对应的 stderr.log 路径,再读取最多指定字节数的日志尾部;如果文件不存在或没有有效内容,就返回 None。出来是可选的 PidLogTail,里面带路径和内容。
调用关系:它被 append_stderr_log_tail_context 这类错误上下文函数调用。启动失败时,上层可以用它把后台服务最后的错误输出附到报错信息里。
调用图:调用 2 个内部函数(read_log_tail, stderr_log_file_for_pid_file);被 1 处调用(append_stderr_log_tail_context)。
stderr_log_file_for_pid_file455–457 ↗
fn stderr_log_file_for_pid_file(pid_file: &Path) -> PathBuf
作用:根据 pid 文件路径推导出错误日志文件路径。规则很简单:把扩展名换成 stderr.log。
数据流:进去的是 pid 文件路径。它调用路径的扩展名替换功能,生成同一位置下对应的 stderr 日志路径。出来是 PathBuf 路径对象。
调用关系:open_stderr_log 用它决定子进程错误输出写到哪里,read_stderr_log_tail 用它决定出错时从哪里读日志。它保证写和读使用同一套命名规则。
调用图:被 2 处调用(open_stderr_log, read_stderr_log_tail);外部调用 1 个(with_extension)。
read_log_tail459–495 ↗
async fn read_log_tail(path: &Path, byte_limit: u64) -> Result<Option<String>>
作用:读取日志文件最后一段内容,而不是整份文件。这样即使日志很大,报错信息也只带最有用的最近部分。
数据流:进去的是日志路径和最多读取的字节数。它打开文件,查看长度;空文件返回 None;非空则跳到接近末尾的位置读取,必要时丢掉开头半截行,最后把字节转成文字并去掉末尾空白。出来是可选的日志文本。
调用关系:它被 read_stderr_log_tail 调用。read_stderr_log_tail 负责找到具体日志文件,它负责高效、安全地取最后几行内容。
调用图:被 1 处调用(read_stderr_log_tail);外部调用 4 个(Start, from_utf8_lossy, new, open)。
process_exists498–504 ↗
fn process_exists(pid: u32) -> bool
作用:检查某个 pid 在操作系统里是否存在。它用的是 Unix 上常见的“发送 0 号信号”技巧:不真的杀进程,只问系统有没有这个进程。
数据流:进去的是 pid 数字。它先转换成系统需要的 pid 类型,再调用 kill(pid, 0);如果成功,或因为权限不够而被拒绝,也说明进程存在;其他情况说明不存在。出来是布尔值。
调用关系:它被 process_matches_record 使用。process_matches_record 先靠它做快速存在性检查,再进一步核对启动时间,避免 pid 被复用导致误判。
调用图:被 1 处调用(process_matches_record);外部调用 3 个(last_os_error, kill, try_from)。
terminate_process552–554 ↗
fn terminate_process(_pid: u32) -> Result<()>
作用:底层的普通终止函数。在 Unix 上它向指定进程发送 SIGTERM,也就是“请你退出”的信号;非支持平台上会报不支持。
数据流:进去的是 pid。Unix 下它把 pid 转成系统类型后调用 kill 发送 SIGTERM;如果进程已不存在,也视为已经停止;其他系统错误会变成带上下文的错误。出来是成功或失败。
调用关系:它由 PidBackend::terminate_process 包装调用。上层不直接关心信号细节,只通过后端的停止流程使用它。
调用图:被 1 处调用(terminate_process);外部调用 4 个(bail!, last_os_error, kill, try_from)。
force_terminate_process557–559 ↗
fn force_terminate_process(_pid: u32) -> Result<()>
作用:底层的单进程强制终止函数。在 Unix 上它发送 SIGKILL,也就是不给进程收尾机会、直接杀掉。
数据流:进去的是 pid。Unix 下它转换 pid 后调用 kill 发送 SIGKILL;如果进程已经没了就算成功;如果 pid 不合法或系统调用失败就返回错误。非支持平台上会报不支持。出来是成功或失败。
调用关系:它由 PidBackend::force_terminate_process 在普通 app-server 场景下调用。stop 只有在温和停止等太久后才会走到这里。
调用图:被 1 处调用(force_terminate_process);外部调用 4 个(bail!, last_os_error, kill, try_from)。
force_terminate_process_group562–564 ↗
fn force_terminate_process_group(_pid: u32) -> Result<()>
作用:底层的进程组强制终止函数。它用于更新循环这类可能带子进程的场景,避免只杀了父进程却留下孩子。
数据流:进去的是 pid。Unix 下它把 pid 取负后发送 SIGKILL,表示杀这个 pid 对应的整个进程组;进程组不存在则视为成功;其他错误返回失败。非支持平台上会报不支持。出来是成功或失败。
调用关系:它由 PidBackend::force_terminate_process 在 UpdateLoop 模式下调用。stop 在强制阶段使用它,确保更新循环相关进程一起退出。
调用图:被 1 处调用(force_terminate_process);外部调用 4 个(bail!, last_os_error, kill, try_from)。
process_matches_record580–582 ↗
async fn process_matches_record(_record: &PidRecord) -> Result<bool>
作用:判断 pid 文件里的记录是否真的匹配当前系统里的进程。它解决 pid 可能被复用的问题。
数据流:进去的是包含 pid 和启动时间的记录。Unix 下它先确认 pid 是否存在,再读取这个 pid 当前的启动时间,和记录里的启动时间比较;如果读取时进程已经没了,就返回 false。非 Unix 下返回 false。出来是是否匹配的布尔值。
调用关系:它被 PidBackend::record_is_active 调用。后端所有“这个记录还活着吗”的判断,最终都落到它这里。
调用图:调用 2 个内部函数(process_exists, read_process_start_time);被 1 处调用(record_is_active)。
try_lock_file609–611 ↗
fn try_lock_file(_file: &fs::File) -> Result<bool>
作用:尝试给锁文件加一把独占锁,而且不等待。成功说明现在可以安全操作 pid 文件,失败可能只是别人正在操作。
数据流:进去的是已打开的锁文件。Unix 下它用 flock 加非阻塞独占锁;成功返回 true;如果只是锁被别人占用,返回 false;如果系统调用出错,返回错误。非支持平台上会报不支持。出来是锁是否拿到。
调用关系:acquire_reservation_lock 用它反复尝试直到拿到锁;reservation_lock_is_active 和 inspect_empty_pid_reservation 用它判断别人是否正在启动或写 pid 文件。
调用图:被 3 处调用(acquire_reservation_lock, inspect_empty_pid_reservation, reservation_lock_is_active);外部调用 4 个(as_raw_fd, bail!, last_os_error, flock)。
reservation_lock_is_active635–637 ↗
async fn reservation_lock_is_active(_path: &Path) -> Result<bool>
作用:判断 pid 锁现在是不是被别的进程拿着。它用来识别“pid 文件还没写好,但启动流程正在进行”的状态。
数据流:进去的是锁文件路径。Unix 下它打开或创建锁文件,然后尝试加锁;如果自己加不上,说明别人持有锁,返回 true;如果能加上,说明没有活跃预留,返回 false。非 Unix 下返回 false。出来是锁是否活跃。
调用关系:它被 read_pid_file_state 调用。当 pid 文件不存在时,read_pid_file_state 会用它区分是真的没有服务,还是服务正在启动但记录还没发布。
调用图:调用 1 个内部函数(try_lock_file);被 1 处调用(read_pid_file_state);外部调用 1 个(new)。
inspect_empty_pid_reservation683–688 ↗
async fn inspect_empty_pid_reservation(
_pid_path: &Path,
_lock_path: &Path,
) -> Result<EmptyPidReservation>
作用:处理“pid 文件存在但内容为空”的尴尬情况。空文件可能是启动中的预留,也可能是启动失败后留下的垃圾。
数据流:进去的是 pid 文件路径和锁文件路径。Unix 下它先尝试拿锁;拿不到说明别人正在启动,返回 Active;拿到后重新读 pid 文件,若文件没了或仍为空,就删除空文件并返回 Stale;如果已经有内容,就解析成记录并返回 Record。非 Unix 下认为是 Stale。出来是空预留的真实状态。
调用关系:它被 read_pid_file_state 调用。read_pid_file_state 遇到空 pid 文件时,会靠它判断该等待、清理,还是接受已经写好的记录。
调用图:调用 1 个内部函数(try_lock_file);被 1 处调用(read_pid_file_state);外部调用 5 个(Record, new, read_to_string, remove_file, from_str)。
read_process_start_time691–708 ↗
async fn read_process_start_time(pid: u32) -> Result<String>
作用:读取某个进程的启动时间,用来给 pid 记录加一道防伪标记。因为 pid 会复用,启动时间能帮助确认“是不是同一个进程”。
数据流:进去的是 pid。Unix 下它运行 ps 命令查询该 pid 的 lstart 字段,把输出转成 UTF-8 文本并去掉空白;如果 ps 失败或没有启动时间就报错。出来是启动时间字符串。
调用关系:start 在启动子进程后调用它,把启动时间写进 pid 文件;process_matches_record 后来也调用它,把当前进程启动时间和记录里的时间做对比。
调用图:被 2 处调用(start, process_matches_record);外部调用 3 个(from_utf8, bail!, new)。
app-server-daemon/src/client.rs源码 ↗
这个文件解决的是:守护进程怎么确认 app-server 活着、版本是多少,以及怎么跟它完成最初的握手。如果没有它,其他代码就只能自己拼连接、自己等回复、自己解析消息,很容易超时卡住或读错格式。它先用 Unix socket(一种本机进程之间通信的通道)连到控制口,再把连接升级为 WebSocket(像一条持续打开的双向消息管道)。之后用 JSON-RPC(一种“带方法名和编号的 JSON 消息格式”)发送 initialize 请求,等待同一个编号的回复,最后从 user-agent 字符串里抠出服务器版本。它还统一处理超时、发送文本消息、只读取文本消息等细节。
probe34–43 ↗
async fn probe(socket_path: &Path) -> Result<ProbeInfo>
作用:这个函数用来快速探测 app-server 控制 socket 是否能用,并拿到服务器版本。它给整个探测过程加了 2 秒超时,防止程序一直傻等。
数据流:进去的是一个 socket 文件路径 → 它把真正的探测交给 probe_inner,同时用 timeout 包住 → 出来的是 ProbeInfo,里面有 app-server 版本;如果连接、握手或等待太久失败,就返回带上下文的错误。
调用关系:它是外部最常用的“探活入口”。bootstrap_locked、start、stop、restart、version、wait_until_ready、set_remote_control_locked、try_restart_if_running 等流程在需要确认服务器状态时会调用它;它自己只负责限时,具体连接和握手交给 probe_inner。
调用图:调用 1 个内部函数(probe_inner);被 9 处调用(bootstrap_locked, restart, set_remote_control_locked, start, stop, try_restart_if_running, version, wait_until_ready, probe_app_server_version);外部调用 1 个(timeout)。
probe_inner45–61 ↗
async fn probe_inner(socket_path: &Path) -> Result<ProbeInfo>
作用:这个函数完成一次真正的探测:连接服务器、发送初始化请求、通知对方已经初始化,然后读出服务器版本。可以把它想成正式聊天前的握手和自我介绍。
数据流:进去的是 socket 路径 → 它调用 connect 建立 WebSocket,再调用 initialize 发送 initialize 请求并拿到回复,接着发送 initialized 通知,最后从回复的 user_agent 里解析版本 → 出来的是 ProbeInfo;过程中会关闭 WebSocket,关闭失败也不会影响主结果。
调用关系:probe 会调用它来做实际工作。它把网络连接交给 connect,把握手交给 initialize,把消息发送交给 send_message,把版本字符串处理交给 parse_version_from_user_agent。
调用图:调用 4 个内部函数(connect, initialize, parse_version_from_user_agent, send_message);被 1 处调用(probe);外部调用 1 个(Notification)。
connect63–71 ↗
async fn connect(socket_path: &Path) -> Result<WebSocketStream<UnixStream>>
作用:这个函数负责把一个本机 socket 路径变成可收发 JSON-RPC 消息的 WebSocket 连接。它把底层连接和 WebSocket 升级两步封装起来,别人就不用关心细节。
数据流:进去的是 socket 文件路径 → 它先用 UnixStream 连接这个路径,再用 client_async 把普通流升级成 WebSocket → 出来的是 WebSocketStream;如果连不上或升级失败,会返回说明是哪个路径出问题的错误。
调用关系:probe_inner 会用它建立探测连接;connect_with_retry、enable_remote_control、disable_remote_control、run_enable_remote_control_scenario 等远程控制相关流程也会用它作为建立通信管道的第一步。
调用图:调用 1 个内部函数(connect);被 5 处调用(probe_inner, connect_with_retry, disable_remote_control, enable_remote_control, run_enable_remote_control_scenario);外部调用 1 个(client_async)。
initialize73–116 ↗
async fn initialize(
websocket: &mut WebSocketStream<S>,
experimental_api: bool,
) -> Result<InitializeResponse>
作用:这个函数负责和 app-server 做初始化握手。它告诉服务器“我是谁、我支持什么能力”,然后等服务器用同一个请求编号回话。
数据流:进去的是一个已经连好的 WebSocket,以及是否启用 experimental_api(实验接口)的开关 → 它组装 initialize JSON-RPC 请求,写入客户端名称、标题、版本和可选能力,发送出去;然后循环读取消息,只接受编号等于初始化请求编号的响应 → 出来的是解析好的 InitializeResponse;如果超时、读错格式或回复内容不能解析,就返回错误。
调用关系:probe_inner 用它做版本探测前的握手,initialize_client 也会在接纳客户端时用到它。它调用 send_message 发请求,调用 read_message 收响应,并用 timeout 防止等待无止境。
调用图:调用 2 个内部函数(read_message, send_message);被 2 处调用(probe_inner, initialize_client);外部调用 5 个(default, Request, env!, to_value, timeout)。
send_message118–129 ↗
async fn send_message(
websocket: &mut WebSocketStream<S>,
message: &JSONRPCMessage,
) -> Result<()>
作用:这个函数把一个 JSON-RPC 消息发到 WebSocket 上。它统一负责把结构化消息变成 JSON 文本,避免各处重复手写序列化。
数据流:进去的是可写的 WebSocket 和一个 JSONRPCMessage → 它用 serde_json 把消息转成字符串,再包装成 WebSocket 文本帧发送 → 正常时没有业务返回值,只表示发送成功;失败时返回发送或转换错误。
调用关系:initialize 用它发送 initialize 请求,probe_inner 用它发送 initialized 通知。initialize_client、send_remote_control_request、accept_initialized_client、send_remote_control_status、serve_enable_remote_control_scenario 等流程也把它当作统一的发信口。
调用图:被 8 处调用(initialize, probe_inner, initialize_client, send_remote_control_request, accept_initialized_client, disable_remote_control_retries_without_params_for_older_servers, send_remote_control_status, serve_enable_remote_control_scenario);外部调用 3 个(send, to_string, Text)。
read_message131–146 ↗
async fn read_message(websocket: &mut WebSocketStream<S>) -> Result<JSONRPCMessage>
作用:这个函数从 WebSocket 里读下一条 JSON-RPC 消息。它会跳过非文本帧,因为这里真正关心的是 JSON 文本消息。
数据流:进去的是可读的 WebSocket → 它不断读取下一帧;如果连接被服务器关掉,就报错;如果不是文本消息,就继续等;如果是文本,就按 JSON-RPC 格式解析 → 出来的是 JSONRPCMessage,或者解析失败的错误。
调用关系:initialize 用它等待初始化响应。read_remote_control_response、accept_initialized_client、disable_remote_control_retries_without_params_for_older_servers、serve_enable_remote_control_scenario、wait_for_remote_control_status 等流程也靠它统一收消息。
调用图:被 6 处调用(initialize, read_remote_control_response, accept_initialized_client, disable_remote_control_retries_without_params_for_older_servers, serve_enable_remote_control_scenario, wait_for_remote_control_status);外部调用 1 个(next)。
parse_version_from_user_agent148–158 ↗
fn parse_version_from_user_agent(user_agent: &str) -> Result<String>
作用:这个函数从 user-agent 字符串里提取 app-server 的版本号。user-agent 可以理解成软件发给对方的一张“名片”,通常包含名字、版本和环境信息。
数据流:进去的是一段 user_agent 字符串 → 它先找斜杠,把斜杠后面的部分当作版本开头,再取第一个空白前的内容 → 出来的是版本字符串;如果没有斜杠或斜杠后没有版本,就返回错误。
调用关系:probe_inner 在拿到 initialize 响应后调用它,把服务器返回的 user_agent 变成 ProbeInfo 里清晰可用的版本字段。测试函数会专门验证它能解析正常格式,也能拒绝缺版本的格式。
调用图:被 1 处调用(probe_inner)。
tests::parses_version_from_codex_user_agent167–175 ↗
fn parses_version_from_codex_user_agent()
作用:这个测试确认 parse_version_from_user_agent 能从正常的 Codex user-agent 里取出版本号。它防止以后改代码时不小心把版本解析弄坏。
数据流:进去的是一段包含名称、版本、系统信息和另一个组件信息的示例 user-agent → 测试调用 parse_version_from_user_agent → 期望出来的版本正好是 1.2.3,否则测试失败。
调用关系:它只在测试时运行,不参与真实服务器通信。它直接检验 parse_version_from_user_agent 的正常路径,用 assert_eq! 比较实际结果和预期结果。
调用图:外部调用 1 个(assert_eq!)。
tests::rejects_user_agent_without_version178–180 ↗
fn rejects_user_agent_without_version()
作用:这个测试确认缺少版本分隔符的 user-agent 会被拒绝。它保证解析函数不会把不完整的名片误当成有效版本。
数据流:进去的是没有斜杠和版本号的字符串 → 测试调用 parse_version_from_user_agent → 期望得到错误;如果函数反而成功,测试就失败。
调用关系:它只在测试时运行,覆盖 parse_version_from_user_agent 的错误路径。它和 parses_version_from_codex_user_agent 一起说明这个解析函数既要能读对,也要能拒绝坏格式。
调用图:外部调用 1 个(assert!)。
cli/src/doctor/background.rs源码 ↗
后台服务有时会留下状态目录、PID 文件(记录进程编号的文件)和控制 socket(本机程序之间通信的小入口)。这个文件就像医生听诊:先看这些痕迹,再在 socket 已经存在时轻轻问一句“你还活着吗、版本是多少”。如果什么都没有,它认为只是没运行,不算坏事;如果 socket 在但问不通,就提示警告,因为这常见于服务早已退出但入口文件还残留,会让客户端连接失败。它还会判断服务是 persistent(有设置文件,像长期驻留模式)还是 ephemeral(临时模式)。最后,它把结果包装成 DoctorCheck,给用户一行状态、若干细节,以及必要时的修复提示。
background_server_check27–76 ↗
async fn background_server_check(config: &Config) -> DoctorCheck
作用:生成一条关于后台 app-server 的体检结果。它的重点是“被动检查”:只读取已有状态,最多探测已有 socket,不主动改变后台服务。
数据流:输入是一份配置 Config,里面有 codex_home 这个主目录位置。它先拼出后台服务的状态目录,查看设置文件、主进程 PID 文件、更新循环 PID 文件是否存在;再计算控制 socket 的路径;如果 socket 存在,就交给 socket_status 试着读取版本。最后把这些信息整理成 DoctorCheck,包含状态、摘要、细节,若发现 socket 可疑还会加一句建议。
调用关系:这是本文件的主入口,测试里的 not_running_background_server_stays_ok_without_version 和 failed_version_probe_reports_unavailable 会直接调用它。它把“看文件”的小活交给 push_file_detail,把“判断 socket 是否可用”的小活交给 socket_status,再用 SocketStatus 的方法把结果翻译成 doctor 能显示的状态和文字。
调用图:调用 3 个内部函数(new, push_file_detail, socket_status);被 2 处调用(failed_version_probe_reports_unavailable, not_running_background_server_stays_ok_without_version);外部调用 3 个(new, app_server_control_socket_path, format!)。
push_file_detail78–91 ↗
fn push_file_detail(details: &mut Vec<String>, label: &str, path: &Path)
作用:把某个文件的存在情况写进详情列表。它用来告诉用户:这个路径是正常文件、不是文件、缺失,还是读取时出错。
数据流:输入是一个可追加文字的详情列表、一个标签名和一个路径。它读取这个路径的文件信息;如果是普通文件,就追加“file”;如果存在但不是文件,就追加“not a file”;如果不存在,就追加“missing”;其他错误则把错误信息也写进去。输出不是返回值,而是改动传入的详情列表。
调用关系:background_server_check 会连续调用它检查设置文件、PID 文件和更新循环 PID 文件。它不决定检查是成功还是失败,只负责把文件现场说清楚。
调用图:被 1 处调用(background_server_check);外部调用 2 个(format!, metadata)。
server_mode93–99 ↗
fn server_mode(state_dir: &Path) -> &'static str
作用:判断后台服务看起来是哪种运行模式。简单说,有设置文件就是长期模式,没有就是临时模式。
数据流:输入是状态目录路径。它检查这个目录下面是否有 settings.json;有就返回字符串 persistent,没有就返回 ephemeral。它不改动任何文件。
调用关系:它给最终详情里的 mode 提供文字说明。这个判断很轻量,只看一个文件是否存在,不会联系后台服务。
调用图:外部调用 1 个(join)。
SocketStatus::check_status108–113 ↗
fn check_status(&self) -> CheckStatus
作用:把 socket 的具体情况翻译成 doctor 使用的总体状态。能确认没运行或正在运行都算正常,只有 socket 残留或连不上才算警告。
数据流:输入是一个 SocketStatus。如果是 NotRunning 或 Running,输出 CheckStatus::Ok;如果是 StaleOrUnreachable,输出 CheckStatus::Warning。它不读取外部信息,也不改东西。
调用关系:background_server_check 用它决定最终 DoctorCheck 是绿色正常还是黄色警告。它是把内部判断转换成用户可见检查状态的翻译器。
SocketStatus::summary115–121 ↗
fn summary(&self) -> &'static str
作用:给 socket 状态配一句短摘要,显示给用户看。它把程序内部的枚举值变成普通人能读懂的一句话。
数据流:输入是一个 SocketStatus。它按状态返回固定文字:没运行、正在运行,或 socket 已失效/无法访问。没有副作用。
调用关系:background_server_check 用它填充 doctor 检查结果的摘要栏。它和 check_status 配合,一个给机器状态,一个给人看的解释。
SocketStatus::detail_label123–129 ↗
fn detail_label(&self) -> &'static str
作用:给详情列表生成更短的状态标签,比如 running 或 not running。它适合放在多行细节里。
数据流:输入是一个 SocketStatus。它返回一个简短字符串标签,不包含版本号或错误细节,也不改动任何东西。
调用关系:background_server_check 用它追加 status: ... 这一行。它比 summary 更短,适合做明细字段值。
SocketStatus::app_server_version_detail131–141 ↗
fn app_server_version_detail(&self) -> Option<String>
作用:在能拿到版本时写出 app-server 版本;拿不到但应该说明原因时,也给出“版本不可用”的说明。
数据流:输入是一个 SocketStatus。如果没运行,输出 None,表示不用写版本行;如果正在运行,输出包含版本号的文字;如果 socket 连不上,输出包含错误原因的“版本不可用”文字。
调用关系:background_server_check 用它决定详情里要不要出现 app-server version 这一行。它把 socket_status 探测到的版本或错误转换成用户能看懂的细节。
调用图:外部调用 1 个(format!)。
socket_status144–153 ↗
async fn socket_status(socket_path: &Path) -> SocketStatus
作用:判断控制 socket 代表的后台服务到底是什么状态。它先看 socket 文件在不在;在的话才尝试问后台服务版本。
数据流:输入是 socket 路径。它先检查路径是否存在;不存在就输出 SocketStatus::NotRunning。如果存在,就调用外部的 probe_app_server_version 尝试连接并获取版本;成功则输出 Running(版本号),失败则把错误交给 concise_probe_error 压短,再输出 StaleOrUnreachable(错误摘要)。
调用关系:background_server_check 调用它完成最关键的“服务是否真的可达”判断。它又把错误整理工作交给 concise_probe_error,避免把又长又带本机路径的报错直接甩给用户。
调用图:调用 1 个内部函数(concise_probe_error);被 1 处调用(background_server_check);外部调用 4 个(exists, Running, StaleOrUnreachable, probe_app_server_version)。
concise_probe_error155–176 ↗
fn concise_probe_error(err: &anyhow::Error, socket_path: &Path) -> String
作用:把探测 socket 失败时的错误信息整理得短一点、干净一点。这样用户能看到关键原因,而不是一大串难读的路径和堆叠信息。
数据流:输入是一个错误对象和 socket 路径。它先把错误转成文字,把真实 socket 路径替换成更泛化的 control socket,再压掉多余空白;如果文字为空就返回 unknown error;如果太长就截断到固定长度并加省略号。输出是一段适合展示的错误摘要。
调用关系:socket_status 在探测失败时调用它。它不判断失败是否严重,只负责把错误包装成 doctor 详情里能放得下、也比较安全的文字。
调用图:被 1 处调用(socket_status);外部调用 3 个(display, to_string, format!)。
tests::test_config187–193 ↗
async fn test_config(codex_home: PathBuf) -> Config
作用:给测试快速造一份配置。测试可以指定一个临时目录当作 codex_home,避免碰到用户真实文件。
数据流:输入是一个目录路径。它用 ConfigBuilder 创建配置,把 codex_home 设置成这个路径,然后异步构建并返回 Config。如果构建失败,测试会直接报错。
调用关系:多个测试用它准备干净环境,特别是 not_running_background_server_stays_ok_without_version 和 failed_version_probe_reports_unavailable。它让测试只在临时目录里读写。
调用图:外部调用 1 个(default)。
tests::create_socket_placeholder195–201 ↗
fn create_socket_placeholder(config: &Config)
作用:在测试里造一个假的 socket 占位文件,用来模拟“socket 路径存在,但服务其实连不上”的情况。
数据流:输入是一份测试配置。它根据 codex_home 算出控制 socket 路径,创建父目录,然后写入一个空文件作为占位。结果是磁盘上出现了一个路径存在但不是真正可用服务 socket 的文件。
调用关系:failed_version_probe_reports_unavailable 调用它制造故障现场。这样测试可以验证主检查函数遇到残留 socket 时会给出警告。
调用图:外部调用 3 个(app_server_control_socket_path, create_dir_all, write)。
tests::not_running_background_server_stays_ok_without_version204–219 ↗
async fn not_running_background_server_stays_ok_without_version()
作用:验证后台服务完全没运行时不应该被当成错误。对 doctor 来说,“没启动后台服务”是正常情况之一。
数据流:它先创建临时目录,再用 tests::test_config 生成配置,然后调用 background_server_check。检查结果应该是 Ok,摘要应该说后台服务没运行,详情里有 status: not running,并且不应该出现版本号信息。
调用关系:这个测试直接覆盖 background_server_check 在无 socket、无后台服务痕迹时的行为。它防止以后有人把“没运行”误改成警告或错误。
调用图:调用 1 个内部函数(background_server_check);外部调用 4 个(assert!, assert_eq!, test_config, tempdir)。
tests::running_background_server_reports_app_server_version222–232 ↗
fn running_background_server_reports_app_server_version()
作用:验证 SocketStatus::Running 会被正确翻译成正常状态、运行摘要、运行标签和版本详情。
数据流:它手动创建一个 SocketStatus::Running("1.2.3")。然后分别调用这个状态的 check_status、summary、detail_label 和 app_server_version_detail,确认输出都符合预期。
调用关系:这个测试不走真实 socket,只测试 SocketStatus 这组翻译方法。它保证运行状态显示给用户时包含正确版本号。
调用图:外部调用 2 个(assert_eq!, Running)。
守护进程远程控制切换
这些文件实现守护进程侧的 JSON-RPC 流程,用于针对运行中的 app-server 启用、禁用并监控远程控制模式。
app-server-daemon/src/remote_control_client.rs源码 ↗
远程控制不是按一下开关就完事:客户端要先连上服务器的 Unix socket(一种本机进程之间通信的插座),再用 WebSocket(一条持续通信的通道)说 JSON-RPC(一种“发请求、收回应”的消息格式)。这个文件把这些麻烦步骤包起来。它先做初始化握手,告诉服务器客户端准备好了;然后发送 remoteControl/enable 或 remoteControl/disable。开启时,如果服务器说还在 Connecting,它会继续等状态变化通知,直到 Connected、Errored 等最终状态,或者等超时。它还照顾老版本服务器:新请求会带 ephemeral 参数,如果老服务器不认识并返回“参数无效”,它会自动再发一次不带参数的老式请求。整体像一个会排队、会重试、会听广播的遥控器,避免调用方自己处理连接、超时、兼容性和消息解析这些细节。
enable_remote_control37–40 ↗
async fn enable_remote_control(socket_path: &Path) -> Result<RemoteControlReadyStatus>
作用:打开远程控制功能。调用者只需要给它服务器 socket 的路径,它会自己连接服务器并完成后续开启流程。
数据流:进去的是一个 socket 文件路径 → 它通过 client::connect 建立 WebSocket 连接,再把连接交给 enable_remote_control_with_timeout → 出来的是 RemoteControlReadyStatus,也就是远程控制当前是否已连接、是否超时等信息。
调用关系:这是普通开启流程的入口。ensure_remote_control_ready 或 set_remote_control_locked 想确认远程控制可用时会调用它;它自己不处理细节,而是先连接,再把真正的握手、发请求、等待状态交给 enable_remote_control_with_timeout。
调用图:调用 2 个内部函数(connect, enable_remote_control_with_timeout);被 2 处调用(ensure_remote_control_ready, set_remote_control_locked)。
disable_remote_control42–54 ↗
async fn disable_remote_control(socket_path: &Path) -> Result<RemoteControlReadyStatus>
作用:关闭远程控制功能。它会联系 app server,发送关闭命令,并返回关闭后的状态。
数据流:进去的是 socket 路径 → 它连接服务器,先 initialize_client 做协议握手,再把 RemoteControlDisableParams 转成 JSON,发送 remoteControl/disable;如果服务器太旧不认识参数,会通过 fallback 机制重试 → 最后关闭 WebSocket,并把服务器回应转换成 RemoteControlReadyStatus。
调用关系:set_remote_control_locked 会用它来关掉远程控制;测试 disable_remote_control_retries_without_params_for_older_servers 会验证它能兼容老服务器。它依赖 initialize_client 和 request_remote_control_with_legacy_fallback 完成主要通信。
调用图:调用 3 个内部函数(connect, initialize_client, request_remote_control_with_legacy_fallback);被 2 处调用(set_remote_control_locked, disable_remote_control_retries_without_params_for_older_servers);外部调用 2 个(from, to_value)。
enable_remote_control_with_connect_retry56–64 ↗
async fn enable_remote_control_with_connect_retry(
socket_path: &Path,
connect_timeout: Duration,
connect_retry_delay: Duration,
) -> Result<RemoteControlReadyStatus>
作用:带重试地打开远程控制。适合服务器刚启动、socket 还没准备好的时候用。
数据流:进去的是 socket 路径、总等待时间、每次重试间隔 → 它不断尝试连接,直到连上或超过时间;连上后继续执行开启流程 → 出来的是远程控制准备状态,或者连接一直失败的错误。
调用关系:enable_remote_control_on_socket 会调用它,用在需要等 app server 就绪的场景。它把“反复尝试连接”交给 connect_with_retry,把“连接后怎么开启”交给 enable_remote_control_with_timeout。
调用图:调用 2 个内部函数(connect_with_retry, enable_remote_control_with_timeout);被 1 处调用(enable_remote_control_on_socket)。
enable_remote_control_with_timeout66–87 ↗
async fn enable_remote_control_with_timeout(
websocket: &mut WebSocketStream<S>,
ready_timeout: Duration,
) -> Result<RemoteControlReadyStatus>
作用:在一条已经连好的 WebSocket 上执行完整的开启远程控制流程。它还会在服务器说“正在连接”时等待最终结果。
数据流:进去的是已连接的 WebSocket 和等待就绪的最长时间 → 它先 initialize_client,发送 remoteControl/enable 请求,拿到初始状态;如果状态是 Connecting,就继续读通知等待变化 → 最后关闭连接,返回 RemoteControlReadyStatus,必要时会标记 timed_out。
调用关系:enable_remote_control、enable_remote_control_with_connect_retry 和测试辅助 run_enable_remote_control_scenario 都会调用它。它串起初始化、发送请求、兼容老协议、等待状态这些步骤,是开启流程的核心流水线。
调用图:调用 3 个内部函数(initialize_client, request_remote_control_with_legacy_fallback, wait_for_remote_control_status);被 3 处调用(enable_remote_control, enable_remote_control_with_connect_retry, run_enable_remote_control_scenario);外部调用 3 个(close, from, to_value)。
initialize_client89–101 ↗
async fn initialize_client(websocket: &mut WebSocketStream<S>) -> Result<()>
作用:和服务器做通信前的“打招呼”。没有这一步,服务器可能不会接受后面的远程控制请求。
数据流:进去的是 WebSocket → 它调用 client::initialize 发送 initialize 请求,并声明启用 experimentalApi,然后再发送 initialized 通知表示客户端初始化完成 → 出来没有业务数据,但连接状态变成可以继续发远程控制命令。
调用关系:disable_remote_control 和 enable_remote_control_with_timeout 都会先调用它。它把底层发送消息交给 client::send_message,是所有远程控制命令之前的准备动作。
调用图:调用 2 个内部函数(initialize, send_message);被 2 处调用(disable_remote_control, enable_remote_control_with_timeout);外部调用 1 个(Notification)。
send_remote_control_request103–121 ↗
async fn send_remote_control_request(
websocket: &mut WebSocketStream<S>,
request_id: RequestId,
method: &str,
params: Option<serde_json::Value>,
) -> Result<()>
作用:把一个远程控制命令包装成 JSON-RPC 请求并发出去。它让上层不用每次手写请求消息格式。
数据流:进去的是 WebSocket、请求 id、方法名和可选参数 → 它组装 JSONRPCRequest,再通过 client::send_message 发到服务器 → 出来没有返回业务结果,只表示请求是否成功发出。
调用关系:request_remote_control_with_legacy_fallback 会调用它发送第一次请求和可能的兼容性重试。它位于“构造协议消息”和“真正写入连接”之间。
调用图:调用 1 个内部函数(send_message);被 1 处调用(request_remote_control_with_legacy_fallback);外部调用 1 个(Request)。
request_remote_control_with_legacy_fallback123–159 ↗
async fn request_remote_control_with_legacy_fallback(
websocket: &mut WebSocketStream<S>,
method: &str,
params: serde_json::Value,
) -> Result<T>
作用:发送远程控制请求,并自动兼容老服务器。新服务器用带参数的请求,老服务器如果报参数无效,就改用不带参数的旧格式再试一次。
数据流:进去的是 WebSocket、方法名和 JSON 参数 → 它先发送带参数请求,读取回应;如果成功就解析成目标类型,如果收到 invalid params 错误码,就再发送一次不带参数的请求 → 出来是解析好的响应对象,或者明确的失败错误。
调用关系:enable_remote_control_with_timeout 和 disable_remote_control 都靠它发送 enable/disable。它调用 send_remote_control_request 发消息,再调用 read_remote_control_response 等回应,是兼容新旧协议的关键夹层。
调用图:调用 2 个内部函数(read_remote_control_response, send_remote_control_request);被 2 处调用(disable_remote_control, enable_remote_control_with_timeout);外部调用 1 个(anyhow!)。
connect_with_retry161–183 ↗
async fn connect_with_retry(
socket_path: &Path,
connect_timeout: Duration,
connect_retry_delay: Duration,
) -> Result<WebSocketStream<codex_uds::UnixStream>>
作用:反复尝试连接 app server,直到成功或超时。它解决“服务器刚启动,还没开门”的竞态问题。
数据流:进去的是 socket 路径、最长连接等待时间、重试间隔 → 它计算截止时间,循环调用 client::connect;失败但还没超时就 sleep 一下再试 → 出来是一条 WebSocket 连接,或者带有“服务器没有准备好”说明的错误。
调用关系:enable_remote_control_with_connect_retry 专门调用它。它只负责把连接拿到手,拿到后由 enable_remote_control_with_timeout 继续做协议层面的开启。
调用图:调用 1 个内部函数(connect);被 1 处调用(enable_remote_control_with_connect_retry);外部调用 2 个(now, sleep)。
read_remote_control_response185–223 ↗
async fn read_remote_control_response(
websocket: &mut WebSocketStream<S>,
request_id: &RequestId,
method: &str,
) -> Result<RemoteControlRpcResponse<T>>
作用:等待某个远程控制请求的正式回应,并把它分成成功、参数无效、其他失败几类。
数据流:进去的是 WebSocket、请求 id 和方法名 → 它在固定超时时间内不断读消息;如果读到匹配 id 的 Response,就把 result 解析成目标类型;如果读到匹配 id 的 invalid params 错误,就返回特殊标记;如果是远程控制状态通知,就忽略后继续等 → 出来是成功响应、参数无效标记,或错误。
调用关系:request_remote_control_with_legacy_fallback 用它判断第一次请求是否要 fallback。它会调用 remote_control_status_notification 识别并跳过插队出现的状态通知,避免把通知误当成请求回应。
调用图:调用 2 个内部函数(read_message, remote_control_status_notification);被 1 处调用(request_remote_control_with_legacy_fallback);外部调用 3 个(anyhow!, Success, timeout)。
wait_for_remote_control_status225–257 ↗
async fn wait_for_remote_control_status(
websocket: &mut WebSocketStream<S>,
mut latest: RemoteControlReadyStatus,
ready_timeout: Duration,
) -> Result<RemoteControlReadyStatus>
作用:开启远程控制后,如果服务器还在“连接中”,这个函数会继续等状态通知,直到有明确结果或超时。
数据流:进去的是 WebSocket、当前已知状态和最长等待时间 → 它持续读取消息,只关心 remoteControl/status/changed 通知;每收到一次就更新 latest;一旦状态不再是 Connecting 就返回 → 如果等到超时,就把 latest.timed_out 设为 true 后返回。
调用关系:enable_remote_control_with_timeout 在初始 enable 响应是 Connecting 时调用它。它依赖 remote_control_status_notification 识别有效通知,属于“从正在连接等到最终状态”的收尾步骤。
调用图:调用 2 个内部函数(read_message, remote_control_status_notification);被 1 处调用(enable_remote_control_with_timeout);外部调用 3 个(from, now, timeout)。
remote_control_status_notification259–267 ↗
fn remote_control_status_notification(
notification: &JSONRPCNotification,
) -> Option<RemoteControlStatusChangedNotification>
作用:判断一条 JSON-RPC 通知是不是远程控制状态变化通知,并尝试把里面的数据读出来。
数据流:进去的是一条 JSONRPCNotification → 它先检查 method 是否等于 remoteControl/status/changed;如果不是就返回 None;如果是,就把 params 从 JSON 转成 RemoteControlStatusChangedNotification → 出来是解析好的状态通知,或者 None。
调用关系:read_remote_control_response 用它跳过不该处理的状态通知;wait_for_remote_control_status 用它找出真正的状态变化。它是通知筛选器,像收件箱里的“只看远程控制状态邮件”。
调用图:被 2 处调用(read_remote_control_response, wait_for_remote_control_status);外部调用 1 个(from_value)。
RemoteControlReadyStatus::from304–317 ↗
fn from(notification: RemoteControlStatusChangedNotification) -> Self
作用:把协议里的远程控制状态通知转换成客户端更好用的 RemoteControlReadyStatus。它会丢掉调用方当前不需要的 installation_id,并补上 timed_out=false。
数据流:进去的是 RemoteControlStatusChangedNotification,里面有连接状态、服务器名、安装 id、环境 id → 它取出状态、服务器名和环境 id,忽略 installation_id → 出来是 RemoteControlReadyStatus,默认表示不是因为等待超时得到的结果。
调用关系:wait_for_remote_control_status 收到状态通知后会用这个转换。文件里对 enable/disable 响应也有同名 From 转换,作用相同:把协议层对象统一变成上层要看的 ready status。
tests::enable_remote_control_uses_connected_enable_response_without_later_notification339–366 ↗
async fn enable_remote_control_uses_connected_enable_response_without_later_notification() -> Result<()>
作用:测试服务器一开始就回复 Connected 时,客户端不需要再等后续通知。
数据流:进去的是一套模拟场景:enable 响应直接是 Connected,没有后续通知 → 它运行 run_enable_remote_control_scenario → 出来用断言确认结果是 Connected、环境 id 正确、timed_out 为 false。
调用关系:这是开启流程的一个基础测试。它通过 run_enable_remote_control_scenario 搭起假服务器,间接覆盖 enable_remote_control_with_timeout 的“已连接就直接返回”分支。
调用图:外部调用 4 个(from_millis, remote_control_status, run_enable_remote_control_scenario, assert_eq!)。
tests::enable_remote_control_waits_for_connected_notification369–395 ↗
async fn enable_remote_control_waits_for_connected_notification() -> Result<()>
作用:测试服务器先说 Connecting、随后发 Connected 通知时,客户端会耐心等到真正连上。
数据流:进去的是模拟场景:enable 响应为 Connecting,之后发送 Connected 通知 → 测试运行开启流程 → 出来断言最终状态变成 Connected,且没有超时。
调用关系:它验证 enable_remote_control_with_timeout 会调用 wait_for_remote_control_status。假服务器由 run_enable_remote_control_scenario 和 serve_enable_remote_control_scenario 配合提供消息。
调用图:外部调用 4 个(from_secs, remote_control_status, run_enable_remote_control_scenario, assert_eq!)。
tests::enable_remote_control_reports_connecting_after_timeout398–421 ↗
async fn enable_remote_control_reports_connecting_after_timeout() -> Result<()>
作用:测试一直等不到最终通知时,客户端不会无限卡住,而是带着超时标记返回。
数据流:进去的是模拟场景:enable 响应为 Connecting,但后面没有状态通知,等待时间很短 → 流程运行到超时 → 出来断言状态仍是 Connecting,同时 timed_out 为 true。
调用关系:它专门覆盖 wait_for_remote_control_status 的超时行为,确保调用方能知道“还在连接,但我已经等不下去了”。
调用图:外部调用 4 个(from_millis, remote_control_status, run_enable_remote_control_scenario, assert_eq!)。
tests::enable_remote_control_returns_errored_enable_response424–447 ↗
async fn enable_remote_control_returns_errored_enable_response() -> Result<()>
作用:测试服务器直接回复 Errored 时,客户端会把错误状态原样报告出来,而不是继续等待。
数据流:进去的是模拟场景:enable 响应是 Errored,没有后续通知 → 开启流程读取响应后结束 → 出来断言状态是 Errored,timed_out 为 false。
调用关系:它验证 enable_remote_control_with_timeout 只有在 Connecting 时才会进入等待通知的流程;其他终态会直接返回。
调用图:外部调用 4 个(from_millis, remote_control_status, run_enable_remote_control_scenario, assert_eq!)。
tests::enable_remote_control_retries_without_params_for_older_servers450–473 ↗
async fn enable_remote_control_retries_without_params_for_older_servers() -> Result<()>
作用:测试开启远程控制时,如果老服务器不接受 ephemeral 参数,客户端会自动改用旧请求格式重试。
数据流:进去的是模拟场景:第一次带参数请求会被服务器用 invalid params 拒绝,第二次不带参数成功 → 测试跑完整开启流程 → 出来断言最终仍然 Connected,没有因为兼容问题失败。
调用关系:它覆盖 request_remote_control_with_legacy_fallback 在 enable 场景下的重试分支。serve_enable_remote_control_scenario 负责模拟老服务器的拒绝和成功回应。
调用图:外部调用 4 个(from_millis, remote_control_status, run_enable_remote_control_scenario, assert_eq!)。
tests::disable_remote_control_retries_without_params_for_older_servers476–539 ↗
async fn disable_remote_control_retries_without_params_for_older_servers() -> Result<()>
作用:测试关闭远程控制时,也能兼容不认识新参数的老服务器。
数据流:进去时测试创建临时 socket 和假服务器 → 假服务器先确认客户端发了带 ephemeral=true 的 disable 请求,再返回 invalid params;随后确认客户端发了不带参数的 fallback 请求,并返回 Disabled → 出来断言 disable_remote_control 得到 Disabled 状态。
调用关系:这是 disable_remote_control 和 request_remote_control_with_legacy_fallback 的集成式测试。它自己启动假服务器,复用 accept_initialized_client 完成初始化握手,再检查请求细节。
调用图:调用 5 个内部函数(read_message, send_message, disable_remote_control, from, bind);外部调用 9 个(new, accept_initialized_client, remote_control_status, Error, Response, assert_eq!, panic!, to_value, spawn)。
tests::run_enable_remote_control_scenario549–562 ↗
async fn run_enable_remote_control_scenario(
scenario: EnableScenario,
) -> Result<RemoteControlReadyStatus>
作用:给多个开启远程控制测试复用的场景运行器。它负责搭一个临时假服务器,然后让客户端去连它。
数据流:进去的是 EnableScenario,描述假服务器该怎么回应 → 它创建临时目录和 socket,绑定 UnixListener,启动 serve_enable_remote_control_scenario,再让客户端 connect 并调用 enable_remote_control_with_timeout → 出来是客户端得到的 RemoteControlReadyStatus。
调用关系:多个 enable 测试都调用它,避免每个测试重复写服务器搭建代码。它连接测试用例和真正的 enable_remote_control_with_timeout。
调用图:调用 3 个内部函数(connect, enable_remote_control_with_timeout, bind);外部调用 3 个(new, serve_enable_remote_control_scenario, spawn)。
tests::serve_enable_remote_control_scenario564–622 ↗
async fn serve_enable_remote_control_scenario(
listener: UnixListener,
scenario: EnableScenario,
) -> Result<()>
作用:扮演一个假的 app server,用来测试客户端开启远程控制时的各种情况。
数据流:进去的是 UnixListener 和 EnableScenario → 它接受并初始化客户端,按场景可能先发状态通知,然后读取 enable 请求;如果场景要求拒绝参数,就先发 invalid params,再等 fallback 请求;之后发 enable 响应,并可能再发后续状态通知 → 出来没有业务返回,但完成了假服务器的整段对话。
调用关系:run_enable_remote_control_scenario 会把它作为后台任务启动。它通过 accept_initialized_client、send_remote_control_status、client::read_message 和 send_message 精确模拟真实服务器行为。
调用图:调用 3 个内部函数(read_message, send_message, from);外部调用 9 个(from_millis, accept_initialized_client, send_remote_control_status, Error, Response, assert_eq!, panic!, to_value, sleep)。
tests::accept_initialized_client624–662 ↗
async fn accept_initialized_client(
mut listener: UnixListener,
) -> Result<WebSocketStream<codex_uds::UnixStream>>
作用:测试里用来接受一个客户端连接,并验证客户端确实按协议完成初始化。
数据流:进去的是 UnixListener → 它接受 Unix socket 连接,升级成 WebSocket,读取 initialize 请求,检查 experimentalApi=true,回复 initialize 结果;然后读取 initialized 通知 → 出来是一条已经初始化好的 WebSocket,供假服务器继续收发远程控制消息。
调用关系:serve_enable_remote_control_scenario 和 disable 的兼容性测试都会调用它。它把测试里的“握手前置流程”统一起来,让后面的测试只关注 enable/disable 本身。
调用图:调用 3 个内部函数(read_message, send_message, accept);外部调用 5 个(Response, assert_eq!, panic!, json!, accept_async)。
tests::send_remote_control_status664–679 ↗
async fn send_remote_control_status(
websocket: &mut WebSocketStream<S>,
status: RemoteControlStatusChangedNotification,
) -> Result<()>
作用:测试辅助函数,用来让假服务器发送一条远程控制状态变化通知。
数据流:进去的是 WebSocket 和一个状态通知结构 → 它把状态结构转成 JSON,包装成 method 为 remoteControl/status/changed 的 JSON-RPC 通知,再发给客户端 → 出来没有返回业务数据,只表示发送是否成功。
调用关系:serve_enable_remote_control_scenario 在需要模拟状态变化时调用它。它配合客户端的 remote_control_status_notification 和 wait_for_remote_control_status,测试通知等待流程。
调用图:调用 1 个内部函数(send_message);外部调用 2 个(Notification, to_value)。
tests::remote_control_status681–691 ↗
fn remote_control_status(
status: RemoteControlConnectionStatus,
environment_id: Option<&str>,
) -> RemoteControlStatusChangedNotification
作用:测试辅助函数,用少量参数快速造出一份远程控制状态通知。
数据流:进去的是连接状态和可选环境 id → 它填入固定的测试服务器名、测试安装 id,并把环境 id 转成字符串 → 出来是 RemoteControlStatusChangedNotification,供测试场景和假服务器使用。
调用关系:多个测试用它构造 enable 响应或状态通知。它让测试代码更短,也保证所有测试使用同一套服务器名和安装 id。
服务器引导与连接路由
这些文件启动 app-server 运行时本身,初始化连接,并管理进程内和外部传输的出站交付。
app-server/src/lib.rs源码 ↗
可以把这个文件理解成“开店前的店长清单”和“营业时的总控室”。启动时,它先读配置,准备运行目录、身份信息、日志、遥测(把运行情况记录下来)、本地 SQLite 数据库(一种本地小数据库),并把配置错误整理成客户端能看懂的警告。然后它根据启动方式打开不同入口:标准输入输出、本地 socket、WebSocket,或远程控制。运行中,它用异步通道(像排队传纸条的管道)接收连接事件和 JSON-RPC 消息,再交给 MessageProcessor 做具体请求处理;另一个后台任务专门负责把回复发回各个客户端。它还处理数据库损坏时的自动备份重建,以及收到退出信号时先等正在进行的对话结束,再断开连接,避免粗暴关机。
configured_thread_config_loader136–141 ↗
fn configured_thread_config_loader(config: &Config) -> Arc<dyn ThreadConfigLoader>
作用:根据配置决定线程配置从哪里来。简单说,有远程地址就用远程加载器,没有就用什么都不做的默认加载器。
数据流:进去的是当前总配置 → 它查看是否设置了 experimental_thread_config_endpoint → 出来的是一个可共享的配置加载器,后续组件用它读取每个线程可能不同的配置。
调用关系:run_main_with_transport_options 在预加载配置后调用它,用发现到的设置替换 ConfigManager 里的默认加载器,为后面的线程配置加载做准备。
调用图:调用 1 个内部函数(new);被 1 处调用(run_main_with_transport_options);外部调用 1 个(new)。
shutdown_signal187–208 ↗
async fn shutdown_signal() -> IoResult<ShutdownSignal>
作用:等待操作系统发来的关机或重启信号。它把 Ctrl+C、终止信号等统一翻译成服务器能理解的退出类型。
数据流:进去没有业务数据,只监听系统信号 → 在 Unix 上同时等 Ctrl+C、TERM、HUP,在非 Unix 上等 Ctrl+C → 出来是一个 ShutdownSignal,说明能不能被第二次信号强制打断。
调用关系:处理器主循环在允许优雅重启时会等它返回;返回后交给 ShutdownState::on_signal 更新退出状态。
ShutdownState::requested211–213 ↗
fn requested(&self) -> bool
作用:告诉外面:当前是否已经有人请求服务器退出或重启。
数据流:进去的是 ShutdownState 自己保存的状态 → 读取 requested 这个布尔值 → 出来是真或假,不改动任何东西。
调用关系:处理器主循环用它判断是否需要继续监听正在运行任务数量的变化,以便优雅退出。
ShutdownState::forced215–217 ↗
fn forced(&self) -> bool
作用:告诉外面:当前退出是否已经升级成强制退出。
数据流:进去的是 ShutdownState 自己保存的状态 → 读取 forced 这个布尔值 → 出来是真或假,不改动任何东西。
调用关系:处理器主循环用它决定是否还要继续监听退出信号,以及收尾时是耐心清理还是直接中止清理任务。
ShutdownState::on_signal219–238 ↗
fn on_signal(
&mut self,
signal: ShutdownSignal,
connection_count: usize,
running_turn_count: usize,
)
作用:收到退出信号时更新关机状态。第一次信号表示“开始优雅退出”,第二次可强制的信号表示“别等了,马上退”。
数据流:进去的是信号类型、当前连接数、正在运行的 assistant turn 数量 → 它设置 requested,必要时设置 forced,并写日志 → 出来没有返回值,但 ShutdownState 的内部状态被更新。
调用关系:shutdown_signal 捕捉到系统信号后,处理器主循环调用它;之后 ShutdownState::update 会根据新状态决定是否真正结束服务器。
ShutdownState::update240–266 ↗
fn update(&mut self, running_turn_count: usize, connection_count: usize) -> ShutdownAction
作用:定期判断优雅退出是否可以完成。它会等正在运行的对话轮次结束,除非已经被强制退出。
数据流:进去的是当前正在运行的 turn 数和连接数 → 它检查是否已请求退出、是否强制、是否还有任务没结束,并按需写日志 → 出来是 Noop 或 Finish,表示继续等还是可以收尾。
调用关系:处理器主循环每轮都会调用它;一旦返回 Finish,主循环会停止接收连接并通知所有连接断开。
调用图:外部调用 1 个(info!)。
config_warning_from_error269–283 ↗
fn config_warning_from_error(
summary: impl Into<String>,
err: &std::io::Error,
) -> ConfigWarningNotification
作用:把配置读取错误包装成客户端能显示的配置警告。这样用户不仅知道“配置坏了”,还能看到错误详情和可能的位置。
数据流:进去的是警告摘要和一个 IO 错误 → 它尝试从错误里挖出配置文件路径和文本范围,再把错误文字放进 details → 出来是 ConfigWarningNotification。
调用关系:run_main_with_transport_options 在非严格配置模式下读配置失败时调用它;它内部会请 config_error_location 帮忙找具体位置。
调用图:调用 1 个内部函数(config_error_location);被 1 处调用(run_main_with_transport_options);外部调用 2 个(into, to_string)。
config_error_location285–295 ↗
fn config_error_location(err: &std::io::Error) -> Option<(String, AppTextRange)>
作用:从配置错误里提取“错在哪个文件、哪几行哪几列”。如果错误不是配置解析错误,就不硬猜。
数据流:进去的是一个 IO 错误 → 它查看错误内部是否包着 ConfigLoadError,并转换其中的路径和文本范围 → 出来是可选的位置数据。
调用关系:只被 config_warning_from_error 调用,是生成友好配置警告时的定位小助手。
调用图:被 1 处调用(config_warning_from_error);外部调用 1 个(get_ref)。
exec_policy_warning_location297–317 ↗
fn exec_policy_warning_location(err: &ExecPolicyError) -> (Option<String>, Option<AppTextRange>)
作用:从执行策略错误里提取位置。执行策略可以理解成“哪些命令允许跑、哪些不允许”的规则文件。
数据流:进去的是 ExecPolicyError → 如果是规则解析失败,就尽量取出文件路径和文本范围;其他错误则不给位置 → 出来是可选路径和可选范围。
调用关系:run_main_with_transport_options 检查执行策略警告后调用它,用来把规则解析失败的位置显示给客户端。
调用图:被 1 处调用(run_main_with_transport_options)。
app_text_range319–330 ↗
fn app_text_range(range: &CoreTextRange) -> AppTextRange
作用:把核心配置模块里的文本范围格式,转换成应用服务器协议里的文本范围格式。
数据流:进去的是 CoreTextRange,包含开始和结束的行列号 → 它逐项复制到 AppTextRange → 出来是客户端协议认识的位置格式。
调用关系:config_error_location 在提取配置错误位置时用它做格式转换,避免把内部类型直接暴露给协议层。
project_config_warning332–372 ↗
fn project_config_warning(config: &Config) -> Option<ConfigWarningNotification>
作用:检查有没有项目本地配置因为项目未受信任而被禁用,并生成一条清楚的提醒。
数据流:进去的是总配置 → 它遍历配置层,找出被禁用的项目 .codex 文件夹和原因 → 如果找到了,就出来一条包含文件夹列表和原因的 ConfigWarningNotification;否则出来 None。
调用关系:run_main_with_transport_options 在启动时调用它,把项目配置被禁用这类安全提示加入启动警告列表。
调用图:被 1 处调用(run_main_with_transport_options);外部调用 3 个(new, concat!, format!)。
LogFormat::from_env_value375–380 ↗
fn from_env_value(value: Option<&str>) -> Self
作用:把环境变量里的日志格式文字转换成程序内部的枚举。只有写成 json(大小写和空格不敏感)才会启用 JSON 日志。
数据流:进去的是可选字符串 → 它去掉首尾空格、转成小写并判断是否等于 json → 出来是 LogFormat::Json 或 LogFormat::Default。
调用关系:log_format_from_env 调用它;相关测试会验证 json、JSON、带空格的 Json 都能识别,其他值都走默认格式。
调用图:被 1 处调用(log_format_from_env)。
log_format_from_env383–386 ↗
fn log_format_from_env() -> LogFormat
作用:读取 LOG_FORMAT 环境变量,决定日志输出成普通文本还是 JSON。
数据流:进去没有参数 → 它从系统环境变量读取 LOG_FORMAT → 交给 LogFormat::from_env_value 解析 → 出来是日志格式选择。
调用关系:run_main_with_transport_options 在安装日志系统时调用它,然后选择对应的 tracing 日志层。
调用图:调用 1 个内部函数(from_env_value);被 1 处调用(run_main_with_transport_options);外部调用 1 个(var)。
run_main388–407 ↗
async fn run_main(
arg0_paths: Arg0DispatchPaths,
cli_config_overrides: CliConfigOverrides,
loader_overrides: LoaderOverrides,
strict_config: bool,
default_analytics_enabled: bool,
作用:这是最常用的服务器启动入口。调用者只给基础启动参数,它会填上默认传输方式和默认运行选项。
数据流:进去的是 arg0 路径、命令行配置覆盖、加载器覆盖、是否严格配置、默认是否开分析 → 它补上 Stdio、VSCode 会话来源、默认认证和默认运行选项 → 出来是 run_main_with_transport_options 的运行结果。
调用关系:外部通常调用它启动应用服务器;真正的大量启动和运行工作都交给 run_main_with_transport_options。
调用图:调用 2 个内部函数(default, run_main_with_transport_options);外部调用 1 个(default)。
AppServerRuntimeOptions::default423–429 ↗
fn default() -> Self
作用:提供应用服务器运行选项的默认值。默认会跑插件启动任务、按持久化偏好决定远程控制、并安装退出信号处理器。
数据流:进去没有参数 → 它构造 AppServerRuntimeOptions → 出来是一份默认运行设置。
调用关系:run_main 使用它给普通启动路径补齐运行选项;测试或嵌入式启动可以不用默认值,自己传入特殊选项。
调用图:被 1 处调用(run_main)。
run_main_with_transport_options433–1180 ↗
async fn run_main_with_transport_options(
arg0_paths: Arg0DispatchPaths,
cli_config_overrides: CliConfigOverrides,
loader_overrides: LoaderOverrides,
strict_config: bool,
default_a
作用:这是整个应用服务器最核心的启动和运行函数。它从读配置开始,一直负责到连接接入、消息分发、后台任务、远程控制、优雅关机全部收尾。
数据流:进去的是启动路径、配置覆盖、传输方式、会话来源、认证设置和运行选项 → 它读取并修正配置,初始化数据库、日志、遥测、认证、远程控制和传输监听器;运行中把传入消息交给 MessageProcessor,把传出消息交给 outbound router;退出时关闭连接、清理后台任务并关闭遥测 → 出来是成功或 IO 错误,同时会启动和结束多个异步任务。
调用关系:run_main 会调用它。它是本文件的总导演:调用 configured_thread_config_loader、config_warning_from_error、project_config_warning、init_sqlite_state_db_with_fresh_start_on_corruption、analytics_rpc_transport 等小工具,也启动 transport 和 MessageProcessor 等其他模块来完成实际工作。
调用图:调用 27 个内部函数(app_server_startup_lock_path, policy_from_settings, analytics_rpc_transport, analytics_events_client_from_config, new, config_warning_from_error, configured_thread_config_loader, new, exec_policy_warning_location, init_sqlite_state_db_with_fresh_start_on_corruption (+15 more));被 1 处调用(run_main);外部调用 33 个(clone, new, new, default, from_default_env, new, new, new, new, default (+15 more))。
init_sqlite_state_db_with_fresh_start_on_corruption1196–1267 ↗
async fn init_sqlite_state_db_with_fresh_start_on_corruption(
config: &Config,
) -> anyhow::Result<StateDbInitResult>
作用:初始化本地 SQLite 状态数据库,并在发现数据库损坏时尝试把坏文件移走后重新开始。这样服务器不至于因为一个坏数据库完全起不来。
数据流:进去的是配置,里面有数据库目录 → 它尝试初始化数据库;如果成功,就带着可能的恢复提示返回;如果发现 SQLite 损坏或数据库目录被文件挡住,就备份坏文件再重试;如果不是可恢复问题或重复失败,就返回错误 → 出来是 StateDbInitResult,包含可用数据库句柄和恢复通知。
调用关系:run_main_with_transport_options 启动早期调用它。它内部会用 sqlite_home_is_blocking_file 判断特殊文件问题,用 sqlite_recovery_notice 生成用户提示,用 emit_state_db_backup_warning 打印恢复过程。
调用图:调用 3 个内部函数(emit_state_db_backup_warning, sqlite_home_is_blocking_file, sqlite_recovery_notice);被 1 处调用(run_main_with_transport_options);外部调用 8 个(new, new, anyhow!, backup_runtime_db_for_fresh_start, is_sqlite_corruption_error, runtime_db_path_for_corruption_error, format!, try_init)。
sqlite_home_is_blocking_file1269–1274 ↗
fn sqlite_home_is_blocking_file(database_path: &Path) -> bool
作用:判断数据库目录位置是不是被一个普通文件挡住了。比如本来该是文件夹的地方却已经有个同名文件。
数据流:进去的是数据库文件路径 → 它查看父路径的文件系统信息 → 如果父路径存在且是文件,出来 true,否则 false。
调用关系:init_sqlite_state_db_with_fresh_start_on_corruption 在数据库初始化失败时调用它,用来判断是否可以通过备份并重建来恢复。
调用图:被 1 处调用(init_sqlite_state_db_with_fresh_start_on_corruption);外部调用 1 个(parent)。
sqlite_recovery_notice1276–1294 ↗
fn sqlite_recovery_notice(
recovered_databases: &[RecoveredSqliteDatabase],
) -> Option<SqliteRecoveryNotice>
作用:把已经恢复过的数据库列表整理成一段给用户看的说明。
数据流:进去的是恢复记录列表 → 如果为空就返回 None;如果不为空,就把每个原数据库路径和备份文件夹拼成文字 → 出来是 SqliteRecoveryNotice。
调用关系:init_sqlite_state_db_with_fresh_start_on_corruption 在数据库最终初始化成功后调用它,用来生成启动警告,告诉用户数据库被重建且旧文件放在哪里。
调用图:被 1 处调用(init_sqlite_state_db_with_fresh_start_on_corruption);外部调用 2 个(is_empty, iter)。
emit_state_db_backup_warning1296–1304 ↗
fn emit_state_db_backup_warning(message: &str)
作用:输出数据库备份和恢复相关警告。即使日志系统还没装好,也尽量把消息打到标准错误里让人看见。
数据流:进去的是一段警告文字 → 它先用 tracing 的 warn 记录;如果全局日志分发器还没设置,就额外 eprintln 到 stderr → 出来没有返回值,只产生可见日志。
调用关系:init_sqlite_state_db_with_fresh_start_on_corruption 在发现损坏、移动坏文件、恢复成功时多次调用它,保证关键恢复信息不会悄悄丢失。
调用图:被 1 处调用(init_sqlite_state_db_with_fresh_start_on_corruption);外部调用 3 个(eprintln!, has_been_set, warn!)。
test_user_config_file_from_env1306–1316 ↗
fn test_user_config_file_from_env() -> Option<std::path::PathBuf>
作用:在调试构建中读取一个专门给测试用的用户配置文件路径。正式构建里它永远不启用。
数据流:调试构建时,进去没有参数 → 它读取 CODEX_APP_SERVER_TEST_USER_CONFIG_FILE 环境变量,空值忽略 → 出来是可选路径;非调试构建直接出来 None。
调用关系:run_main_with_transport_options 启动最早期调用它,再交给 loader_overrides_with_test_user_config_file 合并进配置加载设置。
调用图:被 1 处调用(run_main_with_transport_options);外部调用 1 个(var_os)。
loader_overrides_with_test_user_config_file1318–1341 ↗
fn loader_overrides_with_test_user_config_file(
mut loader_overrides: LoaderOverrides,
test_user_config_file: Option<std::path::PathBuf>,
) -> IoResult<LoaderOverrides>
作用:把调试专用的测试配置文件路径塞进配置加载覆盖项里。这样测试可以指定一份临时配置,而不用污染真实用户配置。
数据流:进去的是原本的 LoaderOverrides 和可选测试配置路径 → 调试构建下,如果路径存在,就校验它是绝对路径并写入 user_config_path;非调试构建忽略该路径 → 出来是更新后的 LoaderOverrides 或路径错误。
调用关系:run_main_with_transport_options 在加载配置前调用它;测试函数 tests::debug_test_user_config_file_overrides_loader_path 也调用它来确认调试覆盖行为正确。
调用图:调用 1 个内部函数(from_absolute_path);被 2 处调用(run_main_with_transport_options, debug_test_user_config_file_overrides_loader_path);外部调用 1 个(warn!)。
analytics_rpc_transport1343–1350 ↗
fn analytics_rpc_transport(transport: &AppServerTransport) -> AppServerRpcTransport
作用:把服务器实际使用的连接方式翻译成分析系统认识的 RPC 传输类型。
数据流:进去的是 AppServerTransport → 如果是 Stdio 就输出 Stdio;UnixSocket、WebSocket 和 Off 都归到 Websocket 这类分析标签 → 出来是 AppServerRpcTransport。
调用关系:run_main_with_transport_options 创建 MessageProcessor 参数时调用它,用于后续分析事件标记这次会话大致通过哪种通道通信。
调用图:被 1 处调用(run_main_with_transport_options)。
tests::log_format_from_env_value_matches_json_values_case_insensitively1364–1368 ↗
fn log_format_from_env_value_matches_json_values_case_insensitively()
作用:测试日志格式解析是否能不区分大小写地识别 json。
数据流:进去是测试里写死的几个字符串:json、JSON、带空格的 Json → 调用 LogFormat::from_env_value 并比较结果 → 如果都得到 Json,测试通过;否则测试失败。
调用关系:它验证 LogFormat::from_env_value 的关键行为,防止以后改代码时把环境变量解析弄得过于严格。
调用图:外部调用 1 个(assert_eq!)。
tests::log_format_from_env_value_defaults_for_non_json_values1371–1379 ↗
fn log_format_from_env_value_defaults_for_non_json_values()
作用:测试非 json 的日志格式值都会回到默认日志格式。
数据流:进去是 None、空字符串、text、jsonl 等测试值 → 调用 LogFormat::from_env_value → 期望输出全是 Default。
调用关系:它和前一个日志格式测试配套,保证只有明确写 json 才切换格式,避免用户拼错变量后进入意外模式。
调用图:外部调用 1 个(assert_eq!)。
tests::debug_test_user_config_file_overrides_loader_path1383–1395 ↗
fn debug_test_user_config_file_overrides_loader_path()
作用:测试调试构建下,测试配置文件路径确实会覆盖配置加载器的用户配置路径。
数据流:进去是临时目录里拼出的测试配置路径和默认 LoaderOverrides → 调用 loader_overrides_with_test_user_config_file → 检查输出里的 user_config_path 是否等于那个绝对路径。
调用关系:它直接覆盖 loader_overrides_with_test_user_config_file 的调试专用分支,确保测试环境能安全指定自己的配置文件。
调用图:调用 1 个内部函数(loader_overrides_with_test_user_config_file);外部调用 3 个(assert_eq!, default, temp_dir)。
app-server/src/outgoing_message.rs源码 ↗
服务器和客户端不是只说一句话就结束,而是会来回对话:服务器可能问客户端“这个操作能不能批准?”,客户端之后再回答;客户端发来的请求,服务器也要回结果或错误。这个文件就是这套对话的中转台。它用通道(可以理解成排队邮箱)把消息交给真正写网络连接的部分;用一次性回信通道 oneshot(只等一次结果的小信箱)保存“这条请求的答复该通知谁”;还保存请求的追踪信息,方便日志和性能追踪把一次请求从开始串到结束。ThreadScopedOutgoingMessageSender 是按某个会话线程缩小范围的发信工具,只给相关连接发消息,避免发错人。文件里也有一组测试,确认各种通知会变成正确的 JSON-RPC 消息,以及请求、回复、取消、断线清理都按预期工作。
RequestContext::new58–68 ↗
fn new(
request_id: ConnectionRequestId,
span: Span,
parent_trace: Option<W3cTraceContext>,
) -> Self
作用:创建一份请求上下文,也就是把“这是哪个连接的哪个请求”和它的追踪信息放在一起。后面发最终回复或错误时,会靠它把日志链路接上。
数据流:传入连接内的请求编号、当前追踪 span(一次工作的日志范围)和可选的父级追踪信息 → 函数把它们原样装进 RequestContext → 返回这份上下文,不会发送消息或改动外部状态。
调用关系:客户端请求刚进入处理流程时会调用它建立记录;之后处理请求、关闭连接、发送回复等流程会拿这份记录来查追踪信息或清理。
调用图:被 4 处调用(process_client_request, process_request, connection_closed_clears_registered_request_contexts, send_response_clears_registered_request_context)。
RequestContext::request_trace70–72 ↗
fn request_trace(&self) -> Option<W3cTraceContext>
作用:取出这次请求可继续传下去的追踪上下文。简单说,就是给后续工作一张“我属于哪次请求”的标签。
数据流:读取自己保存的 span 和父级追踪信息 → 先尝试从 span 里提取 W3C Trace Context(一种跨服务传递追踪编号的标准格式)→ 如果没有,就退回父级追踪信息 → 返回可选的追踪上下文。
调用关系:线程启动等后续处理需要延续同一条追踪链时会用它;它把具体提取工作交给 span_w3c_trace_context。
调用图:被 1 处调用(thread_start_inner);外部调用 1 个(span_w3c_trace_context)。
RequestContext::span74–76 ↗
fn span(&self) -> Span
作用:拿到这次请求的 span。span 可以理解成日志里的一个“工作区间”,用来标记这段异步工作属于哪次请求。
数据流:读取内部保存的 span → 克隆一份轻量句柄 → 返回给调用方,原来的上下文不变。
调用关系:分发和运行请求时会用它给异步任务挂上追踪信息;发送最终消息时也可能用它包住发送动作。
调用图:被 3 处调用(dispatch_initialized_client_request, run_request_with_context, thread_start_inner);外部调用 1 个(clone)。
RequestContext::record_turn_id78–80 ↗
fn record_turn_id(&self, turn_id: &str)
作用:把 turn_id 记录到这次请求的追踪日志里。turn 可以理解成一次对话回合,这样日志里能看出请求属于哪个回合。
数据流:传入一个 turn_id 字符串 → 写入当前 span 的 turn.id 字段 → 没有返回值,只更新追踪信息。
调用关系:当系统后来知道某个请求对应哪个回合时会调用它;它不发消息,只补全日志标签。
调用图:外部调用 1 个(record)。
ThreadScopedOutgoingMessageSender::new121–131 ↗
fn new(
outgoing: Arc<OutgoingMessageSender>,
connection_ids: Vec<ConnectionId>,
thread_id: ThreadId,
) -> Self
作用:创建一个“只服务某个线程/会话”的发信器。它会记住可发送的连接列表和线程编号,避免把线程内消息发到不相关的客户端。
数据流:传入全局 OutgoingMessageSender、连接编号列表和线程编号 → 把连接列表放进可共享的 Arc(多处安全共用的引用)里 → 返回线程范围的发送器。
调用关系:线程相关的事件处理和大量测试会先创建它;之后它把真正发送工作委托给内部的 OutgoingMessageSender。
调用图:被 18 处调用(command_execution_started_helper_emits_once, complete_command_execution_item_emits_declined_once_for_pending_command, guardian_command_execution_notifications_wrap_review_lifecycle, interrupted_subagent_activity_removes_missing_thread_watch, test_handle_token_count_event_emits_usage_and_rate_limits, test_handle_token_count_event_without_usage_info, test_handle_turn_complete_emits_completed_without_error, test_handle_turn_complete_emits_error_multiple_turns, test_handle_turn_complete_emits_failed_with_error, test_handle_turn_diff_emits_v2_notification (+8 more));外部调用 1 个(new)。
ThreadScopedOutgoingMessageSender::send_request133–144 ↗
async fn send_request(
&self,
payload: ServerRequestPayload,
) -> (RequestId, oneshot::Receiver<ClientRequestResult>)
作用:向这个线程对应的客户端连接发送一个需要客户端回答的请求,比如请求用户批准某个操作。
数据流:传入服务器请求内容 → 加上当前线程编号和连接范围 → 调用全局发送器发送 → 返回请求编号和一个等待客户端答复的接收端。
调用关系:线程事件处理遇到需要客户端决策的场景会用它;实际编号生成、排队发送和回调保存由 OutgoingMessageSender::send_request_to_connections 完成。
调用图:被 1 处调用(apply_bespoke_event_handling)。
ThreadScopedOutgoingMessageSender::track_effective_permissions_approval_response146–158 ↗
fn track_effective_permissions_approval_response(
&self,
request_id: RequestId,
response: RequestPermissionsResponse,
)
作用:记录一次权限批准结果的分析事件。它主要用于产品统计或诊断:用户最后同意了什么权限。
数据流:传入请求编号和权限响应 → 读取当前毫秒时间戳 → 把时间、请求编号、响应交给分析客户端 → 不返回业务结果。
调用关系:线程内收到权限批准结果后会调用它;时间由 now_unix_timestamp_ms 提供,事件由 analytics_events_client 发送或记录。
调用图:调用 1 个内部函数(now_unix_timestamp_ms)。
ThreadScopedOutgoingMessageSender::send_server_notification160–170 ↗
async fn send_server_notification(&self, notification: ServerNotification)
作用:给这个线程相关的连接发送服务器通知。通知是不需要客户端回答的消息,比如状态更新、警告、进度变化。
数据流:传入通知 → 先记录一条分析事件 → 如果没有连接就直接结束 → 否则把通知交给全局发送器,发给保存的连接列表。
调用关系:很多事件处理函数会用它把线程内变化推给客户端;真正排队发送由 OutgoingMessageSender::send_server_notification_to_connections 做。
调用图:被 10 处调用(apply_bespoke_event_handling, complete_command_execution_item, emit_turn_completed_with_status, handle_error_notification, handle_token_count_event, handle_turn_diff, handle_turn_plan_update, maybe_emit_hook_prompt_item_completed, maybe_emit_raw_response_item_completed, start_command_execution_item);外部调用 1 个(clone)。
ThreadScopedOutgoingMessageSender::send_global_server_notification172–174 ↗
async fn send_global_server_notification(&self, notification: ServerNotification)
作用:发送一条全局通知,不局限于这个线程的连接。适合账号、导入进度这类所有客户端都可能需要知道的消息。
数据流:传入通知 → 直接交给全局 OutgoingMessageSender → 由它广播给客户端。
调用关系:线程事件里如果遇到全局事项会调用它;它只是一个方便入口,不自己处理发送细节。
调用图:被 1 处调用(apply_bespoke_event_handling)。
ThreadScopedOutgoingMessageSender::abort_pending_server_requests176–191 ↗
async fn abort_pending_server_requests(&self)
作用:取消这个线程里还没收到客户端回答的服务器请求。常见原因是对话回合状态变了,旧问题已经不该再等答案。
数据流:不需要额外输入 → 构造一个内部错误,并在错误数据里写明“回合切换导致取消” → 要求全局发送器取消该线程的等待请求,并把错误通知给等待者。
调用关系:线程事件处理发现状态切换时会调用它;错误对象由 internal_error 创建,实际查找和通知由 cancel_requests_for_thread 完成。
调用图:调用 1 个内部函数(internal_error);被 1 处调用(apply_bespoke_event_handling);外部调用 1 个(json!)。
ThreadScopedOutgoingMessageSender::send_response193–198 ↗
async fn send_response(&self, request_id: ConnectionRequestId, response: T)
作用:对客户端发来的某个请求发送成功回复。它是线程范围里的便捷转发口。
数据流:传入连接请求编号和响应内容 → 把响应交给全局发送器 → 全局发送器负责序列化成 JSON-RPC 回复并发到对应连接。
调用关系:线程事件处理或处理中断请求时会用它;真正发送路径走 OutgoingMessageSender::send_response。
调用图:被 2 处调用(apply_bespoke_event_handling, respond_to_pending_interrupts)。
ThreadScopedOutgoingMessageSender::send_error200–206 ↗
async fn send_error(
&self,
request_id: ConnectionRequestId,
error: impl Into<JSONRPCErrorError>,
)
作用:对客户端发来的某个请求发送错误回复。比如操作失败、回滚失败时,用它告诉客户端失败原因。
数据流:传入连接请求编号和错误 → 转成标准 JSON-RPC 错误 → 交给全局发送器发到对应连接。
调用关系:线程事件处理和失败处理会调用它;实际包装和排队发送由 OutgoingMessageSender::send_error 完成。
调用图:被 2 处调用(apply_bespoke_event_handling, handle_thread_rollback_failed)。
OutgoingMessageSender::new210–221 ↗
fn new(
sender: mpsc::Sender<OutgoingEnvelope>,
analytics_events_client: AnalyticsEventsClient,
) -> Self
作用:创建全局的出站消息发送器。它准备好发信通道、请求编号计数器、等待回调表和请求上下文表。
数据流:传入一个 mpsc 发送端(多生产者单消费者的异步队列入口)和分析事件客户端 → 初始化请求编号为 0,并创建两个受 Mutex 保护的表 → 返回可用的发送器。
调用关系:服务器启动或测试搭建时会创建它;后续所有请求、回复、通知、取消都围绕这个对象进行。
调用图:被 38 处调用(command_execution_started_helper_emits_once, complete_command_execution_item_emits_declined_once_for_pending_command, guardian_command_execution_notifications_wrap_review_lifecycle, interrupted_subagent_activity_removes_missing_thread_watch, test_handle_token_count_event_emits_usage_and_rate_limits, test_handle_token_count_event_without_usage_info, test_handle_turn_complete_emits_completed_without_error, test_handle_turn_complete_emits_error_multiple_turns, test_handle_turn_complete_emits_failed_with_error, test_handle_turn_diff_emits_v2_notification (+15 more));外部调用 3 个(new, new, new)。
OutgoingMessageSender::register_request_context223–231 ↗
async fn register_request_context(&self, request_context: RequestContext)
作用:登记一个正在处理的客户端请求上下文。这样在最后回复或出错时,还能找到它的追踪信息。
数据流:传入 RequestContext → 锁住上下文表 → 用连接请求编号作为钥匙保存进去 → 如果旧记录被替换,就写警告日志。
调用关系:收到客户端请求后会调用它;send_response_as 和 send_error 后面会取走并清理这份上下文。
调用图:外部调用 1 个(warn!)。
OutgoingMessageSender::connection_closed233–236 ↗
async fn connection_closed(&self, connection_id: ConnectionId)
作用:连接断开时清掉属于这个连接的未完成请求上下文。否则内存里会留下已经不可能完成的记录。
数据流:传入连接编号 → 锁住上下文表 → 删除所有 connection_id 相同的记录 → 不返回值。
调用关系:传输层发现客户端断线时会调用它;它只清理入站请求上下文,不处理服务器发给客户端后等待回答的回调表。
OutgoingMessageSender::request_trace_context238–246 ↗
async fn request_trace_context(
&self,
request_id: &ConnectionRequestId,
) -> Option<W3cTraceContext>
作用:查询某个未完成请求的追踪上下文。外部流程可用它把后续工作接到同一条日志链路上。
数据流:传入连接请求编号 → 锁住上下文表查找对应 RequestContext → 如果找到,就取它的 request_trace → 返回可选追踪信息。
调用关系:需要在请求处理中继续传递追踪信息的地方会用它;具体追踪提取由 RequestContext::request_trace 完成。
OutgoingMessageSender::record_request_turn_id248–257 ↗
async fn record_request_turn_id(
&self,
request_id: &ConnectionRequestId,
turn_id: &str,
)
作用:给某个未完成请求补记它所属的对话回合编号。这样之后查日志更容易定位到具体回合。
数据流:传入连接请求编号和 turn_id → 锁住上下文表查找 → 找到就调用上下文记录 turn_id → 找不到则静默跳过。
调用关系:当请求和对话回合建立关联后会调用它;它把实际写入交给 RequestContext::record_turn_id。
OutgoingMessageSender::take_request_context259–265 ↗
async fn take_request_context(
&self,
request_id: &ConnectionRequestId,
) -> Option<RequestContext>
作用:取走并删除某个请求上下文。名字里的 take 表示“拿出来后原处就没有了”。
数据流:传入连接请求编号 → 锁住上下文表 → 删除并返回对应 RequestContext;如果没有则返回空。
调用关系:发送成功回复或错误前会调用它,保证一个客户端请求结束后上下文被清理。
调用图:被 2 处调用(send_error, send_response_as)。
OutgoingMessageSender::request_context_count268–270 ↗
async fn request_context_count(&self) -> usize
作用:返回当前保存的请求上下文数量。这个函数只在测试里使用,用来确认清理是否发生。
数据流:无业务输入 → 锁住上下文表并读取长度 → 返回数量,不改变表内容。
调用关系:测试发送回复、断开连接后的清理行为时会调用它;生产代码不会依赖它。
OutgoingMessageSender::send_request272–280 ↗
async fn send_request(
&self,
request: ServerRequestPayload,
) -> (RequestId, oneshot::Receiver<ClientRequestResult>)
作用:向客户端广播一个需要回答的服务器请求。适合不限定连接和线程的普通请求。
数据流:传入请求内容 → 调用 send_request_to_connections,连接范围为空、线程编号为空 → 返回新请求编号和等待结果的接收端。
调用关系:上层想简单发一个请求时用它;所有重活都交给 send_request_to_connections。
调用图:调用 1 个内部函数(send_request_to_connections)。
OutgoingMessageSender::next_request_id282–284 ↗
fn next_request_id(&self) -> RequestId
作用:生成下一个服务器请求编号。每个等待客户端回答的请求都需要一个稳定编号,方便之后把回答对上。
数据流:读取并递增原子计数器 AtomicI64(多任务同时用也不会撞号的整数)→ 把旧值包装成 RequestId::Integer → 返回请求编号。
调用关系:send_request_to_connections 每次发新请求前都会调用它;它不发送消息,只负责编号。
调用图:被 1 处调用(send_request_to_connections);外部调用 2 个(fetch_add, Integer)。
OutgoingMessageSender::send_request_to_connections286–350 ↗
async fn send_request_to_connections(
&self,
connection_ids: Option<&[ConnectionId]>,
request: ServerRequestPayload,
thread_id: Option<ThreadId>,
) -> (RequestId, o
作用:发送一个服务器请求,并登记“客户端回信来了要通知谁”。这是服务器主动问客户端问题的核心函数。
数据流:传入可选连接列表、请求内容和可选线程编号 → 生成请求编号,把编号写进请求 → 创建 oneshot 回信通道并把发送端存入回调表 → 按连接范围广播或逐个发送 → 如果排队失败就移除回调 → 返回请求编号和等待回信的接收端。
调用关系:OutgoingMessageSender::send_request 和 ThreadScopedOutgoingMessageSender::send_request 都会走到这里;客户端之后回复时,notify_client_response 或 notify_client_error 会用同一个编号找到回调。
调用图:调用 2 个内部函数(track_server_request, next_request_id);被 1 处调用(send_request);外部调用 6 个(send, clone, request_with_id, Request, channel, warn!)。
OutgoingMessageSender::replay_requests_to_connection_for_thread352–371 ↗
async fn replay_requests_to_connection_for_thread(
&self,
connection_id: ConnectionId,
thread_id: ThreadId,
)
作用:把某个线程里还没完成的请求重新发给一个连接。比如新连接加入后,需要看到之前还在等待用户回答的问题。
数据流:传入连接编号和线程编号 → 找出该线程的所有待处理请求 → 逐条包装成出站请求发到这个连接 → 发送失败就写警告。
调用关系:连接恢复或新客户端接入某线程时会用它;待处理列表来自 pending_requests_for_thread。
调用图:调用 1 个内部函数(pending_requests_for_thread);外部调用 3 个(send, Request, warn!)。
OutgoingMessageSender::notify_client_response373–393 ↗
async fn notify_client_response(&self, id: RequestId, result: Result)
作用:客户端对服务器请求给出成功回答时,用这个函数唤醒原来等待答案的人。
数据流:传入请求编号和结果 → 从回调表取出对应等待项 → 记录完成时间和必要的分析事件 → 通过 oneshot 把 Ok(result) 发给等待者 → 找不到或发送失败就写警告。
调用关系:传输层收到客户端 response 后会调用它;它和 send_request_to_connections 存下的回调配对使用。
调用图:调用 3 个内部函数(track_server_response, take_request_callback, now_unix_timestamp_ms);外部调用 2 个(matches!, warn!)。
OutgoingMessageSender::notify_client_error395–411 ↗
async fn notify_client_error(&self, id: RequestId, error: JSONRPCErrorError)
作用:客户端对服务器请求返回错误时,用这个函数把错误转交给等待者。
数据流:传入请求编号和 JSON-RPC 错误 → 从回调表取出等待项 → 记录请求被中止的分析事件 → 通过 oneshot 把 Err(error) 发给等待者 → 找不到或发送失败就写警告。
调用关系:传输层收到客户端 error 后会调用它;它结束由 send_request_to_connections 建立的等待关系。
调用图:调用 3 个内部函数(track_server_request_aborted, take_request_callback, now_unix_timestamp_ms);外部调用 2 个(clone, warn!)。
OutgoingMessageSender::cancel_request413–422 ↗
async fn cancel_request(&self, id: &RequestId) -> bool
作用:取消某一个还在等待客户端回答的服务器请求。用于上层发现这个问题已经不需要答案了。
数据流:传入请求编号 → 从回调表移除对应项 → 如果确实存在,就记录中止事件并返回 true;否则返回 false。
调用关系:需要按单个请求撤销等待时会调用它;它不会把错误发给等待者,只表示是否取消成功。
调用图:调用 3 个内部函数(track_server_request_aborted, take_request_callback, now_unix_timestamp_ms)。
OutgoingMessageSender::cancel_all_requests424–443 ↗
async fn cancel_all_requests(&self, error: Option<JSONRPCErrorError>)
作用:取消所有还在等待客户端回答的服务器请求。通常用于整体关闭、重置或严重状态变化。
数据流:传入可选错误 → 锁住回调表并一次性清空 → 对每个被取消的请求记录中止事件 → 如果提供了错误,就把错误发给对应等待者。
调用关系:全局清理流程会用它;它不筛选线程,直接处理所有待回信请求。
调用图:调用 2 个内部函数(track_server_request_aborted, now_unix_timestamp_ms);外部调用 1 个(warn!)。
OutgoingMessageSender::take_request_callback445–451 ↗
async fn take_request_callback(
&self,
id: &RequestId,
) -> Option<(RequestId, PendingCallbackEntry)>
作用:从等待回调表里取出某个请求的回调,并同时删除它。这样同一个客户端回答不会被处理两次。
数据流:传入请求编号 → 锁住回调表 → 删除并返回请求编号和 PendingCallbackEntry;没有则返回空。
调用关系:notify_client_response、notify_client_error 和 cancel_request 都用它完成“一次性取走”。
调用图:被 3 处调用(cancel_request, notify_client_error, notify_client_response)。
OutgoingMessageSender::pending_requests_for_thread453–466 ↗
async fn pending_requests_for_thread(
&self,
thread_id: ThreadId,
) -> Vec<ServerRequest>
作用:列出某个线程里还在等待客户端回答的请求,并按请求编号排序。这样重放给新连接时顺序稳定。
数据流:传入线程编号 → 锁住回调表 → 筛出 thread_id 匹配的请求并克隆出来 → 按 id 排序 → 返回请求列表。
调用关系:replay_requests_to_connection_for_thread 会调用它;测试也用它确认线程内请求顺序正确。
调用图:被 1 处调用(replay_requests_to_connection_for_thread)。
OutgoingMessageSender::cancel_requests_for_thread468–501 ↗
async fn cancel_requests_for_thread(
&self,
thread_id: ThreadId,
error: Option<JSONRPCErrorError>,
)
作用:取消某个线程下所有还没收到客户端回答的请求。这样一个会话状态变化时,不会误用旧回合的答案。
数据流:传入线程编号和可选错误 → 找到并移除该线程的所有回调项 → 为每个请求记录中止事件 → 如果有错误,就把错误发给等待者。
调用关系:ThreadScopedOutgoingMessageSender::abort_pending_server_requests 会用它;它是按线程清理等待请求的核心工具。
调用图:调用 2 个内部函数(track_server_request_aborted, now_unix_timestamp_ms);外部调用 2 个(with_capacity, warn!)。
OutgoingMessageSender::send_response503–508 ↗
async fn send_response(&self, request_id: ConnectionRequestId, response: T)
作用:向客户端发一个成功回复,并接受多种响应类型。调用方不用先手动转成协议里的统一响应格式。
数据流:传入连接请求编号和响应对象 → 把响应转换成 ClientResponsePayload → 调用 send_response_as 继续处理 → 不直接返回结果。
调用关系:send_result 成功分支会调用它;线程范围发送器也会间接使用它。
调用图:调用 1 个内部函数(send_response_as);被 1 处调用(send_result);外部调用 1 个(into)。
OutgoingMessageSender::send_response_as510–551 ↗
async fn send_response_as(
&self,
request_id: ConnectionRequestId,
response: ClientResponsePayload,
)
作用:把成功响应变成 JSON-RPC 回复,并发到发起请求的那个连接。它还负责发送前清理请求上下文。
数据流:传入连接请求编号和统一响应负载 → 尝试序列化成 JSON-RPC 的 id 和 result,并记录分析事件 → 取走请求上下文 → 成功则发送 Response;序列化失败则改发内部错误。
调用关系:OutgoingMessageSender::send_response 会调用它;它把最终发送交给 send_outgoing_message_to_connection,失败包装交给 send_error_inner。
调用图:调用 4 个内部函数(internal_error, send_error_inner, send_outgoing_message_to_connection, take_request_context);被 1 处调用(send_response);外部调用 3 个(into_jsonrpc_parts_and_payload, Response, format!)。
OutgoingMessageSender::send_server_notification553–556 ↗
async fn send_server_notification(&self, notification: ServerNotification)
作用:广播一条服务器通知给客户端。通知不需要对应某个请求,也不等待回复。
数据流:传入通知 → 调用 send_server_notification_to_connections,连接列表传空表示广播 → 由后者排队发送。
调用关系:登录完成、导入进度等全局通知会调用它;它是广播通知的简化入口。
调用图:调用 1 个内部函数(send_server_notification_to_connections);被 3 处调用(send_chatgpt_login_completion_notifications, send_completed_import_notification, send_import_progress)。
OutgoingMessageSender::send_server_notification_to_connections558–593 ↗
async fn send_server_notification_to_connections(
&self,
connection_ids: &[ConnectionId],
notification: ServerNotification,
)
作用:把通知发给指定连接;如果连接列表为空,就广播给所有连接。它是普通通知发送的主要路径。
数据流:传入连接编号列表和通知 → 写 trace 日志 → 包装成 AppServerNotification → 空列表则发 Broadcast;否则逐个发 ToConnection → 每次失败都写警告。
调用关系:全局通知和线程范围通知都会走这里;真正写网络的工作由接收 OutgoingEnvelope 的传输层完成。
调用图:被 1 处调用(send_server_notification);外部调用 6 个(send, clone, is_empty, AppServerNotification, trace!, warn!)。
OutgoingMessageSender::send_server_notification_to_connection_and_wait595–615 ↗
async fn send_server_notification_to_connection_and_wait(
&self,
connection_id: ConnectionId,
notification: ServerNotification,
)
作用:给一个连接发通知,并等到底层确认“已经写完”。适合必须确保消息先送出去再继续的场景。
数据流:传入连接编号和通知 → 创建 oneshot 完成信号 → 把通知和完成信号一起放入出站队列 → 等待写入方发回完成信号 → 结束。
调用关系:需要强同步发送通知的流程会用它;传输层写完后会通过 write_complete_tx 通知这里。
调用图:外部调用 6 个(send, clone, AppServerNotification, channel, trace!, warn!)。
OutgoingMessageSender::send_error617–625 ↗
async fn send_error(
&self,
request_id: ConnectionRequestId,
error: impl Into<JSONRPCErrorError>,
)
作用:对客户端发来的请求发送错误回复,并清理这次请求的上下文。
数据流:传入连接请求编号和可转换成 JSON-RPC 错误的对象 → 取走请求上下文 → 转成标准错误 → 调用 send_error_inner 发出。
调用关系:send_result 的错误分支会调用它;线程范围发送器也会间接使用它。
调用图:调用 2 个内部函数(send_error_inner, take_request_context);被 1 处调用(send_result);外部调用 1 个(into)。
OutgoingMessageSender::send_result627–641 ↗
async fn send_result(
&self,
request_id: ConnectionRequestId,
result: std::result::Result<T, E>,
)
作用:根据一个 Rust Result 自动选择发成功回复还是错误回复。调用方可以少写一层 match。
数据流:传入连接请求编号和 result → 如果是 Ok,就把内容交给 send_response;如果是 Err,就交给 send_error → 最终发出对应 JSON-RPC 消息。
调用关系:处理客户端请求的代码在拿到业务结果后可用它统一收尾;它本身不做序列化,交给成功或错误分支。
调用图:调用 2 个内部函数(send_error, send_response)。
OutgoingMessageSender::send_error_inner643–660 ↗
async fn send_error_inner(
&self,
request_context: Option<RequestContext>,
request_id: ConnectionRequestId,
error: JSONRPCErrorError,
)
作用:把已经准备好的 JSON-RPC 错误包装成出站错误消息并发送。它是错误发送的内部共用步骤。
数据流:传入可选请求上下文、连接请求编号和错误 → 构造 OutgoingMessage::Error → 调用 send_outgoing_message_to_connection 发到对应连接。
调用关系:send_error 会调用它;send_response_as 序列化成功回复失败时,也会调用它改发内部错误。
调用图:调用 1 个内部函数(send_outgoing_message_to_connection);被 2 处调用(send_error, send_response_as);外部调用 1 个(Error)。
OutgoingMessageSender::send_outgoing_message_to_connection662–683 ↗
async fn send_outgoing_message_to_connection(
&self,
request_context: Option<RequestContext>,
connection_id: ConnectionId,
message: OutgoingMessage,
message_kin
作用:把一条已经包装好的出站消息放进指定连接的发送队列。它是回复和错误真正入队的最后一步。
数据流:传入可选请求上下文、连接编号、消息和消息种类文字 → 构造 ToConnection 信封并发送到 mpsc 队列 → 如果有上下文,就在对应 span 下执行发送,方便追踪 → 失败则写警告。
调用关系:send_response_as 和 send_error_inner 都会调用它;后续由传输层从队列取出并写给客户端。
调用图:被 2 处调用(send_error_inner, send_response_as);外部调用 2 个(send, warn!)。
now_unix_timestamp_ms686–693 ↗
fn now_unix_timestamp_ms() -> u64
作用:取得当前 Unix 时间戳,单位是毫秒。Unix 时间戳就是从 1970 年 1 月 1 日开始算起的时间。
数据流:读取系统当前时间 → 减去 UNIX_EPOCH → 转成毫秒整数 → 如果系统时间异常或数值转换失败,就返回默认值 0。
调用关系:记录分析事件时会调用它,比如请求完成、取消、权限批准响应等。
调用图:被 6 处调用(cancel_all_requests, cancel_request, cancel_requests_for_thread, notify_client_error, notify_client_response, track_effective_permissions_approval_response);外部调用 1 个(now)。
tests::verify_server_notification_serialization729–751 ↗
fn verify_server_notification_serialization()
作用:测试服务器通知能被序列化成正确的 JSON-RPC 通知格式。它防止方法名或字段名被改坏。
数据流:构造账号登录完成通知 → 包装成出站通知 → 转成 JSON → 和预期 JSON 比较。
调用关系:这是测试模块的一部分;它验证协议宏和 OutgoingMessage 的序列化配合是否正确。
调用图:外部调用 4 个(AccountLoginCompleted, nil, AppServerNotification, assert_eq!)。
tests::verify_account_login_completed_notification_serialization754–776 ↗
fn verify_account_login_completed_notification_serialization()
作用:专门测试账号登录完成通知的 JSON 形状。客户端依赖这个格式来理解登录结果。
数据流:创建登录成功通知 → 包装成 AppServerNotification → 序列化为 JSON → 检查 method 和 params 是否符合预期。
调用关系:和其他通知序列化测试一起保护客户端协议兼容性。
调用图:外部调用 4 个(AccountLoginCompleted, nil, AppServerNotification, assert_eq!)。
tests::verify_account_rate_limits_notification_serialization779–823 ↗
fn verify_account_rate_limits_notification_serialization()
作用:测试账号速率限制更新通知的序列化。速率限制信息字段较多,容易漏字段或名字写错。
数据流:构造一份包含 primary 限制和套餐类型的通知 → 包装并转 JSON → 与完整预期结构比较。
调用关系:它确保 ServerNotification::AccountRateLimitsUpdated 通过 OutgoingMessage 发出时客户端能正确读取。
调用图:外部调用 3 个(AccountRateLimitsUpdated, AppServerNotification, assert_eq!)。
tests::verify_account_updated_notification_serialization826–845 ↗
fn verify_account_updated_notification_serialization()
作用:测试账号更新通知的 JSON 格式。比如登录方式 authMode 要按协议写成客户端认识的字符串。
数据流:构造账号更新通知 → 包装成出站通知 → 序列化 → 比对预期 JSON。
调用关系:属于通知协议测试,防止账号状态推送格式回归。
调用图:外部调用 3 个(AccountUpdated, AppServerNotification, assert_eq!)。
tests::verify_config_warning_notification_serialization848–869 ↗
fn verify_config_warning_notification_serialization()
作用:测试配置警告通知如何变成 JSON。配置警告要清楚传给客户端,方便用户看到问题。
数据流:构造带 summary 和 details 的配置警告 → 包装并序列化 → 检查 JSON 是否包含正确方法名和参数。
调用关系:它保护 configWarning 这类通知的外部协议格式。
调用图:外部调用 3 个(ConfigWarning, AppServerNotification, assert_eq!)。
tests::verify_guardian_warning_notification_serialization872–891 ↗
fn verify_guardian_warning_notification_serialization()
作用:测试安全守护警告通知的 JSON 格式。客户端要靠 threadId 和 message 展示正确警告。
数据流:创建 GuardianWarning 通知 → 包装成出站通知 → 转 JSON → 与预期字段比较。
调用关系:它和模型、账号、配置等通知测试一起覆盖 AppServerNotification 的序列化。
调用图:外部调用 3 个(GuardianWarning, AppServerNotification, assert_eq!)。
tests::verify_model_rerouted_notification_serialization894–919 ↗
fn verify_model_rerouted_notification_serialization()
作用:测试模型被改路由通知的 JSON 格式。也就是从一个模型切到另一个模型时,客户端收到的说明要正确。
数据流:构造包含线程、回合、原模型、新模型和原因的通知 → 包装并序列化 → 检查 JSON 字段和值。
调用关系:它确保 model/rerouted 事件通过出站消息发出时不破坏客户端协议。
调用图:外部调用 3 个(ModelRerouted, AppServerNotification, assert_eq!)。
tests::verify_model_verification_notification_serialization922–943 ↗
fn verify_model_verification_notification_serialization()
作用:测试模型验证通知的 JSON 格式。验证结果列表要按客户端约定的字符串发出。
数据流:构造带验证项的通知 → 包装成出站通知 → 序列化 → 比对 method、threadId、turnId 和 verifications。
调用关系:属于通知序列化测试,覆盖 ServerNotification::ModelVerification。
调用图:外部调用 4 个(ModelVerification, AppServerNotification, assert_eq!, vec!)。
tests::verify_turn_moderation_metadata_notification_serialization946–968 ↗
fn verify_turn_moderation_metadata_notification_serialization()
作用:测试对话回合审核元数据通知的 JSON 格式。metadata 是灵活 JSON,更需要确认被原样送出。
数据流:构造带 threadId、turnId 和 metadata 的通知 → 包装并序列化 → 和预期 JSON 比较。
调用关系:它确保 turn/moderationMetadata 通知能被客户端按协议解析。
调用图:外部调用 4 个(TurnModerationMetadata, AppServerNotification, assert_eq!, json!)。
tests::server_request_response_from_result_decodes_typed_response971–1010 ↗
fn server_request_response_from_result_decodes_typed_response()
作用:测试客户端返回的普通 JSON 结果能被解码成具体的服务器响应类型。这样等待方拿到的不是一团不明 JSON。
数据流:构造一个命令执行批准请求 → 用包含 decision 的 JSON 模拟客户端结果 → 调用请求自己的解码方法 → 检查得到的响应类型和决定值正确。
调用关系:它验证 ServerRequest 与 ServerResponse 的配套关系;notify_client_response 记录分析事件时也依赖这种解码能力。
调用图:外部调用 4 个(Integer, assert_eq!, json!, panic!)。
tests::send_response_routes_to_target_connection1012–1050 ↗
async fn send_response_routes_to_target_connection()
作用:测试成功回复会发到指定连接,而不是广播或发错连接。
数据流:创建测试用发送队列和 OutgoingMessageSender → 构造连接请求编号 → 调用 send_response → 从队列取出信封 → 检查连接编号、消息类型、响应 id 和结果。
调用关系:它直接覆盖 OutgoingMessageSender::send_response 的路由行为,保证客户端请求的回复只回到原连接。
调用图:调用 2 个内部函数(disabled, new);外部调用 7 个(ThreadArchive, from_secs, Integer, new, assert_eq!, panic!, timeout)。
tests::send_response_clears_registered_request_context1053–1081 ↗
async fn send_response_clears_registered_request_context()
作用:测试发送成功回复后,请求上下文会被清掉。否则追踪记录会一直留在内存里。
数据流:创建发送器和请求编号 → 注册一个 RequestContext → 确认数量为 1 → 发送回复 → 再确认数量变为 0。
调用关系:它验证 register_request_context、send_response 和 take_request_context 的配合。
调用图:调用 3 个内部函数(disabled, new, new);外部调用 5 个(ThreadArchive, Integer, new, assert_eq!, info_span!)。
tests::send_error_routes_to_target_connection1084–1116 ↗
async fn send_error_routes_to_target_connection()
作用:测试错误回复会发到指定连接,并带上原请求编号和错误内容。
数据流:创建发送器和错误对象 → 调用 send_error → 从队列取出消息 → 检查连接编号、错误消息类型、id 和 error 是否正确。
调用关系:它覆盖 OutgoingMessageSender::send_error 到 send_error_inner 的发送路径。
调用图:调用 3 个内部函数(disabled, internal_error, new);外部调用 6 个(from_secs, Integer, new, assert_eq!, panic!, timeout)。
tests::send_server_notification_to_connection_and_wait_tracks_write_completion1119–1161 ↗
async fn send_server_notification_to_connection_and_wait_tracks_write_completion()
作用:测试“发送通知并等待写完”的机制真的会等到底层确认。没有确认时任务不应提前结束。
数据流:启动一个异步任务发送通知并等待 → 测试端从队列取出信封 → 检查带有 write_complete_tx → 手动发送完成信号 → 确认发送任务结束。
调用关系:它验证 send_server_notification_to_connection_and_wait 和传输层写入完成回调之间的约定。
调用图:调用 2 个内部函数(disabled, new);外部调用 8 个(from_secs, ModelRerouted, new, assert!, assert_eq!, panic!, spawn, timeout)。
tests::connection_closed_clears_registered_request_contexts1164–1196 ↗
async fn connection_closed_clears_registered_request_contexts()
作用:测试某个连接断开后,只清掉这个连接的请求上下文,不影响别的连接。
数据流:注册两个不同连接的请求上下文 → 确认数量为 2 → 调用 connection_closed 清理其中一个连接 → 确认剩下 1 个。
调用关系:它覆盖 OutgoingMessageSender::connection_closed,防止断线清理误删或漏删。
调用图:调用 3 个内部函数(disabled, new, new);外部调用 4 个(Integer, new, assert_eq!, info_span!)。
tests::notify_client_error_forwards_error_to_waiter1199–1227 ↗
async fn notify_client_error_forwards_error_to_waiter()
作用:测试客户端返回错误时,原先等待请求结果的人能收到同一个错误。
数据流:发送一个需要客户端批准的请求并拿到等待端 → 构造错误 → 调用 notify_client_error → 等待端收到结果 → 检查结果是 Err(error)。
调用关系:它验证 send_request 保存回调和 notify_client_error 取回回调之间的闭环。
调用图:调用 4 个内部函数(disabled, internal_error, new, new);外部调用 5 个(from_secs, new, ApplyPatchApproval, assert_eq!, timeout)。
tests::pending_requests_for_thread_returns_thread_requests_in_request_id_order1230–1290 ↗
async fn pending_requests_for_thread_returns_thread_requests_in_request_id_order()
作用:测试按线程查询待处理请求时,返回顺序按请求编号排列。稳定顺序能让重放请求更可预测。
数据流:创建线程范围发送器 → 连续发送三种线程请求 → 调用 pending_requests_for_thread → 检查返回的请求 id 顺序与发送顺序一致。
调用关系:它覆盖 ThreadScopedOutgoingMessageSender::send_request 和 OutgoingMessageSender::pending_requests_for_thread 的配合。
调用图:调用 4 个内部函数(disabled, new, new, new);外部调用 7 个(new, DynamicToolCall, FileChangeRequestApproval, ToolRequestUserInput, assert_eq!, json!, vec!)。
tests::cancel_requests_for_thread_cancels_all_thread_requests1293–1351 ↗
async fn cancel_requests_for_thread_cancels_all_thread_requests()
作用:测试取消某个线程的所有待处理请求时,所有等待者都会收到错误,并且待处理列表变空。
数据流:创建线程发送器并发送两个请求 → 构造取消错误 → 调用 cancel_requests_for_thread → 两个等待端都收到 Err(error) → 再检查该线程没有待处理请求。
调用关系:它验证按线程取消请求的完整流程,也间接保护 abort_pending_server_requests 使用的底层能力。
调用图:调用 5 个内部函数(disabled, internal_error, new, new, new);外部调用 9 个(new, from_secs, DynamicToolCall, ToolRequestUserInput, assert!, assert_eq!, json!, timeout, vec!)。
app-server/src/request_processors/initialize_processor.rs源码 ↗
客户端和 app server 建立连接后,不能马上随便发业务请求,得先做一次 initialize,也就是“报到”。这个文件里的 InitializeRequestProcessor 就负责这件事。它会检查这条连接是不是已经初始化过,避免同一个人重复登记;读取客户端名字、版本和能力声明;把这些信息写进这条连接自己的状态里。它还会做一些全局设置,比如把真实客户端名字写进请求头相关的身份信息里,用来让后续访问别的服务时带上正确来源。这里特别小心:像后台守护进程这类名字不会改全局身份,避免内部程序冒充真正用户客户端。初始化成功后,它会记录一次分析事件,设置地域合规要求,生成 user agent(可以理解成“软件身份证”),最后把初始化响应发回去。文件还负责把配置警告通知给客户端,并在初始化后的请求发生时补打一条分析记录。
InitializeRequestProcessor::new28–42 ↗
fn new(
outgoing: Arc<OutgoingMessageSender>,
analytics_events_client: AnalyticsEventsClient,
config: Arc<Config>,
config_warnings: Vec<ConfigWarningNotification>,
作用:创建一个初始化请求处理器,把它后面干活需要的几个工具都装进去。有人要处理 initialize 请求前,会先用它组装好这个处理器。
数据流:进去的是发送消息的通道、分析事件客户端、配置、配置警告列表、RPC 传输类型 → 它把这些保存到 InitializeRequestProcessor 里面,其中配置警告会包进可共享的 Arc(一种安全共享同一份数据的引用)→ 出来的是一个可以反复使用的处理器对象。
调用关系:它是这个处理器的组装入口,被外层的 new 流程调用。后续 initialize、发送通知、记录请求等函数都依赖这里保存下来的发送器、配置和分析客户端。
InitializeRequestProcessor::initialize44–158 ↗
async fn initialize(
&self,
connection_id: ConnectionId,
request_id: RequestId,
params: InitializeParams,
session: &ConnectionSessionState,
// `Some(...
作用:处理客户端发来的初始化请求。它确认连接还没初始化过,保存客户端信息,设置必要的全局身份信息,记录分析事件,并把服务器的初始化响应发回客户端。
数据流:进去的是连接编号、请求编号、客户端提交的初始化参数、当前连接状态,以及一个可选的“是否立刻允许向外发送”的开关 → 它先检查是否重复初始化,再读取客户端能力、名字和版本;验证客户端名字能不能放进 HTTP 头(一种网络请求里的键值信息);把能力和版本写入连接状态;必要时更新全局 originator(请求来源身份)和 user agent 后缀;记录初始化分析事件;设置地域合规要求;最后生成包含 user agent、codex home、操作系统信息的响应并发回去 → 出来的是 Ok(true/false),表示是否已经把连接标记为可向外发送;如果请求不合法,比如重复初始化或客户端名字非法,就返回 JSON-RPC 错误。
调用关系:它由 handle_client_request 在收到客户端 initialize 请求时调用。它会调用连接状态的 initialized 和 initialize 来检查、写入状态;调用 set_default_originator、set_default_client_residency_requirement、get_codex_user_agent 等函数处理全局客户端身份;调用 analytics_events_client.track_initialize 记录分析;最后通过 outgoing.send_response 把结果交回客户端。
调用图:调用 6 个内部函数(track_initialize, initialize, initialized, get_codex_user_agent, set_default_client_residency_requirement, set_default_originator);被 1 处调用(handle_client_request);外部调用 5 个(from_str, new, clone, format!, warn!)。
InitializeRequestProcessor::send_initialize_notifications_to_connection160–172 ↗
async fn send_initialize_notifications_to_connection(
&self,
connection_id: ConnectionId,
)
作用:把初始化阶段产生的配置警告,只发给某一个指定连接。比如某个客户端刚完成初始化,就可以只提醒它当前配置里有哪些问题。
数据流:进去的是一个连接编号 → 它遍历启动时收集到的配置警告,把每条警告包装成服务器通知 → 通过 outgoing 只发送给这个连接;它不返回业务结果,只完成发送动作。
调用关系:它通常在某条连接初始化完成后,由外层同名流程调用。它不自己生成警告,只使用 InitializeRequestProcessor::new 时保存的 config_warnings,并把发送工作交给 outgoing.send_server_notification_to_connections。
调用图:被 1 处调用(send_initialize_notifications_to_connection);外部调用 1 个(ConfigWarning)。
InitializeRequestProcessor::send_initialize_notifications174–180 ↗
async fn send_initialize_notifications(&self)
作用:把初始化相关的配置警告广播给所有连接。适合服务器想让当前所有客户端都知道配置有问题的时候使用。
数据流:进去不需要额外参数 → 它遍历保存好的配置警告,把每条警告变成 ConfigWarning 服务器通知 → 通过 outgoing 广播出去;它不返回业务数据,只负责把消息送出。
调用关系:它由外层同名流程调用,用在需要面向所有连接发送初始化通知的时候。它和 send_initialize_notifications_to_connection 很像,只是后者点对点发送,这个函数是广播发送。
调用图:被 1 处调用(send_initialize_notifications);外部调用 1 个(ConfigWarning)。
InitializeRequestProcessor::track_initialized_request182–190 ↗
fn track_initialized_request(
&self,
connection_id: ConnectionId,
request_id: RequestId,
request: &ClientRequest,
)
作用:在连接已经初始化后,给客户端发来的普通请求记一笔分析日志。这样系统可以知道哪些客户端在什么连接上发了什么类型的请求。
数据流:进去的是连接编号、请求编号和客户端请求内容 → 它把连接编号里的实际数字、请求编号和请求本身交给分析事件客户端 → 输出不是返回值,而是产生一条请求分析记录。
调用关系:它由 dispatch_initialized_client_request 在分发已初始化连接上的请求时调用。它自己不处理请求内容,只负责把“这次请求发生了”交给 analytics_events_client.track_request 记录下来。
调用图:调用 1 个内部函数(track_request);被 1 处调用(dispatch_initialized_client_request)。
app-server/src/transport.rs源码 ↗
服务器同时可能连着很多客户端,每个客户端能力不一样:有的已经初始化,有的允许实验功能,有的明确说“不想收某类通知”。这个文件就保存这些连接的状态,并决定一条外发消息该发给谁、怎么发、要不要改一下再发。它像快递分拣站:先看收件人是否还在,再看包裹里有没有对方不能收的内容,最后投递到对应的发送队列。这里用 AtomicBool(可被多个任务安全读取的小开关)记录能力开关,用 RwLock(一把读写锁,防止同时乱改名单)保存退订通知列表。遇到慢连接,如果队列塞满,就主动断开,避免一个慢客户端拖累所有人。
ConnectionState::new47–59 ↗
fn new(
_origin: ConnectionOrigin,
outbound_initialized: Arc<AtomicBool>,
outbound_experimental_api_enabled: Arc<AtomicBool>,
outbound_opted_out_notification_methods: A
作用:创建一个新连接的入站状态,也就是服务器接收这个连接消息时要记住的上下文。它把外部传进来的几个共享开关和一个新的会话状态绑在一起。
数据流:输入连接来源、是否已初始化的共享开关、是否启用实验接口的共享开关、退订通知名单 → 函数新建一个 ConnectionSessionState(每个连接自己的会话记录)→ 输出一个 ConnectionState,供后续处理这个连接的请求时使用。
调用关系:它是连接建立早期会用到的构造函数。它自己只负责把零件装好,其中会创建新的会话状态;后面的消息处理代码会拿这个状态判断这个连接当前处在哪个会话阶段。
OutboundConnectionState::new71–85 ↗
fn new(
writer: mpsc::Sender<QueuedOutgoingMessage>,
initialized: Arc<AtomicBool>,
experimental_api_enabled: Arc<AtomicBool>,
opted_out_notification_methods: Arc<RwLock
作用:创建一个连接的出站状态,也就是服务器往这个连接发消息时需要知道的东西。它记录发送通道、功能开关、退订名单,以及是否能主动踢掉这个连接。
数据流:输入发送队列 writer、几个共享状态开关、退订通知名单和可选的断开信号 → 函数把它们原样保存到结构里 → 输出一个 OutboundConnectionState,之后发消息时就靠它找到发送口并做过滤判断。
调用关系:它会在连接启动时被使用,也被多组测试用来搭建不同客户端场景,比如慢连接、没有实验能力、退订某些通知等。后续 route_outgoing_envelope 和 send_message_to_connection 会间接依赖这里保存的信息。
调用图:被 10 处调用(start_uninitialized, broadcast_does_not_block_on_slow_connection, command_execution_request_approval_keeps_additional_permissions_with_capability, command_execution_request_approval_strips_additional_permissions_without_capability, experimental_notifications_are_dropped_without_capability, experimental_notifications_are_preserved_with_capability, to_connection_notification_respects_opt_out_filters, to_connection_notifications_are_dropped_for_opted_out_clients, to_connection_notifications_are_preserved_for_non_opted_out_clients, to_connection_stdio_waits_instead_of_disconnecting_when_writer_queue_is_full)。
OutboundConnectionState::can_disconnect87–89 ↗
fn can_disconnect(&self) -> bool
作用:判断这个连接能不能被服务器主动断开。简单说,就是看手里有没有“断开按钮”。
数据流:输入当前连接的出站状态 → 检查 disconnect_sender 是否存在 → 输出 true 或 false;不修改任何数据。
调用关系:它在发送消息时帮助决定策略:如果能主动断开,发送队列满了就可以踢掉慢连接;如果不能主动断开,就只能等待正常发送完成。
OutboundConnectionState::request_disconnect91–95 ↗
fn request_disconnect(&self)
作用:请求断开这个连接。它不会自己关网络,而是按下一个取消令牌,让真正负责连接的任务收到信号后停下来。
数据流:输入当前连接状态 → 如果里面有 CancellationToken(取消令牌,可以通知异步任务停止)就调用 cancel → 结果是相关连接任务会被通知退出;如果没有令牌,就什么也不做。
调用关系:disconnect_connection 会在决定移除某个连接时调用它。它把“我要断开”的决定传给底层连接任务,避免这里直接碰网络细节。
should_skip_notification_for_connection98–121 ↗
fn should_skip_notification_for_connection(
connection_state: &OutboundConnectionState,
message: &OutgoingMessage,
) -> bool
作用:判断某条通知对某个连接来说要不要跳过。它主要处理两件事:实验功能通知不能发给没开实验能力的客户端,客户端退订的通知也不要再发。
数据流:输入一个连接状态和一条外发消息 → 先读取这个连接的退订通知名单;如果读锁失败,就记录警告并默认不跳过 → 如果消息是通知,就检查实验能力和退订名单 → 输出 true 表示别发,false 表示可以继续。
调用关系:send_message_to_connection 在给单个连接发消息前会用它挡掉不该发的通知;route_outgoing_envelope 在广播时也用同样规则先筛一遍目标连接。它遇到读取退订名单失败时会调用 warn! 记录警告。
调用图:被 1 处调用(send_message_to_connection);外部调用 1 个(warn!)。
disconnect_connection123–132 ↗
fn disconnect_connection(
connections: &mut HashMap<ConnectionId, OutboundConnectionState>,
connection_id: ConnectionId,
) -> bool
作用:从当前连接表里移除一个连接,并请求它断开。它是“踢掉这个连接”的统一入口。
数据流:输入所有出站连接的表和一个连接编号 → 如果表里有这个连接,就把它移除,并调用 request_disconnect 发出断开信号 → 输出 true 表示确实断开了一个连接;如果没找到,输出 false。
调用关系:send_message_to_connection 在发现连接发送队列关闭或慢到塞满时会调用它。这样断开逻辑集中在一个地方,避免到处重复写移除和取消的步骤。
调用图:被 1 处调用(send_message_to_connection)。
send_message_to_connection134–172 ↗
async fn send_message_to_connection(
connections: &mut HashMap<ConnectionId, OutboundConnectionState>,
connection_id: ConnectionId,
message: OutgoingMessage,
write_complete_tx: Option<
作用:把一条消息发给指定连接,并处理各种现实问题:连接可能已经没了,消息可能需要删掉实验字段,通知可能被退订,发送队列可能已经满了。
数据流:输入连接表、目标连接编号、要发的消息和可选的写入完成通知 → 先查连接是否还存在,不存在就记录警告并返回 false → 再按连接能力过滤消息内容,并判断通知是否要跳过 → 然后把消息包成 QueuedOutgoingMessage 放进该连接的发送队列 → 如果队列满或关闭,可能会移除并断开连接 → 输出布尔值,表示这次是否触发了断开。
调用关系:route_outgoing_envelope 把具体投递工作交给它。它内部会调用 filter_outgoing_message_for_connection 改写不兼容的消息,调用 should_skip_notification_for_connection 跳过不该发的通知,必要时调用 disconnect_connection 清理坏连接。
调用图:调用 3 个内部函数(disconnect_connection, filter_outgoing_message_for_connection, should_skip_notification_for_connection);被 1 处调用(route_outgoing_envelope);外部调用 1 个(warn!)。
filter_outgoing_message_for_connection174–196 ↗
fn filter_outgoing_message_for_connection(
connection_state: &OutboundConnectionState,
message: OutgoingMessage,
) -> OutgoingMessage
作用:按客户端能力调整外发消息,避免把客户端不支持或不该看到的实验字段发出去。现在它重点处理命令执行审批请求里的实验字段。
数据流:输入连接状态和一条外发消息 → 读取这个连接是否启用了实验接口 → 如果消息是 CommandExecutionRequestApproval,并且对方没启用实验接口,就从参数里删掉实验字段 → 输出可能被修改过的消息;其他消息原样返回。
调用关系:send_message_to_connection 在真正发送前调用它。它负责内容层面的兼容处理,让老客户端或未授权客户端收到的消息仍然安全、能理解。
调用图:被 1 处调用(send_message_to_connection);外部调用 1 个(Request)。
route_outgoing_envelope198–237 ↗
async fn route_outgoing_envelope(
connections: &mut HashMap<ConnectionId, OutboundConnectionState>,
envelope: OutgoingEnvelope,
)
作用:决定一个外发信封该怎么送:是送给某一个连接,还是广播给所有合适的连接。它是外发消息进入传输层后的主要分发入口。
数据流:输入当前连接表和一个 OutgoingEnvelope(外发信封,里面说明发给谁以及发什么)→ 如果是指定连接,就交给 send_message_to_connection → 如果是广播,就先挑出已初始化、且没有退订或被能力限制挡住的连接,再逐个发送消息副本 → 函数不返回业务结果,但可能让消息入队,也可能导致慢连接被断开。
调用关系:start_uninitialized 等上层启动流程会调用它来处理待发送消息。它不直接写网络,而是把单连接投递交给 send_message_to_connection,从而复用过滤、退订和慢连接保护这些规则。
调用图:调用 1 个内部函数(send_message_to_connection);被 1 处调用(start_uninitialized)。
app-server/src/in_process.rs源码 ↗
平时 app-server 可能像一个单独的服务,通过 socket、stdio 这类通道收发消息。这个文件做的是“同进程版本”:服务器和调用方住在同一个程序里,用 Tokio 异步任务和内存通道传消息。可以把它想成把两个办公室之间的快递,换成同一张桌上的收件篮。它仍然保留 JSON-RPC 的请求、响应、通知规则,所以行为和真正的 app-server 通道一致,不会另起一套规矩。启动时,start 会先完成 initialize/initialized 这套握手,确保返回的句柄已经能用。调用方用 InProcessClientHandle 发请求、发通知、收服务器事件,最后 shutdown 关掉后台任务。它还特别处理“队列满了”的情况:普通通知可能被丢弃,但需要回应的服务器请求不会悄悄消失,而是会回一个错误,避免审批之类的流程永远卡住。
server_notification_requires_delivery104–111 ↗
fn server_notification_requires_delivery(notification: &ServerNotification) -> bool
作用:判断某个服务器通知是不是必须送到客户端,不能因为队列忙就随便丢掉。比如一次对话结束、线程设置更新、外部代理配置导入完成,这些通知会影响界面或流程收尾。
数据流:进去的是一个服务器通知 → 函数检查它属于哪一种通知 → 出来一个布尔值:true 表示必须保证送达,false 表示在压力太大时可以丢弃。
调用关系:运行时在处理 OutgoingMessage::AppServerNotification 时会用它做分流:重要通知走会等待的发送方式,普通通知走不等待的 try_send,队列满了就记录警告并丢弃。
调用图:外部调用 1 个(matches!)。
InProcessClientSender::request204–216 ↗
async fn request(&self, request: ClientRequest) -> IoResult<PendingClientRequestResponse>
作用:把一个客户端请求送进同进程 app-server,并等待对应的回应。它适合用在“我问服务器一件事,并且需要结果”的场景。
数据流:进去的是一个 ClientRequest → 它创建一个一次性回信通道(oneshot,一次只能传一个结果的小信封),把请求和回信地址一起塞进客户端队列 → 出来的是传输层结果:要么拿到 JSON-RPC 成功结果或错误结果,要么发现通道断了。
调用关系:InProcessClientHandle::request 会调用它。它不直接处理请求,而是把消息交给 try_send_client_message,后面的运行时任务再交给 MessageProcessor 真正执行。
调用图:调用 1 个内部函数(try_send_client_message);被 1 处调用(request);外部调用 2 个(new, channel)。
InProcessClientSender::notify218–220 ↗
fn notify(&self, notification: ClientNotification) -> IoResult<()>
作用:发送一个客户端通知。通知和请求不同,它只是“告诉服务器一声”,不等业务结果。
数据流:进去的是一个 ClientNotification → 它包装成内部消息并尝试放入客户端队列 → 出来只表示有没有成功放进队列;不会返回应用层处理结果。
调用关系:InProcessClientHandle::notify 会调用它。它继续交给 try_send_client_message 统一处理队列满了或运行时已关闭的情况。
调用图:调用 1 个内部函数(try_send_client_message);被 1 处调用(notify)。
InProcessClientSender::respond_to_server_request222–227 ↗
fn respond_to_server_request(&self, request_id: RequestId, result: Result) -> IoResult<()>
作用:当服务器反过来向客户端提问时,用这个函数把成功答案回给服务器。常见场景是服务器发起审批、确认等需要客户端表态的流程。
数据流:进去的是服务器请求的 request_id 和一个成功结果 → 它包装成 ServerRequestResponse 内部消息 → 出来是是否成功放入运行时队列;真正通知服务器的动作稍后由运行时完成。
调用关系:InProcessClientHandle::respond_to_server_request 会调用它。运行时收到这个内部消息后,会通过 OutgoingMessageSender 把结果交还给正在等答复的服务器逻辑。
调用图:调用 1 个内部函数(try_send_client_message);被 1 处调用(respond_to_server_request)。
InProcessClientSender::fail_server_request229–238 ↗
fn fail_server_request(
&self,
request_id: RequestId,
error: JSONRPCErrorError,
) -> IoResult<()>
作用:当客户端无法满足服务器请求时,用这个函数把失败原因回给服务器。这样服务器不会一直等着,流程也不会卡死。
数据流:进去的是服务器请求的 request_id 和一个 JSON-RPC 错误对象 → 它包装成 ServerRequestError 内部消息 → 出来是队列投递是否成功;具体失败会由运行时转交给服务器请求等待方。
调用关系:InProcessClientHandle::fail_server_request 会调用它。它和 respond_to_server_request 是一对:一个表示答应并给结果,一个表示拒绝并给错误。
调用图:调用 1 个内部函数(try_send_client_message);被 1 处调用(fail_server_request)。
InProcessClientSender::try_send_client_message240–252 ↗
fn try_send_client_message(&self, message: InProcessClientMessage) -> IoResult<()>
作用:这是所有客户端发消息的统一入口,负责把内部消息放进有容量限制的队列,并把队列问题翻译成普通 IO 错误。
数据流:进去的是一个 InProcessClientMessage → 它调用 try_send 尝试立即入队 → 如果成功就返回 Ok;如果队列满了就返回 WouldBlock;如果运行时已经关了就返回 BrokenPipe。
调用关系:request、notify、respond_to_server_request、fail_server_request 都通过它发送消息。它像门口的保安,统一判断“还能不能进”“是不是已经关门”。
调用图:被 4 处调用(fail_server_request, notify, request, respond_to_server_request);外部调用 2 个(try_send, new)。
InProcessClientHandle::request276–278 ↗
async fn request(&self, request: ClientRequest) -> IoResult<PendingClientRequestResponse>
作用:这是外部调用方最常用的请求接口:向内嵌 app-server 发一个有编号的请求,并等待结果。
数据流:进去的是 ClientRequest → 它转交给内部的 InProcessClientSender::request → 出来的是 IO 包装过的 JSON-RPC 结果,可能是成功值,也可能是服务器返回的错误。
调用关系:更高层代码会拿着 InProcessClientHandle 调它,例如删除线程这类操作。它本身只是门面,真正排队发送由 InProcessClientSender::request 完成。
调用图:调用 1 个内部函数(request);被 1 处调用(delete_thread)。
InProcessClientHandle::notify284–286 ↗
fn notify(&self, notification: ClientNotification) -> IoResult<()>
作用:向内嵌 app-server 发一个不需要回应的通知。适合“状态已经发生了,你知道一下”的消息。
数据流:进去的是 ClientNotification → 它转交给内部 sender → 出来只表示这条通知有没有成功进入队列。
调用关系:start 在初始化成功后会用它发送 Initialized 通知。其他调用方也可以用它发送无需返回值的客户端事件。
调用图:调用 1 个内部函数(notify)。
InProcessClientHandle::respond_to_server_request293–295 ↗
fn respond_to_server_request(&self, request_id: RequestId, result: Result) -> IoResult<()>
作用:给服务器发来的请求回一个成功答案。调用方通常先从 next_event 收到 ServerRequest,再用这个函数回应。
数据流:进去的是服务器请求 ID 和成功结果 → 它交给内部 sender 入队 → 出来是入队是否成功。
调用关系:它是客户端处理服务器反向请求的“答复按钮”。内部会调用 InProcessClientSender::respond_to_server_request,运行时再把结果送回 MessageProcessor 相关等待点。
调用图:调用 1 个内部函数(respond_to_server_request)。
InProcessClientHandle::fail_server_request301–307 ↗
fn fail_server_request(
&self,
request_id: RequestId,
error: JSONRPCErrorError,
) -> IoResult<()>
作用:给服务器发来的请求回一个失败答案。适合客户端不能批准、不能提供数据,或者遇到本地错误的时候。
数据流:进去的是服务器请求 ID 和错误对象 → 它交给内部 sender 入队 → 出来是投递是否成功。
调用关系:它通常和 next_event 搭配使用:先收到 ServerRequest,再决定调用 respond_to_server_request 或 fail_server_request。
调用图:调用 1 个内部函数(fail_server_request)。
InProcessClientHandle::next_event313–315 ↗
async fn next_event(&mut self) -> Option<InProcessServerEvent>
作用:从服务器事件队列里取下一条事件。事件可能是服务器请求、服务器通知,也可能是提示“你读得太慢,有事件丢了”的 Lagged 标记。
数据流:没有业务输入 → 它等待 event_rx 队列的下一项 → 出来是 Some(event),如果运行时结束且队列空了,就返回 None。
调用关系:调用方的事件循环会反复调用它。运行时后台任务负责往 event_rx 里塞 ServerRequest、ServerNotification 或健康提示。
调用图:调用 1 个内部函数(recv)。
InProcessClientHandle::shutdown321–340 ↗
InProcessClientHandle::sender342–344 ↗
fn sender(&self) -> InProcessClientSender
作用:取出一个可以克隆、可以单独保存的发送器。这样别的任务也能向同一个内嵌服务器发请求或通知。
数据流:没有业务输入 → 它克隆内部 InProcessClientSender → 出来一个新的 sender,仍然指向同一个运行时队列。
调用关系:适合多任务场景:主 handle 继续收事件,其他 worker 持有 sender 去发送消息。
调用图:外部调用 1 个(clone)。
start352–372 ↗
async fn start(args: InProcessStartArgs) -> IoResult<InProcessClientHandle>
作用:启动同进程 app-server,并自动完成初始化握手。调用方拿到返回值时,不需要再自己发送 initialize 和 initialized。
数据流:进去的是 InProcessStartArgs,里面包含配置、认证、环境、初始化参数、队列容量等启动材料 → 它先调用 start_uninitialized 建好运行时,再发送 Initialize 请求并检查结果,成功后发送 Initialized 通知 → 出来是可直接使用的 InProcessClientHandle;如果初始化失败,会先关掉运行时再返回 InvalidData 错误。
调用关系:这是外部最主要的启动入口。测试和更高层的 in-process client 都会调用它;它把真正复杂的搭建工作交给 start_uninitialized。
调用图:调用 1 个内部函数(start_uninitialized);被 9 处调用(start, start_test_client_with_capacity, get_conversation_summary_by_thread_id_reads_pathless_store_thread, mcp_resource_read_returns_error_for_unknown_thread, start_in_process_client, thread_list_includes_store_thread_without_rollout_path, thread_read_loaded_include_turns_reads_store_history_without_rollout_path, thread_turns_list_reads_store_history_without_rollout_path, thread_unarchive_preserves_pathless_store_metadata);外部调用 3 个(Integer, new, format!)。
start_uninitialized374–727 ↗
async fn start_uninitialized(args: InProcessStartArgs) -> IoResult<InProcessClientHandle>
作用:搭起同进程运行时的所有后台机器,但不主动做 initialize 握手。它是底层启动函数,start 会在它之上补上握手流程。
数据流:进去的是完整启动参数 → 它创建多条有界内存队列,解析安装 ID,准备认证、分析事件、配置管理器、MessageProcessor、出站消息路由任务和主运行时循环 → 出来是 InProcessClientHandle,里面带发送端、事件接收端和运行时任务句柄;同时后台任务已经开始等消息。
调用关系:start 调用它。它内部把客户端消息送给 MessageProcessor,把 MessageProcessor 的出站消息再转成客户端响应或事件;关闭时还会取消挂起请求、清理线程监听、停止后台任务。
调用图:调用 9 个内部函数(analytics_events_client_from_config, new, internal_error, new, new, new, new, route_outgoing_envelope, shared_from_config);被 1 处调用(start);外部调用 11 个(clone, new, new, new, new, new, new, resolve_installation_id, select!, spawn (+1 more))。
tests::build_test_config747–761 ↗
async fn build_test_config(codex_home: &Path) -> Config
作用:为测试构造一份可用配置。它优先用 ConfigBuilder,失败时再退回默认配置加载方式。
数据流:进去的是临时 codex_home 路径 → 它尝试基于这个目录构建 Config;如果失败,就调用默认加载函数并传入空的命令行覆盖项 → 出来是一份测试用 Config。
调用关系:测试启动客户端前会调用它。它让测试不依赖用户真实配置目录,避免污染本机环境。
调用图:外部调用 4 个(to_path_buf, new, load_default_with_cli_overrides_for_codex_home, default)。
tests::start_test_client_with_capacity763–800 ↗
async fn start_test_client_with_capacity(
session_source: SessionSource,
channel_capacity: usize,
) -> InProcessClientHandle
作用:按指定队列容量启动一个测试用同进程客户端。它把真实启动所需的一大堆参数都填成安全的测试值。
数据流:进去的是会话来源和队列容量 → 它创建临时目录、测试配置、状态数据库、环境管理器和 InitializeParams,再调用 start 启动运行时 → 出来是 InProcessClientHandle,并把临时目录挂在 handle 上防止过早删除。
调用关系:多个测试都会用它。它是测试里的“启动工厂”,专门复用启动样板代码。
调用图:调用 5 个内部函数(start, default, default_for_tests, new, try_init);外部调用 6 个(new, new, new, build_test_config, default, default)。
tests::start_test_client802–804 ↗
async fn start_test_client(session_source: SessionSource) -> InProcessClientHandle
作用:用默认队列容量启动测试客户端。它是 start_test_client_with_capacity 的简化版。
数据流:进去的是会话来源 → 它填入 DEFAULT_IN_PROCESS_CHANNEL_CAPACITY → 出来是一个已经初始化好的测试客户端。
调用关系:普通测试用它,只有需要测试特殊容量时才直接调用 start_test_client_with_capacity。
调用图:外部调用 1 个(start_test_client_with_capacity)。
tests::in_process_start_initializes_and_handles_typed_v2_request807–825 ↗
async fn in_process_start_initializes_and_handles_typed_v2_request()
作用:验证 start 启动后确实已经初始化,并且能处理一个类型化的 v2 请求。
数据流:测试先启动客户端 → 发送 ConfigRequirementsRead 请求 → 检查请求成功、响应是对象,并能反序列化成 ConfigRequirementsReadResponse → 最后关闭运行时。
调用关系:它覆盖 start、InProcessClientHandle::request 和 shutdown 的基本 happy path,确认同进程通道真的能跑通请求响应。
调用图:外部调用 4 个(Integer, start_test_client, assert!, from_value)。
tests::in_process_start_uses_requested_session_source_for_thread_start828–853 ↗
async fn in_process_start_uses_requested_session_source_for_thread_start()
作用:验证启动参数里的会话来源会正确写进新建线程的信息里。比如 CLI 启动就应该标成 Cli,exec 启动就应该标成 Exec。
数据流:测试分别用不同 SessionSource 启动客户端 → 发送 ThreadStart 请求 → 把响应解析成 ThreadStartResponse → 检查返回线程的 source 是否符合预期 → 关闭运行时。
调用关系:它确保 start_uninitialized 传给 MessageProcessor 的 session_source 没有丢失或写错,这会影响后续会话元数据。
调用图:外部调用 5 个(Integer, default, start_test_client, assert_eq!, from_value)。
tests::in_process_start_clamps_zero_channel_capacity856–880 ↗
async fn in_process_start_clamps_zero_channel_capacity()
作用:验证即使调用方把队列容量设成 0,运行时也会自动修正为至少 1,不会创建一个完全不可用的队列。
数据流:测试用 channel_capacity 为 0 启动客户端 → 循环发送 ConfigRequirementsRead,请求如果暂时因为队列满返回 WouldBlock 就让出执行机会再试 → 成功后解析响应 → 最后关闭运行时。
调用关系:它检查 start_uninitialized 里的容量保护逻辑。没有这个保护,传 0 可能让运行时启动后无法正常收发消息。
调用图:外部调用 5 个(Integer, start_test_client_with_capacity, panic!, from_value, yield_now)。
tests::guaranteed_delivery_helpers_cover_terminal_server_notifications883–907 ↗
fn guaranteed_delivery_helpers_cover_terminal_server_notifications()
作用:验证“必须送达通知”的判断函数包含关键的终止类通知。特别是对话完成和外部代理配置导入完成不能被当成普通通知随便丢。
数据流:测试构造 TurnCompleted 和 ExternalAgentConfigImportCompleted 两种通知 → 调用 server_notification_requires_delivery → 断言结果都是 true。
调用关系:它直接保护 server_notification_requires_delivery 的规则,避免以后改代码时漏掉这些会影响流程收尾的重要通知。
调用图:外部调用 1 个(assert!)。
传输监听器与 websocket 策略
这些文件提供具体的 stdio、Unix-socket 和 websocket 监听器,以及保护 websocket 升级的认证策略。
app-server-transport/src/transport/auth.rs源码 ↗
这个文件像 WebSocket 服务门口的保安。启动时,它先把命令行参数整理成认证设置:你可以指定一个 token 文件、一个 token 的 SHA-256 摘要(把 token 算成固定长度指纹,避免直接保存明文),或者指定 JWT(带签名的令牌)用的共享密钥、签发方和受众。随后它把这些设置变成运行时用的 WebsocketAuthPolicy。真正有客户端请求升级为 WebSocket 时,authorize_upgrade 会读取 HTTP 的 Authorization 头,确认里面是 Bearer token,再按当前模式检查:普通 token 会算哈希后做恒定时间比较(避免通过耗时差猜出 token),JWT 会先验签,再检查过期时间、尚未生效时间、签发方和受众。文件还特别检查一种危险情况:监听地址不是本机回环地址却没有认证,这通常意味着外部机器也能访问,风险很高。
WebsocketAuthError::status_code128–130 ↗
fn status_code(&self) -> StatusCode
作用:取出认证失败时应该返回给客户端的 HTTP 状态码。这里通常是 401,意思是“未授权”。
数据流:输入是一个 WebsocketAuthError 错误对象 → 它读取里面保存的 status_code 字段 → 返回这个状态码,不改动任何东西。
调用关系:当 authorize_upgrade 或 JWT 校验失败后,会产生 WebsocketAuthError;上层的 WebSocket 处理代码可以调用它,把失败原因转换成 HTTP 响应。
WebsocketAuthError::message132–134 ↗
fn message(&self) -> &'static str
作用:取出认证失败时给上层看的简短错误说明。它帮助日志或响应知道到底是缺 token、token 错了,还是 JWT 过期了。
数据流:输入是一个 WebsocketAuthError → 它读取内部 message 字段 → 返回一段静态文字,不修改错误对象。
调用关系:它和 WebsocketAuthError::status_code 搭配使用:一个给状态码,一个给文字说明,方便调用方把认证失败讲清楚。
AppServerWebsocketAuthArgs::try_into_settings138–219 ↗
fn try_into_settings(self) -> anyhow::Result<AppServerWebsocketAuthSettings>
作用:把命令行传进来的 WebSocket 认证参数,检查并整理成内部更好用的设置。它会拦住互相冲突、缺少必要项、格式不对的参数。
数据流:输入是一组命令行参数,比如认证模式、token 文件、token 哈希、JWT 密钥文件、issuer 和 audience → 它先判断选择了哪种认证模式,再检查哪些参数能用、哪些不能混用,并把路径转成绝对路径、把十六进制哈希转成 32 字节数据 → 输出 AppServerWebsocketAuthSettings;如果参数搭配不合法,就返回错误。
调用关系:这是启动阶段的第一道整理工序。它会调用 absolute_path_arg 检查路径,调用 sha256_digest_arg 解析 token 指纹;后面的 policy_from_settings 会接着把这些设置变成真正用于请求检查的策略。
调用图:调用 2 个内部函数(absolute_path_arg, sha256_digest_arg);外部调用 1 个(bail!)。
policy_from_settings222–264 ↗
fn policy_from_settings(
settings: &AppServerWebsocketAuthSettings,
) -> io::Result<WebsocketAuthPolicy>
作用:把已经整理好的认证设置,变成运行时真正拿来验人的 WebsocketAuthPolicy。它会读取密钥文件,并把 token 明文变成哈希。
数据流:输入是 AppServerWebsocketAuthSettings → 如果是 token 文件模式,它读文件、去掉首尾空白、计算 SHA-256 指纹;如果直接给了 token 指纹,它直接保存;如果是 JWT 模式,它读取共享密钥文件、检查密钥长度、转换时钟容忍秒数 → 输出 WebsocketAuthPolicy,里面放着请求时要用的认证材料。
调用关系:它在程序启动时被 run_main_with_transport_options 使用,负责把配置落地成策略。测试 capability_token_hash_policy_authorizes_matching_bearer_token 也会调用它,确认生成的策略能正确放行或拒绝连接。
调用图:调用 3 个内部函数(read_trimmed_secret, sha256_digest, validate_signed_bearer_secret);被 2 处调用(capability_token_hash_policy_authorizes_matching_bearer_token, run_main_with_transport_options);外部调用 1 个(try_from)。
is_unauthenticated_non_loopback_listener266–271 ↗
fn is_unauthenticated_non_loopback_listener(
bind_address: SocketAddr,
policy: &WebsocketAuthPolicy,
) -> bool
作用:判断当前监听地址是不是“对外开放但没有认证”。这是一个安全提醒,因为非本机地址可能被别的机器访问。
数据流:输入是监听地址和认证策略 → 它检查 IP 是否不是回环地址,也就是不只是本机访问,再看策略里是否没有认证模式 → 返回 true 或 false,不修改任何状态。
调用关系:start_websocket_acceptor 会用它来发现危险配置:如果服务监听在 0.0.0.0 这类外部可访问地址,却没启用 WebSocket 认证,就需要特别处理或提示。
调用图:被 1 处调用(start_websocket_acceptor);外部调用 1 个(ip)。
authorize_upgrade273–304 ↗
fn authorize_upgrade(
headers: &HeaderMap,
policy: &WebsocketAuthPolicy,
) -> Result<(), WebsocketAuthError>
作用:在客户端准备升级成 WebSocket 连接时,做真正的认证检查。通过就允许继续,失败就返回未授权错误。
数据流:输入是 HTTP 请求头和当前认证策略 → 如果策略没有开启认证,直接通过;否则从 Authorization 头里取 Bearer token;普通 token 模式会算 token 的 SHA-256 并和保存的指纹比较;JWT 模式会把 token 交给 verify_signed_bearer_token 验签和检查声明 → 成功返回空结果,失败返回 WebsocketAuthError。
调用关系:websocket_upgrade_handler 在处理每个 WebSocket 升级请求时会调用它。它自己会调用 bearer_token_from_headers 取 token,必要时调用 sha256_digest、unauthorized 或 verify_signed_bearer_token。
调用图:调用 4 个内部函数(bearer_token_from_headers, sha256_digest, unauthorized, verify_signed_bearer_token);被 2 处调用(capability_token_hash_policy_authorizes_matching_bearer_token, websocket_upgrade_handler);外部调用 1 个(constant_time_eq_32)。
verify_signed_bearer_token306–315 ↗
fn verify_signed_bearer_token(
token: &str,
shared_secret: &[u8],
issuer: Option<&str>,
audience: Option<&str>,
max_clock_skew_seconds: i64,
) -> Result<(), WebsocketAuthError>
作用:检查一个签名版 bearer token,也就是 JWT,是否真的可信、是否还在有效期内、是否符合预期身份范围。
数据流:输入是 JWT 字符串、共享密钥、可选的签发方 issuer、可选的受众 audience、允许的时钟误差秒数 → 它先调用 decode_jwt_claims 验签并解析内容,再调用 validate_jwt_claims 检查时间和身份字段 → 成功返回通过,失败返回未授权错误。
调用关系:authorize_upgrade 在 signed-bearer-token 模式下会把认证工作交给它。多组测试也直接调用它,覆盖篡改 token、合法 token、多受众、alg=none 和缺少过期时间等情况。
调用图:调用 2 个内部函数(decode_jwt_claims, validate_jwt_claims);被 6 处调用(authorize_upgrade, signed_bearer_token_verification_accepts_multiple_audiences, signed_bearer_token_verification_accepts_valid_token, signed_bearer_token_verification_rejects_alg_none_tokens, signed_bearer_token_verification_rejects_missing_exp, signed_bearer_token_verification_rejects_tampering)。
decode_jwt_claims317–327 ↗
fn decode_jwt_claims(token: &str, shared_secret: &[u8]) -> Result<JwtClaims, WebsocketAuthError>
作用:把 JWT token 解开,并确认它确实是用共享密钥按 HS256 算法签出来的。HS256 可以理解为“双方用同一把秘密钥匙盖章”。
数据流:输入是 token 字符串和共享密钥字节 → 它创建 JWT 验证规则,只接受 HS256,并把库自带的一些声明检查关掉,先专心做验签和解析 → 输出 JwtClaims;如果签名或格式不对,就返回“invalid websocket jwt”。
调用关系:它是 verify_signed_bearer_token 的第一步。验签通过后,后续的 validate_jwt_claims 才会检查过期时间、issuer 和 audience。
调用图:被 1 处调用(verify_signed_bearer_token);外部调用 2 个(from_secret, new)。
validate_jwt_claims329–356 ↗
fn validate_jwt_claims(
claims: &JwtClaims,
issuer: Option<&str>,
audience: Option<&str>,
max_clock_skew_seconds: i64,
) -> Result<(), WebsocketAuthError>
作用:检查 JWT 里面的声明是否符合当前要求,比如没过期、还没到生效时间就不能用、签发方和受众要匹配。
数据流:输入是解析出的 JwtClaims、期望的 issuer、期望的 audience、最大时钟误差 → 它读取当前 UTC 时间,比较 exp 过期时间和 nbf 生效时间,再按需比较 iss 和 aud → 全部通过返回成功,否则返回带具体原因的未授权错误。
调用关系:verify_signed_bearer_token 在 decode_jwt_claims 验签成功后调用它。它会把 audience 的细节匹配交给 audience_matches。
调用图:调用 2 个内部函数(audience_matches, unauthorized);被 1 处调用(verify_signed_bearer_token);外部调用 1 个(now_utc)。
audience_matches358–366 ↗
fn audience_matches(audience: Option<&JwtAudienceClaim>, expected_audience: &str) -> bool
作用:判断 JWT 的 audience 字段里是否包含服务期望的受众。audience 可以是一条字符串,也可以是一组字符串。
数据流:输入是 token 里的 audience 和期望的 audience 字符串 → 它分别处理单个值、多个值和缺失三种情况 → 返回是否匹配,不修改数据。
调用关系:validate_jwt_claims 在配置了期望 audience 时调用它,用来避免把发给别的服务的 token 拿来连接这里。
调用图:被 1 处调用(validate_jwt_claims)。
bearer_token_from_headers368–386 ↗
fn bearer_token_from_headers(headers: &HeaderMap) -> Result<&str, WebsocketAuthError>
作用:从 HTTP 请求头里取出 Bearer token。它只接受形如 Authorization: Bearer xxxxxx 的写法。
数据流:输入是 HTTP HeaderMap → 它查找 Authorization 头,确认头内容能转成文字,按空格拆出认证方案和 token,要求方案是 Bearer 且 token 非空 → 输出 token 字符串切片;任何格式问题都会变成未授权错误。
调用关系:authorize_upgrade 需要先靠它拿到客户端提交的凭证,之后才能按 token 模式或 JWT 模式继续验证。
调用图:调用 1 个内部函数(unauthorized);被 1 处调用(authorize_upgrade);外部调用 1 个(get)。
validate_signed_bearer_secret388–399 ↗
fn validate_signed_bearer_secret(path: &Path, shared_secret: &[u8]) -> io::Result<()>
作用:检查 JWT 共享密钥不能太短。密钥太短就像门锁太简单,容易被猜出来。
数据流:输入是密钥文件路径和读出来的密钥字节 → 它检查长度是否至少 32 字节 → 合格返回成功;太短就返回 InvalidInput 类型的输入错误,并在错误里带上文件路径。
调用关系:policy_from_settings 在启动时读取 JWT 密钥后会调用它,确保服务不会带着弱密钥运行。测试 validate_signed_bearer_secret_rejects_short_secret 专门确认短密钥会被拒绝。
调用图:被 2 处调用(policy_from_settings, validate_signed_bearer_secret_rejects_short_secret);外部调用 2 个(new, format!)。
read_trimmed_secret401–419 ↗
fn read_trimmed_secret(path: &std::path::Path) -> io::Result<String>
作用:从文件里读取 token 或共享密钥,并去掉首尾空白。这样文件末尾常见的换行符不会影响认证。
数据流:输入是一个文件路径 → 它把文件内容读成字符串,trim 掉首尾空白,再检查剩下内容不能为空 → 输出整理后的字符串;读文件失败或内容为空都会返回带路径的错误。
调用关系:policy_from_settings 在 token 文件模式和 JWT 共享密钥文件模式下都会调用它,把磁盘上的秘密材料读入内存。
调用图:被 1 处调用(policy_from_settings);外部调用 3 个(new, format!, read_to_string)。
absolute_path_arg421–423 ↗
fn absolute_path_arg(flag_name: &str, path: PathBuf) -> anyhow::Result<AbsolutePathBuf>
作用:确认命令行传入的路径是绝对路径。绝对路径从根目录开始,避免因为当前工作目录不同而读错文件。
数据流:输入是参数名和路径 → 它尝试把普通 PathBuf 转成 AbsolutePathBuf → 成功输出绝对路径对象;失败时返回“这个参数必须是绝对路径”的错误。
调用关系:AppServerWebsocketAuthArgs::try_into_settings 会用它检查 token 文件和共享密钥文件路径,保证后续 policy_from_settings 读的是明确的文件。
调用图:调用 1 个内部函数(try_from);被 1 处调用(try_into_settings)。
sha256_digest_arg425–438 ↗
fn sha256_digest_arg(flag_name: &str, value: &str) -> anyhow::Result<[u8; 32]>
作用:把命令行里写的 SHA-256 十六进制指纹,转换成程序能直接比较的 32 字节数组。
数据流:输入是参数名和一段文本 → 它先去掉首尾空白,要求长度必须是 64 个十六进制字符,再每两个字符转成一个字节 → 输出 32 字节摘要;长度或字符不合法就报错。
调用关系:AppServerWebsocketAuthArgs::try_into_settings 在用户传 --ws-token-sha256 时调用它。它会把每个十六进制字符的解析交给 hex_nibble。
调用图:调用 1 个内部函数(hex_nibble);被 1 处调用(try_into_settings);外部调用 1 个(bail!)。
hex_nibble440–447 ↗
fn hex_nibble(flag_name: &str, byte: u8) -> anyhow::Result<u8>
作用:把一个十六进制字符转换成 0 到 15 的数字。比如 'a' 或 'A' 都会变成 10。
数据流:输入是参数名和一个字节字符 → 它判断字符是否在 0-9、a-f、A-F 范围内 → 合法就输出对应数字;非法就返回“必须是 64 字符十六进制 SHA-256”的错误。
调用关系:sha256_digest_arg 在解析 token 指纹时反复调用它,一次处理半个字节。
调用图:被 1 处调用(sha256_digest_arg);外部调用 1 个(bail!)。
sha256_digest449–453 ↗
fn sha256_digest(input: &[u8]) -> [u8; 32]
作用:计算一段数据的 SHA-256 摘要,也就是固定 32 字节的“指纹”。同样内容会得到同样指纹,不同内容几乎不可能撞上。
数据流:输入是一段字节数据 → 它调用 SHA-256 算法计算摘要,并复制到固定长度数组里 → 输出 32 字节数组,不修改输入。
调用关系:policy_from_settings 用它把 token 文件里的明文 token 变成指纹;authorize_upgrade 用它给客户端提交的 token 算指纹;测试也用它构造预期策略。
调用图:被 3 处调用(authorize_upgrade, policy_from_settings, capability_token_hash_policy_authorizes_matching_bearer_token);外部调用 1 个(digest)。
unauthorized455–460 ↗
fn unauthorized(message: &'static str) -> WebsocketAuthError
作用:快速创建一个“未授权”的 WebsocketAuthError。它统一了认证失败时的状态码和错误格式。
数据流:输入是一段静态错误消息 → 它把 HTTP 401 状态码和这段消息装进 WebsocketAuthError → 输出错误对象。
调用关系:authorize_upgrade、bearer_token_from_headers 和 validate_jwt_claims 在发现各种认证失败时都会调用它,保证上层收到的错误长得一致。
调用图:被 3 处调用(authorize_upgrade, bearer_token_from_headers, validate_jwt_claims)。
tests::signed_token474–482 ↗
fn signed_token(shared_secret: &[u8], claims: serde_json::Value) -> String
作用:测试用的小工具,用共享密钥临时造一个合法签名的 JWT。它让测试不用依赖外部发 token 的系统。
数据流:输入是共享密钥和 JSON 格式的 claims → 它组装 JWT 头和内容,做 URL 安全的 base64 编码,再用 HMAC-SHA256 算签名 → 输出一个完整 token 字符串。
调用关系:多个 signed_bearer_token_verification_* 测试会调用它,生成不同 claims 的 token,再交给 verify_signed_bearer_token 检查。
tests::detects_unauthenticated_non_loopback_listener485–503 ↗
tests::capability_token_args_require_token_file_or_hash506–518 ↗
fn capability_token_args_require_token_file_or_hash()
作用:测试 capability-token 模式下必须提供 token 文件或 token 指纹。只说要用这种模式但不给凭证来源,是不允许的。
数据流:输入是一组只指定 ws_auth 为 CapabilityToken 的参数 → 调用 AppServerWebsocketAuthArgs::try_into_settings → 期望得到错误,并检查错误文字提到 token 文件或 token 哈希。
调用关系:它保护 try_into_settings 的参数校验规则,避免启动时生成一个无法认证任何人的半成品配置。
tests::capability_token_args_accept_token_hash521–540 ↗
fn capability_token_args_accept_token_hash()
作用:测试 capability-token 模式可以接受直接传入的 SHA-256 token 指纹。
数据流:输入是一组带 --ws-token-sha256 的参数,内容是重复的 ab 十六进制文本 → 调用 try_into_settings → 期望输出的设置里保存 32 个 0xab 字节。
调用关系:它间接验证 sha256_digest_arg 和 try_into_settings 的配合,说明用户不用提供 token 明文文件,也能用指纹配置认证。
调用图:外部调用 2 个(default, assert_eq!)。
tests::capability_token_args_reject_multiple_token_sources543–556 ↗
tests::capability_token_args_reject_malformed_token_hash559–571 ↗
tests::capability_token_hash_policy_authorizes_matching_bearer_token574–596 ↗
fn capability_token_hash_policy_authorizes_matching_bearer_token()
作用:测试普通 bearer token 模式下,正确 token 能通过,错误 token 会被拒绝。
数据流:输入是一个保存了 super-secret-token 哈希的设置和两组 Authorization 请求头 → 先用 policy_from_settings 生成策略,再调用 authorize_upgrade 检查正确 token 和错误 token → 正确的期望成功,错误的期望返回 401。
调用关系:它把 sha256_digest、policy_from_settings 和 authorize_upgrade 串起来测,覆盖从配置到请求验证的完整小流程。
调用图:调用 3 个内部函数(authorize_upgrade, policy_from_settings, sha256_digest);外部调用 3 个(new, from_static, assert_eq!)。
tests::signed_bearer_args_require_mode_when_mode_specific_flags_are_set599–610 ↗
fn signed_bearer_args_require_mode_when_mode_specific_flags_are_set()
作用:测试如果用户只传 JWT 相关参数,却没明确选择认证模式,系统会拒绝启动配置。
数据流:输入是一组只有 shared secret 文件、没有 ws_auth 模式的参数 → 调用 try_into_settings → 期望返回提示必须设置 WebSocket auth 模式的错误。
调用关系:它保护 try_into_settings 的模式选择规则,避免用户以为开了认证,其实没有真正启用。
tests::signed_bearer_args_default_clock_skew_and_trim_optional_claims613–636 ↗
fn signed_bearer_args_default_clock_skew_and_trim_optional_claims()
作用:测试 JWT 模式会给时钟误差使用默认值,并会清理 issuer 和 audience 参数的空白。
数据流:输入是 signed-bearer-token 参数,其中 issuer 前后有空格,audience 只有空白 → 调用 try_into_settings → 期望 issuer 变成 issuer,audience 变成 None,时钟误差使用默认 30 秒。
调用关系:它覆盖 try_into_settings 对可选文本参数的整理逻辑,保证命令行里常见的空格不会造成意外配置。
调用图:外部调用 3 个(default, from, assert_eq!)。
tests::signed_bearer_token_verification_rejects_tampering639–657 ↗
fn signed_bearer_token_verification_rejects_tampering()
作用:测试 JWT 被篡改后会验签失败。哪怕只改 token 中间的一点内容,也不应该通过。
数据流:输入是用 signed_token 生成的合法 token,然后手动替换其中一段内容 → 调用 verify_signed_bearer_token → 期望返回 401 未授权错误。
调用关系:它直接验证 verify_signed_bearer_token 和 decode_jwt_claims 的安全核心:签名必须能发现内容被改过。
调用图:调用 1 个内部函数(verify_signed_bearer_token);外部调用 3 个(signed_token, assert_eq!, json!)。
tests::signed_bearer_token_verification_accepts_valid_token660–678 ↗
fn signed_bearer_token_verification_accepts_valid_token()
作用:测试一个签名正确、没过期、issuer 和 audience 都匹配的 JWT 会通过验证。
数据流:输入是共享密钥和包含 exp、iss、aud 的 claims → signed_token 生成 token,再交给 verify_signed_bearer_token,并传入期望 issuer 和 audience → 期望验证成功。
调用关系:它覆盖 signed-bearer-token 正常成功路径,说明 decode_jwt_claims 和 validate_jwt_claims 能配合放行合法请求。
调用图:调用 1 个内部函数(verify_signed_bearer_token);外部调用 2 个(signed_token, json!)。
tests::signed_bearer_token_verification_accepts_multiple_audiences681–698 ↗
fn signed_bearer_token_verification_accepts_multiple_audiences()
作用:测试 JWT 的 audience 是数组时,只要其中一个值匹配,也能通过。
数据流:输入是一个 aud 字段含多个受众的 token,其中包含期望的 audience → 调用 verify_signed_bearer_token → 期望验证成功。
调用关系:它验证 validate_jwt_claims 调用 audience_matches 时,能正确处理 JWT 里常见的多受众写法。
调用图:调用 1 个内部函数(verify_signed_bearer_token);外部调用 2 个(signed_token, json!)。
tests::signed_bearer_token_verification_rejects_alg_none_tokens701–719 ↗
fn signed_bearer_token_verification_rejects_alg_none_tokens()
作用:测试 alg=none 的 JWT 会被拒绝。alg=none 表示没有签名,如果接受它就等于谁都能伪造 token。
数据流:输入是测试手工拼出的无签名 JWT → 调用 verify_signed_bearer_token → 期望返回 401 未授权错误。
调用关系:它验证 decode_jwt_claims 只接受 HS256 这种带共享密钥签名的算法,不会被无签名 token 绕过。
调用图:调用 1 个内部函数(verify_signed_bearer_token);外部调用 4 个(assert_eq!, format!, json!, to_vec)。
tests::signed_bearer_token_verification_rejects_missing_exp722–739 ↗
fn signed_bearer_token_verification_rejects_missing_exp()
作用:测试缺少 exp 过期时间的 JWT 会被拒绝。没有过期时间的 token 风险很高,因为可能永久有效。
数据流:输入是一个只有 issuer、没有 exp 的签名 token → 调用 verify_signed_bearer_token → 期望返回 401 未授权错误。
调用关系:它验证 decode_jwt_claims 对 JwtClaims 的解析要求:exp 是必须字段,缺了就不能通过。
调用图:调用 1 个内部函数(verify_signed_bearer_token);外部调用 3 个(signed_token, assert_eq!, json!)。
tests::validate_signed_bearer_secret_rejects_short_secret742–750 ↗
fn validate_signed_bearer_secret_rejects_short_secret()
作用:测试 JWT 共享密钥太短时会被拒绝。这样可以防止服务用弱密码启动。
数据流:输入是路径 /tmp/secret 和内容 too-short → 调用 validate_signed_bearer_secret → 期望返回 InvalidInput,并检查错误文字提到至少 32 字节。
调用关系:它直接覆盖 policy_from_settings 启动时会用到的密钥强度检查,确保安全底线不会被删掉。
调用图:调用 1 个内部函数(validate_signed_bearer_secret);外部调用 3 个(new, assert!, assert_eq!)。
app-server-transport/src/transport/stdio.rs源码 ↗
这个文件解决的是“没有网络端口时,怎么和服务通信”的问题。很多编辑器、命令行工具会用标准输入输出连接后台服务,就像两个人隔着一根纸条管道:一边塞一行消息,另一边吐一行回复。这里用的是 JSON-RPC,也就是用 JSON 表示“请求、响应、通知”的一种约定。start_stdio_connection 会先登记一条新的 stdio 连接,然后启动两个异步任务:一个专门读 stdin,每读到一行就转交给上层处理;另一个专门等上层发来要输出的消息,再写到 stdout。它还会特别偷看第一条 initialize 请求,从里面取出客户端名字,方便启动阶段知道是谁连上来了。读到文件结束、出错、或转发失败时,它会发出连接关闭事件,避免系统误以为连接还活着。
start_stdio_connection24–101 ↗
async fn start_stdio_connection(
transport_event_tx: mpsc::Sender<TransportEvent>,
stdio_handles: &mut Vec<JoinHandle<()>>,
initialize_client_name_tx: oneshot::Sender<String>,
) -> IoResul
作用:打开并维护一条基于标准输入输出的连接。别人调用它,是为了让当前进程能通过 stdin 接收消息、通过 stdout 发回消息,而不需要网络。
数据流:进去的是一个发送运输事件的通道、一组用来保存后台任务句柄的列表,以及一个只发送一次的通道,用来回报客户端名字。它先生成连接编号,建立一个“待写出消息”的队列,把“连接已打开”告诉上层。之后它启动两个后台任务:读任务从 stdin 一行行读文本,必要时解析 initialize 里的客户端名,再把原始消息交给 forward_incoming_message;写任务从队列里拿要发出的消息,用 serialize_outgoing_message 转成 JSON 字符串,加换行后写到 stdout。出来的结果是成功或 IO 错误;同时它会改动任务列表,往里面放入两个正在运行的任务,并持续向上层发送连接打开、收到消息、连接关闭等事件。
调用关系:它是这个文件的主入口,通常在服务启动 stdio 通信模式时被调用。它自己不理解每种业务请求,而是把读到的文本交给 forward_incoming_message,让后面的处理器去判断;写回客户端时,也把消息交给 serialize_outgoing_message 做成能传输的文本。它会调用 stdio_initialize_client_name 只做一件额外的小事:从初始化请求里提取客户端名字,供启动流程使用。
调用图:调用 1 个内部函数(stdio_initialize_client_name);外部调用 13 个(new, clone, send, take, debug!, error!, info!, stdin, stdout, forward_incoming_message (+3 more))。
stdio_initialize_client_name103–113 ↗
fn stdio_initialize_client_name(line: &str) -> Option<String>
作用:从一行 stdin 消息里尝试找出客户端的名字。它只关心 JSON-RPC 的 initialize 请求,因为这个请求通常会说明“我是哪个客户端”。
数据流:进去的是一行文本。它先尝试把文本解析成 JSON-RPC 消息;如果不是合法 JSON、不是请求、方法名不是 initialize,或者参数格式不对,就返回空。只有当这行确实是 initialize 请求,并且里面有 client_info.name 时,它才返回这个名字字符串。它不改动外部状态,只做识别和提取。
调用关系:它只被 start_stdio_connection 的 stdin 读取任务调用。读任务每收到一行都会让它看一眼;一旦成功拿到客户端名字,就通过一次性通道告诉外面的启动流程,然后不再重复发送。真正的消息处理仍然由 start_stdio_connection 后续交给 forward_incoming_message。
调用图:被 1 处调用(start_stdio_connection)。
app-server-transport/src/transport/unix_socket.rs源码 ↗
可以把这个文件理解成 app-server 的“本机后门门卫”。客户端不是从网络端口进来,而是通过一个本机文件路径上的 Unix socket 进来;Unix socket 是一种只在同一台机器上通信的管道,常用来做本地控制接口。启动时,它先检查 socket 文件路径:目录要安全,旧文件如果是坏掉的残留就删掉,如果已经有活着的服务在用就报错。然后它绑定监听这个路径,并把权限设成只有当前用户能读写,防止别人乱连。运行中,它循环接受新连接,把连接升级成 WebSocket(一种持续双向聊天的协议),再交给通用的 WebSocket 处理代码。关闭时,守卫对象会自动删除 socket 文件,避免下次启动被旧文件卡住。
start_control_socket_acceptor24–44 ↗
async fn start_control_socket_acceptor(
socket_path: AbsolutePathBuf,
transport_event_tx: mpsc::Sender<TransportEvent>,
shutdown_token: CancellationToken,
) -> IoResult<JoinHandle<()>>
作用:启动控制 socket 的监听器。别人调用它,就是让 app-server 开始在指定的本机 socket 路径上等客户端连接。
数据流:输入是 socket 文件路径、发送传输事件的通道、以及关闭信号。它先准备路径,确认没有活的旧服务占用;再绑定 Unix socket;然后设置文件权限;最后启动一个后台任务去循环接收连接。输出是这个后台任务的句柄,如果中间任何一步失败,就返回错误。
调用关系:这是本文件的启动入口。它先把清理路径的活交给 prepare_control_socket_path,再调用底层 bind 创建监听器,随后调用 set_control_socket_permissions 收紧权限,最后用 tokio 的 spawn 启动 run_control_socket_acceptor,让真正的接客循环在后台跑。
调用图:调用 5 个内部函数(prepare_control_socket_path, run_control_socket_acceptor, set_control_socket_permissions, bind, as_path);外部调用 2 个(info!, spawn)。
run_control_socket_acceptor46–91 ↗
async fn run_control_socket_acceptor(
mut listener: UnixListener,
transport_event_tx: mpsc::Sender<TransportEvent>,
shutdown_token: CancellationToken,
socket_guard: ControlSocketFileGu
作用:这是控制 socket 的长期接客循环。它一边等新连接,一边听关闭信号;有客户端连进来时,就为它开一个独立任务处理。
数据流:输入是已经绑定好的 UnixListener、事件发送通道、关闭令牌、以及负责最后清理 socket 文件的守卫。循环里如果收到关闭信号就退出;如果收到连接,就把连接升级成 WebSocket,再拆成读写两半,交给 WebSocket 连接处理函数。遇到临时连接错误会警告后继续,遇到其他接受连接错误会记录并稍等再试。
调用关系:它由 start_control_socket_acceptor 启动,是运行期一直活跃的核心循环。每接到一个连接,它会复制一份事件发送通道,然后在新任务里调用 accept_async 做 WebSocket 升级,升级成功后调用 run_websocket_connection 处理后续消息。循环结束时,ControlSocketFileGuard 会随之被释放并触发清理。
调用图:调用 1 个内部函数(run_websocket_connection);被 1 处调用(start_control_socket_acceptor);外部调用 6 个(clone, info!, select!, spawn, accept_async, warn!)。
prepare_control_socket_path93–132 ↗
async fn prepare_control_socket_path(socket_path: &Path) -> IoResult<()>
作用:在真正监听之前,把 socket 路径整理干净。它的目标是分清三种情况:可以安全使用、已有服务正在使用、或者有旧垃圾文件需要清掉。
数据流:输入是准备使用的 socket 文件路径。它先确保父目录存在并且是私密目录;然后尝试连接这个路径:如果连得上,说明已有服务在用,返回“地址占用”错误;如果文件不存在,就直接通过;如果连接被拒绝,可能是旧 socket 残留,就继续检查。最后它确认路径上的东西确实是过期 socket,才会删除它;如果那里是别的普通文件,就返回错误,避免误删。
调用关系:它被 start_control_socket_acceptor 在绑定监听前调用,是启动前的安全检查员。它会用 UnixStream::connect 探测旧 socket 是否还活着,也会借助 codex_uds 提供的目录准备和过期 socket 判断工具。
调用图:调用 1 个内部函数(connect);被 1 处调用(start_control_socket_acceptor);外部调用 8 个(exists, parent, try_exists, new, is_stale_socket_path, prepare_private_socket_directory, format!, remove_file)。
acquire_app_server_startup_lock138–156 ↗
async fn acquire_app_server_startup_lock(
startup_lock_path: AbsolutePathBuf,
) -> IoResult<AppServerStartupLock>
作用:拿到 app-server 启动锁,防止两个 app-server 同时启动并互相踩踏。启动锁可以理解成一把文件做的门锁,谁先拿到谁先启动。
数据流:输入是锁文件路径。它先确保锁文件所在目录安全存在;然后在阻塞线程里打开或创建锁文件,并对文件加锁。成功后输出 AppServerStartupLock 对象,只要这个对象还活着,锁就会保持;如果打开、加锁或后台任务失败,就返回错误。
调用关系:这个函数不在本文件其他函数中被调用,但通常会在 app-server 启动流程里被更上层代码调用。它使用 spawn_blocking,是因为文件加锁可能阻塞,不能卡住异步运行时的主工作线程。
调用图:调用 1 个内部函数(as_path);外部调用 2 个(prepare_private_socket_directory, spawn_blocking)。
set_control_socket_permissions170–172 ↗
async fn set_control_socket_permissions(_socket_path: &Path) -> IoResult<()>
作用:把控制 socket 文件的权限调安全。它确保这个本机控制入口不是随便哪个系统用户都能访问。
数据流:输入是 socket 文件路径。在 Unix 系统上,它把权限设置成 0600,也就是只有文件所有者能读写;在非 Unix 系统上,这个函数什么都不做并直接成功。输出是成功或失败的 IO 结果。
调用关系:它由 start_control_socket_acceptor 在 socket 绑定成功后调用。这样监听文件一出现,就马上被收紧权限,减少其他用户趁机连接控制接口的风险。
调用图:被 1 处调用(start_control_socket_acceptor);外部调用 2 个(from_mode, set_permissions)。
ControlSocketFileGuard::drop179–189 ↗
fn drop(&mut self)
作用:在监听器结束时自动删除 socket 文件。它像一个“离开时顺手关灯并清桌面”的守卫,避免下次启动看到旧 socket 文件而误判。
数据流:输入是守卫对象里保存的 socket 路径;这个函数没有普通返回值,因为它是在对象销毁时自动运行。它尝试删除这个路径上的 socket 文件;如果文件已经不存在,就当作没事;如果删除失败且不是“找不到文件”,就写一条警告日志。
调用关系:ControlSocketFileGuard 被 start_control_socket_acceptor 创建,并传进 run_control_socket_acceptor。只要接收循环还在跑,守卫就活着;循环退出或任务结束时,守卫被丢弃,drop 自动执行清理。
调用图:调用 1 个内部函数(as_path);外部调用 2 个(remove_file, warn!)。
app-server-transport/src/transport/websocket.rs源码 ↗
可以把这个文件想成一个“WebSocket 前台接待处”。启动时,它先确认地址和鉴权设置是否安全:如果监听的不是本机地址,却没有开认证,就直接拒绝启动,避免把服务裸露到网络上。然后它开一个 HTTP/WebSocket 服务,提供 /readyz 和 /healthz 这种健康检查入口,其他请求则尝试升级成 WebSocket 连接。连接建立后,它给每个客户端分配一个连接编号,并拆成两个小工:一个专门读客户端发来的消息,一个专门把服务器要发的消息写回客户端。读消息时,它只接受文本消息,把它交给上层传输系统;收到 ping 会回 pong,像是在回答“我还活着”。写消息时,它把内部消息转成 JSON 文本再发出去。文件里还用一个小 trait(统一接口)抹平了 axum 和 tungstenite 两种 WebSocket 消息类型的差异,让同一套读写逻辑可以复用。
colorize51–54 ↗
fn colorize(text: &str, style: Style) -> String
作用:给一段文字加上终端颜色样式,主要用来让启动提示更醒目。如果当前终端不支持颜色,它也能退回普通文字。
数据流:输入是一段文字和一种样式 → 它检查标准错误输出是否支持颜色,并按需套上样式 → 输出一个可以直接打印的字符串,不改动外部状态。
调用关系:它是启动横幅里的小工具,只被 print_websocket_startup_banner 使用,用来把标题、地址和标签染成不同颜色。
调用图:被 1 处调用(print_websocket_startup_banner)。
print_websocket_startup_banner57–77 ↗
fn print_websocket_startup_banner(addr: SocketAddr)
作用:在服务启动时打印一段人能看懂的提示,告诉用户 WebSocket 地址、健康检查地址,以及当前监听是否只限本机。
数据流:输入是实际监听的网络地址 → 它拼出 WebSocket、readyz、healthz 的 URL,并用 colorize 做美化 → 输出到标准错误流,也就是终端提示;没有返回值。
调用关系:start_websocket_acceptor 成功绑定端口后会调用它。它不参与网络收发,只负责把“服务已经在哪儿开门”这件事告诉操作者。
调用图:调用 1 个内部函数(colorize);被 1 处调用(start_websocket_acceptor);外部调用 4 个(ip, new, eprintln!, format!)。
health_check_handler85–87 ↗
async fn health_check_handler() -> StatusCode
作用:给健康检查接口用,简单回答“我还在”。外部监控或脚本可以访问它来判断服务是否活着。
数据流:没有输入业务数据 → 它不做额外检查,直接生成 HTTP 200 OK 状态码 → 返回给访问 /readyz 或 /healthz 的请求方。
调用关系:start_websocket_acceptor 在路由里把 /readyz 和 /healthz 接到它。它是最轻量的请求处理函数,只用于探活。
reject_requests_with_origin_header89–103 ↗
async fn reject_requests_with_origin_header(
request: Request<Body>,
next: Next,
) -> Result<Response, StatusCode>
作用:拒绝带有 Origin 头的请求,减少网页跨站误连或恶意网页尝试连接这个 WebSocket 服务的风险。Origin 可以理解为浏览器告诉服务器“这个请求来自哪个网页”。
数据流:输入是一条 HTTP 请求和后续处理器 → 它查看请求头里有没有 Origin → 如果有,就记录警告并返回 403 禁止访问;如果没有,就把请求交给后面的路由继续处理。
调用关系:start_websocket_acceptor 把它作为中间件(一道经过所有请求的关卡)装到路由上。请求必须先过它,才可能到 health_check_handler 或 websocket_upgrade_handler。
websocket_upgrade_handler105–127 ↗
async fn websocket_upgrade_handler(
websocket: WebSocketUpgrade,
ConnectInfo(peer_addr): ConnectInfo<SocketAddr>,
State(state): State<WebSocketListenerState>,
headers: HeaderMap,
) ->
作用:处理真正的 WebSocket 连接请求:先检查认证是否通过,通过后才把普通 HTTP 请求升级成长连接。
数据流:输入包括升级对象、客户端地址、共享状态和请求头 → 它用请求头和认证策略做授权检查 → 失败时返回错误状态和原因;成功时记录连接日志,并在升级完成后启动 run_websocket_connection 来处理这条连接。
调用关系:start_websocket_acceptor 把所有非健康检查的请求兜底交给它。它是从“有人敲门”到“正式建立 WebSocket 会话”的入口,之后把长期读写工作交给 run_websocket_connection。
调用图:调用 1 个内部函数(authorize_upgrade);外部调用 3 个(on_upgrade, info!, warn!)。
start_websocket_acceptor129–170 ↗
async fn start_websocket_acceptor(
bind_address: SocketAddr,
transport_event_tx: mpsc::Sender<TransportEvent>,
shutdown_token: CancellationToken,
auth_policy: WebsocketAuthPolicy,
) ->
作用:启动 WebSocket 监听器,也就是让服务开始在某个地址和端口上等客户端连接。它还负责启动前的安全把关和优雅关闭。
数据流:输入是绑定地址、向上层汇报事件的通道、关闭信号和认证策略 → 它先拒绝不安全的无认证公网监听,再绑定 TCP 端口,打印启动信息,搭好路由和中间件 → 输出一个后台任务句柄;如果绑定失败或配置危险,就返回错误。
调用关系:这是本文件的启动总开关。它会调用 is_unauthenticated_non_loopback_listener 做安全判断,调用 print_websocket_startup_banner 显示地址,并把请求分发给 health_check_handler、reject_requests_with_origin_header 和 websocket_upgrade_handler。
调用图:调用 2 个内部函数(is_unauthenticated_non_loopback_listener, print_websocket_startup_banner);外部调用 13 个(new, cancelled, new, bind, any, get, serve, new, error!, format! (+3 more))。
run_websocket_connection172–229 ↗
async fn run_websocket_connection(
websocket_writer: impl futures::sink::Sink<M, Error = SinkError> + Send + 'static,
websocket_reader: impl futures::stream::Stream<Item = Result<M, StreamErro
作用:管理一条已经建立好的 WebSocket 连接的整个生命周期。它给连接编号,通知上层“有人连上了”,并同时启动读消息和写消息两个循环。
数据流:输入是 WebSocket 写端、读端,以及给上层发送传输事件的通道 → 它分配 connection_id,创建发消息队列和断开信号,发送 ConnectionOpened 事件 → 然后并行运行入站和出站循环;任一边结束,就取消另一边,最后发送 ConnectionClosed 事件。
调用关系:websocket_upgrade_handler 在升级成功后会把连接交给它;调用图里也显示它会被 run_control_socket_acceptor 使用。它内部把实际读写分别交给 run_websocket_inbound_loop 和 run_websocket_outbound_loop,并用 CancellationToken(取消令牌,一种通知任务该停下的信号)协调关闭。
调用图:调用 2 个内部函数(run_websocket_inbound_loop, run_websocket_outbound_loop);被 1 处调用(run_control_socket_acceptor);外部调用 6 个(new, clone, send, next_connection_id, select!, spawn)。
AxumWebSocketMessage::text249–251 ↗
fn text(text: String) -> Self
作用:把一段普通文本包装成 axum 使用的 WebSocket 文本帧。服务器往 axum 连接发 JSON 文本时会用它。
数据流:输入是 String 文本 → 它把文本放进 axum 的 Text 消息类型里 → 输出一个可以发到 WebSocket 写端的 AxumWebSocketMessage。
调用关系:run_websocket_outbound_loop 通过统一的 AppServerWebSocketMessage 接口调用它,这样出站逻辑不用关心底层消息来自 axum 还是 tungstenite。
调用图:外部调用 1 个(Text)。
AxumWebSocketMessage::pong253–255 ↗
fn pong(payload: Bytes) -> Self
作用:把收到的 ping 载荷包装成 axum 的 pong 回复。ping/pong 可以理解成连接双方互相确认“还在线”的小纸条。
数据流:输入是 ping 携带的字节内容 → 它原样放进 Pong 消息 → 输出一个 axum WebSocket pong 帧。
调用关系:run_websocket_inbound_loop 收到 ping 后,会通过 AppServerWebSocketMessage 接口调用它生成 pong,再交给出站循环发送。
调用图:外部调用 1 个(Pong)。
AxumWebSocketMessage::into_incoming257–265 ↗
fn into_incoming(self) -> Option<IncomingWebSocketMessage>
作用:把 axum 的具体 WebSocket 消息翻译成本文件内部更小、更统一的消息分类,方便后面的逻辑判断。
数据流:输入是一个 axum WebSocket 消息 → 它按类型分成文本、二进制、ping、pong、关闭等几类 → 输出 IncomingWebSocketMessage;这里 axum 的各种消息都会被映射出来。
调用关系:run_websocket_inbound_loop 读取到 axum 消息后会调用它。这样入站循环只需要处理统一分类,不用写一堆专门针对 axum 的判断。
调用图:外部调用 2 个(Ping, Text)。
TungsteniteWebSocketMessage::text269–271 ↗
fn text(text: String) -> Self
作用:把一段普通文本包装成 tungstenite 使用的 WebSocket 文本帧。这样同一套发送逻辑也能用于 tungstenite 连接。
数据流:输入是 String 文本 → 它生成 tungstenite 的 Text 消息 → 输出一个 TungsteniteWebSocketMessage。
调用关系:run_websocket_outbound_loop 通过 AppServerWebSocketMessage 接口间接使用它,让出站循环可以同时兼容 axum 和 tungstenite 两种 WebSocket 实现。
调用图:外部调用 1 个(Text)。
TungsteniteWebSocketMessage::pong273–275 ↗
fn pong(payload: Bytes) -> Self
作用:为 tungstenite 连接生成 pong 回复,用来回应客户端的 ping。
数据流:输入是 ping 的字节载荷 → 它构造一个 tungstenite Pong 消息 → 输出给写端发送的 WebSocket 消息。
调用关系:run_websocket_inbound_loop 收到 ping 后通过统一接口调用它。它和 AxumWebSocketMessage::pong 做同一件事,只是适配不同底层库。
调用图:外部调用 1 个(Pong)。
TungsteniteWebSocketMessage::into_incoming277–286 ↗
fn into_incoming(self) -> Option<IncomingWebSocketMessage>
作用:把 tungstenite 的 WebSocket 消息翻译成本文件内部统一的消息分类,并忽略底层库内部用的 Frame 类型。
数据流:输入是一个 tungstenite 消息 → 它把文本、二进制、ping、pong、关闭映射成 IncomingWebSocketMessage;如果是 Frame 这种本文件不需要直接处理的底层帧,就返回 None → 输出统一分类或空结果。
调用关系:run_websocket_inbound_loop 用它来简化接收逻辑。这样读消息循环不用了解 tungstenite 的全部细节,只处理 app-server 真正关心的几种消息。
调用图:外部调用 2 个(Ping, Text)。
run_websocket_outbound_loop289–328 ↗
async fn run_websocket_outbound_loop(
websocket_writer: impl futures::sink::Sink<M, Error = SinkError> + Send + 'static,
mut writer_rx: mpsc::Receiver<QueuedOutgoingMessage>,
mut writer_co
作用:专门负责把服务器内部要发给客户端的消息写到 WebSocket 上,也负责发送 pong 这类控制消息。
数据流:输入是 WebSocket 写端、普通出站消息队列、控制消息队列和断开信号 → 它不断等待三类事情:被要求断开、收到控制消息、收到普通业务消息 → 控制消息直接发;业务消息先用 serialize_outgoing_message 转成 JSON 文本再发;发送完成后如果有人等确认,就发一个完成通知。
调用关系:run_websocket_connection 为每条连接启动它。它和 run_websocket_inbound_loop 并排工作:入站循环收到 ping 时会把 pong 放进控制队列,由它真正写出去;上层业务要回消息时,也通过连接打开时给出的 writer 通道送到它这里。
调用图:被 1 处调用(run_websocket_connection);外部调用 2 个(pin!, select!)。
run_websocket_inbound_loop330–388 ↗
async fn run_websocket_inbound_loop(
websocket_reader: impl futures::stream::Stream<Item = Result<M, StreamError>> + Send + 'static,
transport_event_tx: mpsc::Sender<TransportEvent>,
write
作用:专门负责读取客户端发来的 WebSocket 消息,并决定这些消息该转给上层、回应、忽略还是关闭连接。
数据流:输入是 WebSocket 读端、向上层发送事件的通道、给当前连接写回消息的通道、控制消息通道、连接编号和断开信号 → 它循环读取消息:文本消息交给 forward_incoming_message;ping 变成 pong 放进控制队列;pong 忽略;close 或错误会结束循环;二进制消息会被丢弃并记录警告 → 结束时让外层连接管理逻辑收尾。
调用关系:run_websocket_connection 为每条连接启动它。它是客户端输入进入系统的第一站,把真正的文本内容交给 forward_incoming_message,把要立即回复的 pong 交给 run_websocket_outbound_loop。
调用图:被 1 处调用(run_websocket_connection);外部调用 2 个(pin!, select!)。
远程控制传输子系统
这些文件实现远程控制期望状态、注册持久化和 HTTP 配对、多路复用客户端跟踪,以及可重连的 websocket 循环。
app-server-transport/src/transport/remote_control/mod.rs源码 ↗
远程控制让这台 app-server 可以通过远端服务器被别的客户端连接和操作。这个文件不直接写网络细节,而是像前台经理:先检查公司政策是否允许、数据库是否可用、用户登录信息是否有效;再启动后台 WebSocket(一条长期在线的网络连接);还提供启用、关闭、配对、查看和吊销客户端这些入口。它特别小心“同一时间只有一个人改关键状态”,用信号通道和信号量(可以理解成排队牌)避免配对和连接同时抢着改同一份登记信息。它还会把连接状态发布出去,让界面或其他模块知道现在是已禁用、连接中还是已连接。
take_remote_control_disabled_env89–95 ↗
fn take_remote_control_disabled_env() -> bool
作用:读取一个内部环境变量,判断启动时是否要临时关掉远程控制。读完马上删掉,避免后面的工作线程再误读这个一次性标记。
数据流:进去的是进程环境变量里的值 → 它查看 CODEX_INTERNAL_APP_SERVER_REMOTE_CONTROL_DISABLED 是否等于 1,然后把这个变量从环境里移除 → 出来一个 true 或 false,表示是否禁用;同时环境变量被清理掉。
调用关系:它通常在程序刚启动、还没创建线程时被调用。它只和系统环境变量打交道,不把工作交给本文件其他函数。
调用图:外部调用 2 个(remove_var, var_os)。
RemoteControlEnrollmentState::new136–141 ↗
fn new(enrollment: Option<RemoteControlEnrollment>) -> Self
作用:创建一份“当前远程控制登记信息”的共享容器。登记信息就像这台服务器在远端系统里的会员卡,后面配对和连接都要用。
数据流:进去的是可有可无的一份登记信息 → 它把信息放进互斥锁(一把锁,防止两个任务同时改同一份数据)里,并配一个信号量(排队许可) → 出来一个可安全共享的状态对象。
调用关系:start_remote_control 会在启动远程控制时创建它,测试和客户端管理辅助也会创建它。之后 RemoteControlEnrollmentState::lock 会用它来安全读写当前登记。
调用图:被 5 处调用(start_remote_control, client_management_handle, plain_start_resolves_persisted_remote_control_preference, remote_control_handle_with_current_enrollment, test_current_enrollment);外部调用 2 个(new, new)。
RemoteControlEnrollmentState::lock143–153 ↗
async fn lock(&self) -> RemoteControlEnrollmentLease<'_>
作用:拿到当前登记信息的独占使用权。它保证配对流程和 WebSocket 连接流程不会同时修改同一张“会员卡”。
数据流:进去的是这个共享状态对象 → 它先等待拿到信号量许可,再复制一份当前登记快照 → 出来一个 RemoteControlEnrollmentLease;持有它期间可以安全查看和修改登记。
调用关系:配对和连接相关流程会先调用它再操作登记。它内部调用 RemoteControlEnrollmentState::snapshot 取当前值;等 lease 被丢弃时,RemoteControlEnrollmentLease::drop 会把改动写回。
调用图:调用 1 个内部函数(snapshot);外部调用 2 个(acquire, unreachable!)。
RemoteControlEnrollmentState::snapshot155–160 ↗
RemoteControlEnrollmentLease::deref172–174 ↗
fn deref(&self) -> &Self::Target
作用:让 lease 可以像普通的登记信息一样被读取。对调用者来说,拿到 lease 后不用关心外面包了多少层保护壳。
数据流:进去的是一个登记 lease → 它返回内部那份可选登记信息的只读引用 → 不产生新数据,也不改状态。
调用关系:这是 Rust 的 Deref 机制,编译器会在需要读取时自动用到它。它配合 RemoteControlEnrollmentState::lock 返回的 lease 使用。
RemoteControlEnrollmentLease::deref_mut178–180 ↗
fn deref_mut(&mut self) -> &mut Self::Target
作用:让 lease 可以像普通登记信息一样被修改。它把“安全持有期间才能改”的规则藏在类型里。
数据流:进去的是一个可变 lease → 它返回内部登记信息的可变引用 → 调用者可以改这份临时副本;真正写回发生在 lease 结束时。
调用关系:这是 Rust 的 DerefMut 机制,配对和刷新登记时会间接用到。最后由 RemoteControlEnrollmentLease::drop 把修改同步回共享状态。
RemoteControlEnrollmentLease::drop184–190 ↗
fn drop(&mut self)
作用:在 lease 用完时自动把修改后的登记信息写回共享状态。它像借书还书:借出去期间可批注,还回来时更新馆藏。
数据流:进去的是即将被释放的 lease → 它拿内部互斥锁,把 lease 里当前的登记值放回共享状态 → 没有返回值,但共享登记被更新。
调用关系:这是 Rust 的 Drop 机制,lease 离开作用域时自动触发。它完成 RemoteControlEnrollmentState::lock 开始的那次安全修改流程。
RemoteControlDisabledByRequirements::fmt211–213 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
作用:把“远程控制被管理要求禁用”的错误变成人能看懂的文字。这里的管理要求可以理解成组织策略或安全规则。
数据流:进去的是格式化器 → 它写入固定错误消息 → 出来的是格式化结果。
调用关系:RemoteControlHandle::ensure_remote_control_allowed 发现策略不允许时会产生这个错误,后续显示错误时会用到它。
调用图:外部调用 1 个(write!)。
RemoteControlEnableError::fmt225–230 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
作用:把启用远程控制失败的具体原因转成文字。它会区分是数据库不可用,还是策略不允许。
数据流:进去的是 RemoteControlEnableError 和格式化器 → 它根据错误种类转交给对应的小错误对象格式化 → 出来一段可读错误文字。
调用关系:RemoteControlHandle::enable_with_preference 返回启用失败时,外部打印或记录错误会用到这个函数。
RemoteControlHandle::ensure_remote_control_allowed236–241 ↗
fn ensure_remote_control_allowed(&self) -> Result<(), RemoteControlDisabledByRequirements>
作用:检查当前政策是否允许远程控制。它是很多入口的第一道门禁。
数据流:进去的是 RemoteControlHandle 里的 policy → 如果是 Allowed 就返回成功;如果是 DisabledByRequirements 就返回禁用错误 → 不改任何状态。
调用关系:enable_with_preference 和 ensure_remote_control_allowed_io 会调用它。后者再把它包装成普通输入输出错误,供异步接口使用。
调用图:被 2 处调用(enable_with_preference, ensure_remote_control_allowed_io)。
RemoteControlHandle::ensure_remote_control_allowed_io243–246 ↗
fn ensure_remote_control_allowed_io(&self) -> io::Result<()>
作用:做同样的政策检查,但把错误换成 io::Error。io::Error 是 Rust 里很多异步接口统一使用的错误格式。
数据流:进去的是 handle → 它调用 RemoteControlHandle::ensure_remote_control_allowed → 出来成功,或一个 PermissionDenied 类型的错误。
调用关系:disable、start_pairing、pairing_status、list_clients、revoke_client 都先调用它,确保被管理策略禁用时不会继续做后续动作。
调用图:调用 1 个内部函数(ensure_remote_control_allowed);被 5 处调用(disable, list_clients, pairing_status, revoke_client, start_pairing)。
RemoteControlHandle::enable_ephemeral248–252 ↗
fn enable_ephemeral(
&self,
) -> Result<RemoteControlStatusChangedNotification, RemoteControlEnableError>
作用:临时启用远程控制,但不主动把“以后也启用”的偏好写入数据库。适合本次运行内打开。
数据流:进去的是 handle → 它调用 enable_with_preference,并传入 None 表示没有持久化偏好 → 出来当前或更新后的远程控制状态,或启用失败原因。
调用关系:这是给外部调用的简洁入口,真正的启用逻辑交给 RemoteControlHandle::enable_with_preference。
调用图:调用 1 个内部函数(enable_with_preference)。
RemoteControlHandle::enable_with_preference254–305 ↗
fn enable_with_preference(
&self,
persistence_preference: Option<bool>,
) -> Result<RemoteControlStatusChangedNotification, RemoteControlEnableError>
作用:执行启用远程控制的核心步骤。它会检查策略和数据库,设置“期望状态”为启用,并把状态推到“连接中”。
数据流:进去的是是否持久化的偏好 → 它先检查政策,再确认状态数据库存在;然后更新 desired_state 通道里的目标状态,并读取当前状态 → 如果已经连接或正在连接就直接返回,否则发布 Connecting 状态并返回。
调用关系:RemoteControlHandle::enable_ephemeral 调用它。它内部调用 ensure_remote_control_allowed、status 和 publish_status;真正连服务器的后台任务会监听 desired_state 的变化。
调用图:调用 3 个内部函数(ensure_remote_control_allowed, publish_status, status);被 1 处调用(enable_ephemeral);外部调用 4 个(Unavailable, info!, matches!, warn!)。
RemoteControlHandle::disable307–324 ↗
async fn disable(
&self,
app_server_client_name: Option<&str>,
) -> io::Result<RemoteControlStatusChangedNotification>
作用:正式关闭远程控制,并把“关闭”这个偏好保存下来。这样下次启动也能记住用户选择。
数据流:进去的是可选的 app-server 客户端名字 → 它先检查政策,拿到两个排队锁,写数据库偏好为 false → 最后把当前状态切到 Disabled 并返回通知。
调用关系:外部请求关闭远程控制时调用它。它调用 ensure_remote_control_allowed_io、persist_preference、acquire_persistence_lock 和 transition_disabled。
调用图:调用 4 个内部函数(ensure_remote_control_allowed_io, persist_preference, transition_disabled, acquire_persistence_lock)。
RemoteControlHandle::disable_ephemeral326–334 ↗
async fn disable_ephemeral(&self) -> RemoteControlStatusChangedNotification
作用:临时关闭远程控制,但不保存到数据库。适合只想本次运行暂停远程控制的场景。
数据流:进去的是 handle → 它拿到排队锁和持久化锁,但不写数据库 → 调用 transition_disabled,把目标状态和公开状态都切到禁用。
调用关系:外部或内部需要临时禁用时使用它。它和 disable 共用 transition_disabled,但少了 persist_preference。
调用图:调用 2 个内部函数(transition_disabled, acquire_persistence_lock)。
RemoteControlHandle::transition_disabled336–352 ↗
fn transition_disabled(&self) -> RemoteControlStatusChangedNotification
作用:把远程控制的目标状态和公开连接状态都切成“已禁用”。这是关闭流程的共同收尾动作。
数据流:进去的是 handle → 它把 desired_state 改成 Disabled,读取当前状态用于日志 → 发布 Disabled 状态并返回最新通知。
调用关系:RemoteControlHandle::disable 和 RemoteControlHandle::disable_ephemeral 都调用它。它内部调用 status 和 publish_status。
调用图:调用 2 个内部函数(publish_status, status);被 2 处调用(disable, disable_ephemeral);外部调用 1 个(info!)。
RemoteControlHandle::persist_preference354–376 ↗
async fn persist_preference(
&self,
app_server_client_name: Option<&str>,
remote_control_enabled: bool,
) -> io::Result<()>
作用:把用户对远程控制开关的选择写进状态数据库。没有它,重启后就可能忘记用户上次想开还是想关。
数据流:进去的是客户端名字和 true/false 开关值 → 它读取数据库句柄、登录账号、规范化后的远程控制地址,并确定持久化用的客户端键 → 把“某账号在某远程控制目标下是否启用”写入数据库。
调用关系:RemoteControlHandle::disable 调用它来保存关闭偏好。它会调用 load_remote_control_auth、normalize_remote_control_url 和 pairing_persistence_key。
调用图:调用 3 个内部函数(pairing_persistence_key, load_remote_control_auth, normalize_remote_control_url);被 1 处调用(disable)。
RemoteControlHandle::status378–380 ↗
fn status(&self) -> RemoteControlStatusChangedNotification
作用:读取当前远程控制状态快照。调用者可以知道现在是禁用、连接中、已连接,以及服务器名和环境 ID 等信息。
数据流:进去的是 handle → 它从 watch 通道里借出最新通知并克隆一份 → 出来一个 RemoteControlStatusChangedNotification,不改状态。
调用关系:enable_with_preference、start_pairing、pairing_status、transition_disabled 和 publish_status 都会读取它,用来判断或返回当前状态。
调用图:被 5 处调用(enable_with_preference, pairing_status, publish_status, start_pairing, transition_disabled)。
RemoteControlHandle::status_receiver382–384 ↗
fn status_receiver(&self) -> watch::Receiver<RemoteControlStatusChangedNotification>
作用:给调用者一个状态订阅器。以后远程控制状态变化时,订阅者可以收到通知。
数据流:进去的是 handle → 它从 status_tx 创建一个新的 watch 接收端 → 出来一个 receiver,后续可持续接收最新状态。
调用关系:界面层或其他模块会用它监听状态变化。状态变化本身主要由 publish_status 和 WebSocket 状态发布器推动。
RemoteControlHandle::start_pairing386–502 ↗
async fn start_pairing(
&self,
params: RemoteControlPairingStartParams,
app_server_client_name: Option<&str>,
) -> io::Result<RemoteControlPairingStartResponse>
作用:开始一次远程控制客户端配对。配对就是让一个新客户端获得许可,能安全连接这台 app-server。
数据流:进去的是配对参数和可选客户端名字 → 它检查远程控制已允许且已启用,加载登录信息,准备或创建服务器登记,必要时刷新令牌;然后向远端发起配对请求 → 出来配对开始响应,或配对不可用、令牌失效等错误;过程中可能更新当前登记和数据库。
调用关系:外部收到“开始配对”请求时调用它。它会调用 load_or_enroll_pairing_server、refresh_pairing_enrollment、clear_pairing_server_token、pairing_persistence_key、load_remote_control_auth 等函数来完成整条配对链路。
调用图:调用 8 个内部函数(ensure_remote_control_allowed_io, load_or_enroll_pairing_server, pairing_persistence_key, status, load_remote_control_auth, clear_pairing_server_token, pairing_unavailable_error, refresh_pairing_enrollment);外部调用 2 个(lock, pairing_disabled_error)。
RemoteControlHandle::load_or_enroll_pairing_server504–552 ↗
async fn load_or_enroll_pairing_server(
&self,
current_enrollment: &mut Option<RemoteControlEnrollment>,
auth: &mut auth::RemoteControlConnectionAuth,
installation_id:
作用:为配对准备一份可用的服务器登记。能复用就复用,不能复用就注册一份新的,并按需要保存。
数据流:进去的是当前登记、登录信息、安装 ID、服务器名、客户端名字和选择策略 → 它先调用 load_or_enroll_server 取得登记;如果是新建的,就在持久化锁保护下把登记写进数据库 → 出来可用于配对的登记,并更新当前登记缓存。
调用关系:start_pairing 和 pairing_status 都依赖它保证有正确登记。它把底层查找或注册交给 load_or_enroll_server,把保存交给 update_persisted_remote_control_enrollment。
调用图:调用 4 个内部函数(load_or_enroll_server, acquire_persistence_lock, update_persisted_remote_control_enrollment, publish_current_enrollment);被 2 处调用(pairing_status, start_pairing);外部调用 1 个(pairing_disabled_error)。
RemoteControlHandle::load_or_enroll_server554–602 ↗
async fn load_or_enroll_server(
&self,
current_enrollment: &Option<RemoteControlEnrollment>,
auth: &mut auth::RemoteControlConnectionAuth,
installation_id: &str,
作用:查找已有远程控制登记,找不到时向远端服务器注册新的登记。它决定“用旧会员卡”还是“办新会员卡”。
数据流:进去的是当前登记、账号认证、安装 ID、服务器名、客户端名字和复用/替换策略 → 它规范化远程控制地址;在复用模式下先看内存,再查数据库;仍没有就调用远端注册 → 出来登记对象,以及一个布尔值说明是不是刚创建的。
调用关系:load_or_enroll_pairing_server 调用它。它把数据库读取交给 load_persisted_remote_control_enrollment,把远端注册交给 enroll_pairing_server。
调用图:调用 3 个内部函数(load_persisted_remote_control_enrollment, enroll_pairing_server, normalize_remote_control_url);被 1 处调用(load_or_enroll_pairing_server)。
RemoteControlHandle::pairing_persistence_key604–616 ↗
fn pairing_persistence_key(
&self,
app_server_client_name: Option<&str>,
) -> io::Result<Option<String>>
作用:确定配对相关记录保存时使用的客户端名字。它避免不同 app-server 客户端把同一份持久化记录混在一起。
数据流:进去的是可选客户端名字 → 如果当前要求必须有持久化键但还没有保存,它会用传入名字填进去;如果缺名字则报配对不可用 → 出来当前保存用的名字,可能是 None。
调用关系:persist_preference 和 start_pairing 会调用它。它维护的键也会被 WebSocket 任务共享使用。
调用图:被 2 处调用(persist_preference, start_pairing);外部调用 2 个(borrow, send_replace)。
RemoteControlHandle::pairing_status618–716 ↗
async fn pairing_status(
&self,
params: RemoteControlPairingStatusParams,
) -> io::Result<RemoteControlPairingStatusResponse>
作用:查询一次配对是否完成、失败或仍在等待。它用于“开始配对”之后持续轮询结果。
数据流:进去的是配对状态查询参数 → 它检查允许和已启用,加载认证,找到当前登记,必要时刷新服务器令牌,再把 pairing_code 或 manual_pairing_code 转成远端请求格式 → 出来配对状态响应;如果登记失效,可能重新登记并返回配对暂不可用。
调用关系:外部查询配对进度时调用它。它调用 remote_control_pairing_status_code、refresh_pairing_enrollment、clear_pairing_server_token、load_or_enroll_pairing_server 和 load_remote_control_auth。
调用图:调用 8 个内部函数(ensure_remote_control_allowed_io, load_or_enroll_pairing_server, status, load_remote_control_auth, clear_pairing_server_token, pairing_unavailable_error, refresh_pairing_enrollment, remote_control_pairing_status_code);外部调用 3 个(lock, borrow, pairing_disabled_error)。
RemoteControlHandle::list_clients718–725 ↗
async fn list_clients(
&self,
params: RemoteControlClientsListParams,
) -> io::Result<RemoteControlClientsListResponse>
作用:列出已经配对或授权的远程控制客户端。用户可以用它查看谁有权限连接。
数据流:进去的是查询参数 → 它先检查政策允许,再把远程控制地址、认证管理器和参数交给 clients 模块 → 出来客户端列表响应。
调用关系:外部客户端管理请求会调用它。它把真正的网络请求交给 clients::list_remote_control_clients。
调用图:调用 2 个内部函数(ensure_remote_control_allowed_io, list_remote_control_clients)。
RemoteControlHandle::revoke_client727–734 ↗
async fn revoke_client(
&self,
params: RemoteControlClientsRevokeParams,
) -> io::Result<RemoteControlClientsRevokeResponse>
作用:吊销某个远程控制客户端的权限。被吊销后,该客户端不应再能继续远程控制。
数据流:进去的是吊销参数 → 它先检查政策允许,再把远程控制地址、认证管理器和参数交给 clients 模块 → 出来吊销结果响应。
调用关系:外部发起“移除设备/客户端”时调用它。它把具体请求交给 clients::revoke_remote_control_client。
调用图:调用 2 个内部函数(ensure_remote_control_allowed_io, revoke_remote_control_client)。
RemoteControlHandle::pairing_disabled_error736–741 ↗
fn pairing_disabled_error() -> io::Error
作用:生成一个统一错误:远程控制没启用,所以不能配对。这样各处返回的错误信息一致。
数据流:没有业务输入 → 它创建一个 InvalidInput 类型的 io::Error,消息说明配对要求远程控制已启用 → 出来这个错误对象。
调用关系:start_pairing、load_or_enroll_pairing_server 和 pairing_status 在发现未启用时会使用它。
调用图:外部调用 1 个(new)。
RemoteControlHandle::publish_status743–771 ↗
fn publish_status(
&self,
connection_status: RemoteControlConnectionStatus,
) -> RemoteControlStatusChangedNotification
作用:发布新的连接状态,比如从禁用变成连接中。它只在状态真的变化时通知订阅者并写日志。
数据流:进去的是新的连接状态 → 它基于旧通知生成新通知,若内容不同就写入 status 通道 → 出来最新状态通知;订阅者也会收到变化。
调用关系:enable_with_preference 和 transition_disabled 调用它。它内部用 remote_control_status_with_connection_status 组合状态,并用 status 返回最终快照。
调用图:调用 1 个内部函数(status);被 2 处调用(enable_with_preference, transition_disabled);外部调用 1 个(info!)。
enroll_pairing_server774–798 ↗
async fn enroll_pairing_server(
auth_manager: &Arc<AuthManager>,
auth: &mut auth::RemoteControlConnectionAuth,
remote_control_target: &protocol::RemoteControlTarget,
installation_id: &
作用:向远程控制服务器登记这台 app-server,拿到配对所需的服务器登记信息。若认证过期,它会尝试恢复登录后再试一次。
数据流:进去的是认证管理器、当前认证信息、远程控制目标、安装 ID 和服务器名 → 它调用远端登记接口;如果收到 PermissionDenied,就启动认证恢复并重新加载认证 → 出来新的 RemoteControlEnrollment,或登记失败错误。
调用关系:load_or_enroll_server 找不到可复用登记时调用它。它把远端请求交给 enroll_remote_control_server,把认证恢复交给 recover_remote_control_auth。
调用图:调用 3 个内部函数(load_remote_control_auth, recover_remote_control_auth, enroll_remote_control_server);被 1 处调用(load_or_enroll_server)。
remote_control_pairing_status_code800–819 ↗
fn remote_control_pairing_status_code(
params: &RemoteControlPairingStatusParams,
) -> io::Result<RemoteControlPairingStatusCode>
作用:检查配对状态查询参数是否合法,并把它变成内部统一格式。它确保用户不能同时传两种配对码,也不能一个都不传。
数据流:进去的是 RemoteControlPairingStatusParams → 如果只有 pairing_code,就生成 PairingCode;如果只有 manual_pairing_code,就生成 ManualPairingCode;如果两个都有或都没有,就返回 InvalidInput 错误 → 出来合法的状态码对象或错误。
调用关系:pairing_status 在向远端查询前调用它,避免把不清楚的参数发给服务器。
调用图:被 1 处调用(pairing_status);外部调用 3 个(ManualPairingCode, PairingCode, new)。
refresh_pairing_enrollment821–850 ↗
async fn refresh_pairing_enrollment(
current_enrollment: &mut Option<RemoteControlEnrollment>,
auth_manager: &Arc<AuthManager>,
auth: &mut auth::RemoteControlConnectionAuth,
installati
作用:刷新配对登记里的服务器令牌。令牌可以理解成临时通行证,过期或无效时需要换新的。
数据流:进去的是当前登记、认证管理器、认证信息、安装 ID 和要刷新的登记 → 它调用刷新接口;若权限被拒绝,就尝试恢复认证、重新加载认证并再刷一次;最后确认当前登记仍是同一份记录 → 成功则更新当前登记,失败则返回配对不可用或原始错误。
调用关系:start_pairing 和 pairing_status 在发现令牌该刷新或远端拒绝时调用它。它内部调用 refresh_remote_control_server、recover_remote_control_auth、load_remote_control_auth 和 replace_current_enrollment。
调用图:调用 5 个内部函数(load_remote_control_auth, recover_remote_control_auth, refresh_remote_control_server, pairing_unavailable_error, replace_current_enrollment);被 2 处调用(pairing_status, start_pairing)。
clear_pairing_server_token852–862 ↗
fn clear_pairing_server_token(
current_enrollment: &mut Option<RemoteControlEnrollment>,
enrollment: &mut RemoteControlEnrollment,
) -> io::Result<()>
作用:清掉当前配对登记里的服务器令牌。通常用于服务器说这个令牌不被接受时,避免继续拿坏令牌重试。
数据流:进去的是当前登记和要修改的登记 → 它把登记里的 server token 清空,再确认这份登记仍然是当前选中的那一份 → 成功则更新当前登记;不匹配则返回配对不可用。
调用关系:start_pairing 和 pairing_status 在收到 PermissionDenied 时调用它。它依赖 replace_current_enrollment 保证没有误改别的登记。
调用图:调用 3 个内部函数(clear_server_token, pairing_unavailable_error, replace_current_enrollment);被 2 处调用(pairing_status, start_pairing)。
remote_control_status_with_connection_status871–885 ↗
fn remote_control_status_with_connection_status(
status: &RemoteControlStatusChangedNotification,
connection_status: RemoteControlConnectionStatus,
) -> RemoteControlStatusChangedNotification
作用:基于旧状态生成一个只改变连接状态的新通知。它保留服务器名和安装 ID,并在禁用时清掉环境 ID。
数据流:进去的是旧状态通知和新的连接状态 → 它复制服务器名、安装 ID,设置新 status;如果新状态是 Disabled,就把 environment_id 设为 None,否则保留原环境 ID → 出来一份新的状态通知。
调用关系:publish_status 用它来构造要发布的新状态。这个函数本身不发通知,只负责拼装数据。
publish_current_enrollment887–892 ↗
fn publish_current_enrollment(
current_enrollment: &mut Option<RemoteControlEnrollment>,
enrollment: &RemoteControlEnrollment,
)
作用:把某份登记设为当前正在使用的登记。它是一个很小但关键的同步动作。
数据流:进去的是当前登记槽位和新的登记 → 它克隆新登记并放进槽位 → 没有返回值,但当前登记被更新。
调用关系:load_or_enroll_pairing_server 在成功拿到登记后调用它,让后续配对状态查询和 WebSocket 连接看到同一份登记。
调用图:被 1 处调用(load_or_enroll_pairing_server);外部调用 1 个(clone)。
replace_current_enrollment894–906 ↗
fn replace_current_enrollment(
current_enrollment: &mut Option<RemoteControlEnrollment>,
enrollment: &RemoteControlEnrollment,
) -> bool
作用:只有在确认还是同一份登记时,才替换当前登记。这样可以防止一个慢请求把另一个新请求的登记覆盖掉。
数据流:进去的是当前登记槽位和候选登记 → 它比较账号、服务器 ID、环境 ID 是否一致;一致就克隆候选登记写回,不一致就拒绝 → 出来 true 表示替换成功,false 表示没有替换。
调用关系:refresh_pairing_enrollment 和 clear_pairing_server_token 调用它,确保刷新令牌或清令牌不会误伤已切换的登记。
调用图:被 2 处调用(clear_pairing_server_token, refresh_pairing_enrollment);外部调用 1 个(clone)。
same_remote_control_enrollment908–917 ↗
fn same_remote_control_enrollment(
left: &RemoteControlEnrollment,
right: &RemoteControlEnrollment,
) -> bool
作用:判断两份远程控制登记是不是同一个服务器记录。刷新令牌会变,但账号、服务器 ID 和环境 ID 应该不变。
数据流:进去的是左右两份登记 → 它比较 account_id、server_id、environment_id → 出来 true 或 false,不改任何数据。
调用关系:replace_current_enrollment 用它判断能不能安全替换当前登记。
start_remote_control919–1071 ↗
async fn start_remote_control(
config: RemoteControlStartConfig,
state_db: Option<Arc<StateRuntime>>,
auth_manager: Arc<AuthManager>,
transport_event_tx: mpsc::Sender<TransportEvent>,
作用:启动整个远程控制后台任务,并返回一个可供外部操作的 handle。它是这个文件最重要的装配函数。
数据流:进去的是启动配置、状态数据库、认证管理器、事件发送通道、关闭令牌、客户端名字接收器和启动模式 → 它计算初始目标状态,创建状态通道、锁、登记状态和状态发布器,然后启动一个 WebSocket 后台任务 → 出来后台任务句柄和 RemoteControlHandle;后台任务会持续连接远端、接收事件,并响应关闭令牌。
调用关系:程序启动远程控制子系统时调用它。它创建 RemoteControlEnrollmentState,调用 normalize_remote_control_url,并把实际长连接工作交给 RemoteControlWebsocket::new(...).run(...);同时把控制入口封装成 RemoteControlHandle 返回给其他模块。
调用图:调用 4 个内部函数(new, normalize_remote_control_url, new, new);外部调用 11 个(new, clone, new, error!, gethostname, info!, AssertUnwindSafe, resume_unwind, spawn, warn! (+1 more))。
app-server-transport/src/transport/remote_control/desired_state.rs源码 ↗
这个文件像远程控制功能的“开关登记员”。它先定义了三种想要的状态:还不知道、关闭、开启;开启时还会记住这个选择是不是要长期保存。程序启动时可能还没登录、也没查数据库,所以先是“未知”。等需要用时,它会查本地保存的登记记录,把状态变成开或关。用户要求开启时,它会检查是否允许远程控制,加载账号信息,规范化远程控制服务器地址,必要时完成登记,然后把“已开启”写进本地数据库。这里还用信号量(一把一次只允许一个任务通过的锁)防止两个开启/保存动作同时改同一份状态,避免前后打架。最后它会发布新的登记和状态,让其他部分知道远程控制已经可用了。
RemoteControlDesiredState::is_enabled29–31 ↗
fn is_enabled(self) -> bool
作用:这个小函数回答一个很直接的问题:当前期望状态是不是“开启”。别人不用关心里面还有什么保存偏好,只要知道开没开。
数据流:进去的是一个远程控制期望状态 → 它检查这个状态是不是 Enabled 这一类 → 出来一个 true 或 false,不改动任何东西。
调用关系:它是状态判断的小工具。比如在已经解析过持久化偏好后,RemoteControlHandle::resolve_persisted_preference 会用它把内部状态转换成外面容易理解的布尔结果。
调用图:外部调用 1 个(matches!)。
acquire_persistence_lock34–36 ↗
async fn acquire_persistence_lock(lock: &Semaphore) -> SemaphorePermit<'_>
作用:这个函数负责拿到“保存远程控制设置”这把锁,保证同一时间只有一个任务在改持久化数据。这样可以避免两个请求同时写数据库,造成状态互相覆盖。
数据流:进去的是一个 Semaphore(信号量,可以理解成限流用的锁)→ 它等待直到拿到许可 → 出来一个 SemaphorePermit(持有锁的凭证),这个凭证活着时其他竞争者就得等;如果锁被意外关闭,代码认为这是不该发生的情况。
调用关系:它被多个会改远程控制保存状态的流程使用,包括 disable、disable_ephemeral、load_or_enroll_pairing_server、enable、enroll_and_persist_remote_control_server、resolve_desired_state_after_account_change。RemoteControlHandle::enable 在真正写入“已开启”之前会先通过它排队。
调用图:被 6 处调用(disable, disable_ephemeral, load_or_enroll_pairing_server, enable, enroll_and_persist_remote_control_server, resolve_desired_state_after_account_change);外部调用 1 个(acquire)。
desired_state_from_persisted_enrollment38–48 ↗
fn desired_state_from_persisted_enrollment(
enrollment: Option<RemoteControlEnrollmentRecord>,
) -> RemoteControlDesiredState
作用:这个函数把数据库里保存的远程控制登记记录,翻译成程序内部好用的“期望状态”。外面不用直接理解数据库字段,只看开或关。
数据流:进去的是一个可能存在、也可能不存在的 RemoteControlEnrollmentRecord(远程控制登记记录)→ 它查看其中 remote_control_enabled 是否明确为 true → 如果是 true,就输出 Enabled,并标记这是长期保存的开启偏好;否则输出 Disabled。
调用关系:它是“数据库记录”和“运行时状态”之间的翻译器。RemoteControlHandle::resolve_persisted_preference、resolve_unknown_desired_state、resolve_desired_state_after_account_change 都会用它把保存过的记录变成当前该采用的状态。
调用图:被 3 处调用(resolve_persisted_preference, resolve_unknown_desired_state, resolve_desired_state_after_account_change)。
RemoteControlHandle::resolve_persisted_preference51–94 ↗
async fn resolve_persisted_preference(
&self,
app_server_client_name: Option<&str>,
) -> io::Result<bool>
作用:这个函数在远程控制状态还是“未知”时,去本地保存的数据里查用户以前到底想开还是关。它通常用于启动后或状态尚未确定时,给系统一个可靠的初始答案。
数据流:进去的是远程控制句柄自身,以及可选的 app_server_client_name(用来区分不同客户端的名字)→ 它先检查远程控制是否被允许;不允许就直接返回 false → 然后拿一把状态切换锁,避免多个解析动作同时进行 → 如果状态已经不是未知,就直接返回当前是否开启 → 否则它读取状态数据库、加载远程控制账号、整理服务器地址、算出保存用的客户端标识,再查数据库里的登记记录 → 最后把查到的记录转换成期望状态并写回内存状态,输出是否开启。
调用关系:它处在“从未知状态走向明确状态”的入口位置。它会调用 load_remote_control_auth 取得当前账号,调用 normalize_remote_control_url 统一服务器地址格式,再交给 desired_state_from_persisted_enrollment 做记录到状态的翻译。
调用图:调用 3 个内部函数(load_remote_control_auth, desired_state_from_persisted_enrollment, normalize_remote_control_url);外部调用 1 个(matches!)。
RemoteControlHandle::enable96–170 ↗
async fn enable(
&self,
app_server_client_name: Option<&str>,
) -> io::Result<RemoteControlStatusChangedNotification>
作用:这个函数执行“开启远程控制”这件事,并且把这个选择长期保存下来。用户或外部接口要求启用远程控制时,最终需要走到这里,才能完成登记、写数据库、发布新状态。
数据流:进去的是远程控制句柄自身,以及可选的客户端名字 → 它先确认远程控制被允许,再拿状态切换锁 → 然后找到状态数据库,加载账号信息,整理远程控制服务器地址,并确定客户端保存键 → 接着读取当前状态和当前登记,必要时向服务器登记或复用已有登记 → 登记过程中它会重新加载账号,确认账号没有中途切换;如果账号变了,就停止并报错 → 之后拿持久化写入锁,把“远程控制已开启”写进数据库;如果原本没有对应记录,就创建或更新完整登记记录 → 最后发布当前登记,切换内存中的期望状态,广播环境 ID,并返回最新的远程控制状态通知。
调用关系:它是开启远程控制的核心流程。它会调用 load_remote_control_auth 获取账号,调用 normalize_remote_control_url 整理目标地址,调用 acquire_persistence_lock 防止并发写入,必要时调用 update_persisted_remote_control_enrollment 补齐数据库记录,还会用 publish_current_enrollment 和 RemoteControlStatusPublisher::new 把新登记、新状态通知给系统其他部分。
调用图:调用 5 个内部函数(load_remote_control_auth, acquire_persistence_lock, update_persisted_remote_control_enrollment, normalize_remote_control_url, new);外部调用 2 个(new, publish_current_enrollment)。
app-server-transport/src/transport/remote_control/enroll.rs源码 ↗
远程控制不能随便连,后台必须先知道“这是哪台服务器、属于哪个账号、通行证还有效吗”。这个文件就像办门禁卡的窗口:先用账号认证信息把本机登记到后台,拿到服务器编号、环境编号和一个会过期的远程控制令牌(可以理解成临时通行证);之后把不敏感的登记信息写进本地 SQLite 状态库,方便下次启动继续用;真正要配对时,再用令牌向后台发起配对或查询配对是否被用户认领。它也很重视出错信息:会记录请求编号等响应头,响应正文只截取一小段,并把令牌、配对码这类秘密打码,避免日志泄密。还有一个重要行为:令牌快过期前 30 秒就会被认为不能用了,避免刚拿去请求就失效。
RemoteControlEnrollment::start_pairing49–140 ↗
async fn start_pairing(
&self,
request: StartRemoteControlPairingRequest,
) -> io::Result<RemoteControlPairingStartResponse>
作用:用已有的远程控制登记信息向后台发起一次“配对”。配对成功后,会返回给前端展示的配对码和过期时间,让用户能把远程控制客户端和这台服务器连起来。
数据流:进去的是当前登记对象和一个开始配对请求。函数先检查远程控制令牌是不是缺失或快过期;如果不能用,就直接返回“配对暂不可用”。能用时,它带着令牌向后台配对地址发 HTTP 请求,读取响应,失败时把状态码、关键响应头和打码后的正文放进错误里;成功时解析 JSON,确认后台返回的服务器编号和环境编号确实属于当前登记,最后把配对码、手动配对码、环境编号和 Unix 时间戳形式的过期时间交出去。
调用关系:这是远程控制配对流程里真正“开始配对”的一步。它先借助 should_refresh_server_token 判断令牌是否可靠,再通过 build_reqwest_client 发网络请求;出错展示时会用 preview_remote_control_response_body 保护敏感内容。外层流程通常在用户请求开始配对时调用它。
调用图:调用 3 个内部函数(should_refresh_server_token, preview_remote_control_response_body, build_reqwest_client);外部调用 5 个(parse, new, other, format!, pairing_unavailable_error)。
RemoteControlEnrollment::pairing_status142–203 ↗
async fn pairing_status(
&self,
request: RemoteControlPairingStatusRequest,
) -> io::Result<RemoteControlPairingStatusResponse>
作用:查询某次配对有没有被客户端认领。它让前端或调用方知道用户扫码、输入码之后,配对是不是已经完成。
数据流:进去的是当前登记对象和一个配对状态查询请求。函数先确认令牌存在且还没接近过期,然后带令牌向后台的配对状态地址发 HTTP 请求。后台返回失败时,它把常见状态码翻译成更具体的本地错误;成功时解析后台 JSON,只取出 claimed,也就是“是否已被认领”,再包装成对外协议里的状态响应。
调用关系:它和 start_pairing 是一组:先 start_pairing 拿配对码,再由 pairing_status 轮询或查询结果。它同样依赖 should_refresh_server_token 做安全检查,并在错误中使用 preview_remote_control_response_body 生成不泄密的响应预览。
调用图:调用 3 个内部函数(should_refresh_server_token, preview_remote_control_response_body, build_reqwest_client);外部调用 3 个(new, format!, pairing_unavailable_error)。
RemoteControlEnrollment::should_refresh_server_token205–212 ↗
fn should_refresh_server_token(&self) -> bool
作用:判断当前远程控制令牌是不是应该刷新。它不是等令牌真的过期才说要刷新,而是提前 30 秒就提醒,避免请求发到一半令牌失效。
数据流:进去的是登记对象里保存的令牌和过期时间。函数检查两件事:有没有令牌、有没有过期时间,以及过期时间是否已经小于等于“现在加 30 秒”。出来的是一个布尔值:true 表示应该刷新,false 表示还能继续用。
调用关系:start_pairing 和 pairing_status 在真正访问后台前都会先问它一句“通行证还能不能用”。如果它说需要刷新,这两个函数就不会继续配对请求,而是返回配对暂不可用,让上层先走刷新流程。
调用图:被 2 处调用(pairing_status, start_pairing)。
RemoteControlEnrollment::clear_server_token214–217 ↗
fn clear_server_token(&mut self)
作用:清掉登记对象里的临时远程控制令牌和它的过期时间。常用于发现令牌不可信、要强制重新获取时。
数据流:进去的是一个可修改的登记对象。函数把 remote_control_token 设为空,把 expires_at 也设为空。出来没有返回值,但登记对象已经变成“没有可用令牌”的状态。
调用关系:它被 clear_pairing_server_token 调用,属于清理动作。清掉以后,should_refresh_server_token 会认为必须刷新,从而阻止继续拿旧令牌发配对请求。
调用图:被 1 处调用(clear_pairing_server_token)。
load_persisted_remote_control_enrollment220–281 ↗
async fn load_persisted_remote_control_enrollment(
state_db: Option<&StateRuntime>,
remote_control_target: &RemoteControlTarget,
account_id: &str,
app_server_client_name: Option<&str>,
作用:从本地状态库里读取之前保存过的远程控制登记信息。这样程序重启后不必每次都重新登记一台服务器。
数据流:进去的是可选的状态库、远程控制目标地址、账号 ID、以及可选的客户端名字。函数先确认 SQLite 状态库可用;不可用就返回找不到缓存的错误。可用时,它按 websocket 地址、账号和客户端名查询记录;查到就组装成 RemoteControlEnrollment 返回,但不会带上令牌,因为令牌是临时秘密,需要重新刷新;没查到就返回 None。
调用关系:它在 load_or_enroll_server 和 prepare_remote_control_enrollment 这类准备流程中使用。它的作用是先看看本地有没有旧登记,能复用就复用;如果没有,上层再去调用登记流程。
调用图:被 2 处调用(load_or_enroll_server, prepare_remote_control_enrollment);外部调用 6 个(clone, new, other, format!, info!, warn!)。
update_persisted_remote_control_enrollment283–348 ↗
async fn update_persisted_remote_control_enrollment(
state_db: Option<&StateRuntime>,
remote_control_target: &RemoteControlTarget,
account_id: &str,
app_server_client_name: Option<&str
作用:把远程控制登记信息写入本地状态库,或者把已有记录删掉。它保证本地记住“这台服务器曾经登记过”,也能在用户关闭或清理时撤掉这条记录。
数据流:进去的是可选状态库、目标地址、账号 ID、客户端名、可选登记对象,以及远程控制是否启用的标记。函数先确认状态库存在,再检查登记对象里的账号和传入账号是否一致。传入登记对象时,它把 websocket 地址、账号、服务器编号、环境编号、服务器名等写入或更新;传入 None 时,它按目标地址、账号和客户端名删除匹配记录。出来是成功或错误,并会改变本地数据库内容。
调用关系:它被启用远程控制、加载或登记配对服务器、以及多种测试场景调用。它是“内存里的登记对象”和“磁盘上的长期记录”之间的桥,保证下次启动还能找到正确记录,也保证删除时只删匹配的那一条。
调用图:被 12 处调用(load_or_enroll_pairing_server, enable, clearing_persisted_remote_control_enrollment_removes_only_matching_entry, persisted_remote_control_enrollment_round_trips_by_target_and_account, persisted_enable_does_not_follow_auth_to_an_account_without_a_preference, remote_control_http_mode_preserves_enrollment_after_generic_websocket_404, remote_control_http_mode_preserves_stale_enrollment_when_reenrollment_fails, remote_control_http_mode_reenrolls_after_explicit_missing_server_404, remote_control_http_mode_reenrolls_when_refresh_reports_stale_enrollment, remote_control_http_mode_refreshes_persisted_enrollment_before_connecting (+2 more));外部调用 4 个(new, other, format!, info!)。
preview_remote_control_response_body350–368 ↗
fn preview_remote_control_response_body(body: &[u8]) -> String
作用:把后台响应正文变成适合放进错误信息或日志里的短预览。它会先打码敏感字段,再限制长度,防止日志里泄露秘密或塞进超大内容。
数据流:进去的是一段原始字节形式的响应正文。函数先按 UTF-8 尽量转成字符串并去掉首尾空白;如果为空就返回“<empty>”。不为空时,它调用 redact_remote_control_response_body 打码,然后最多保留 4096 字节,并小心不要从一个中文字符或 emoji 中间切断,最后必要时加上省略号。
调用关系:它被配对、登记、刷新、客户端列表、撤销客户端、WebSocket 连接错误格式化等地方使用。凡是要把远程控制后台返回内容写进错误信息时,都会通过它先做安全处理。
调用图:调用 1 个内部函数(redact_remote_control_response_body);被 6 处调用(list_remote_control_clients, revoke_remote_control_client, pairing_status, start_pairing, send_remote_control_server_request, format_remote_control_websocket_connect_error);外部调用 1 个(from_utf8_lossy)。
redact_remote_control_response_body370–387 ↗
fn redact_remote_control_response_body(body: &str) -> String
作用:把响应正文里的秘密字段替换成“<redacted>”。这样排查问题时能看到大致返回了什么,又不会把令牌或配对码泄露到日志里。
数据流:进去的是一段字符串。函数尝试把它当 JSON 对象解析;如果不是 JSON,或不是对象,就原样返回。如果是对象,它会找到 remote_control_token、pairing_code、manual_pairing_code 这些字段,并把值改成“<redacted>”,最后再转回字符串。
调用关系:它是 preview_remote_control_response_body 的内部小帮手。外部不直接靠它做完整预览,而是由 preview_remote_control_response_body 负责“打码 + 截断 + 空内容处理”的完整流程。
调用图:被 1 处调用(preview_remote_control_response_body);外部调用 1 个(String)。
format_headers389–400 ↗
fn format_headers(headers: &HeaderMap) -> String
作用:从后台响应头里提取排查问题最有用的两个标识:请求 ID 和 Cloudflare 的 cf-ray。它让错误信息更容易被服务端日志对上号。
数据流:进去的是 HTTP 响应头集合。函数优先找 x-request-id,找不到再找 x-oai-request-id;还会找 cf-ray。找不到就写“<none>”,值不是合法 UTF-8 就写“<invalid utf-8>”。出来是一段短字符串,例如“request-id: ..., cf-ray: ...”。
调用关系:send_remote_control_server_request 在登记或刷新失败、解析失败时会用它生成错误文字。start_pairing 和 pairing_status 这类直接发请求的流程也用同样思路把响应头放进错误中,方便追踪后台问题。
调用图:被 1 处调用(send_remote_control_server_request);外部调用 2 个(get, format!)。
enroll_remote_control_server402–441 ↗
async fn enroll_remote_control_server(
remote_control_target: &RemoteControlTarget,
auth: &RemoteControlConnectionAuth,
installation_id: &str,
server_name: &str,
) -> io::Result<Remote
作用:把当前本机服务器正式登记到远程控制后台。成功后会得到服务器 ID、环境 ID 和临时令牌,这是远程控制后续能工作的基础。
数据流:进去的是远程控制目标、账号认证信息、安装 ID、服务器名字。函数组装登记请求,里面包含服务器名、操作系统、CPU 架构、应用版本和安装 ID;然后向后台登记地址发送请求。后台成功返回后,它创建 RemoteControlEnrollment,并调用 update_remote_control_server_token 把令牌和过期时间填进去,最后返回完整登记对象。
调用关系:它被 enroll_pairing_server、enroll_and_persist_remote_control_server 以及相关测试调用。它通常发生在本地没有可复用登记、或者需要重新登记时;发送请求的通用细节交给 send_remote_control_server_request,令牌解析交给 update_remote_control_server_token。
调用图:调用 1 个内部函数(update_remote_control_server_token);被 3 处调用(enroll_remote_control_server_parse_failure_includes_response_body, enroll_pairing_server, enroll_and_persist_remote_control_server);外部调用 2 个(clone, env!)。
refresh_remote_control_server443–480 ↗
async fn refresh_remote_control_server(
auth: &RemoteControlConnectionAuth,
installation_id: &str,
enrollment: &mut RemoteControlEnrollment,
) -> io::Result<()>
作用:刷新一台已经登记过的远程控制服务器的临时令牌。令牌快过期时,不需要重新登记整台服务器,只要续一张新的临时通行证。
数据流:进去的是账号认证信息、安装 ID、以及可修改的登记对象。函数拿登记对象里的服务器 ID 组装刷新请求,发给后台刷新地址。拿到结果后,它先确认后台返回的服务器 ID 和环境 ID 没变,防止把别的服务器令牌写进来;确认无误后,用 update_remote_control_server_token 更新令牌和过期时间。
调用关系:它被 refresh_pairing_enrollment 和 prepare_remote_control_enrollment 调用。上层通常先用 should_refresh_server_token 判断需要刷新,再调用它续令牌;它把网络请求交给 send_remote_control_server_request,把时间解析和写入交给 update_remote_control_server_token。
调用图:调用 1 个内部函数(update_remote_control_server_token);被 2 处调用(refresh_pairing_enrollment, prepare_remote_control_enrollment);外部调用 2 个(other, format!)。
send_remote_control_server_request482–540 ↗
async fn send_remote_control_server_request(
url: &str,
auth: &RemoteControlConnectionAuth,
installation_id: &str,
request: &Request,
action: &str,
response_kind: &str,
) -> io
作用:统一发送“登记服务器”和“刷新服务器”这类后台请求。它把认证头、账号头、安装 ID 头、超时、错误格式化和 JSON 解析放在一个地方,避免每个调用点重复写。
数据流:进去的是 URL、认证信息、安装 ID、请求体、动作名和响应类型说明。函数创建 HTTP 客户端,加入认证头、账号 ID 头和安装 ID 头,把请求体按 JSON 发出去。之后读取状态码、响应头和正文;如果状态码失败,就生成带状态码、请求 ID、cf-ray、打码正文的错误;如果成功,就把正文按调用方指定的响应类型解析出来。
调用关系:它是 enroll_remote_control_server 和 refresh_remote_control_server 背后的通用网络管道。它调用 build_reqwest_client 发请求,调用 format_headers 整理排查用响应头,调用 preview_remote_control_response_body 保护错误里的响应正文。
调用图:调用 3 个内部函数(format_headers, preview_remote_control_response_body, build_reqwest_client);外部调用 3 个(new, new, format!)。
update_remote_control_server_token542–556 ↗
fn update_remote_control_server_token(
enrollment: &mut RemoteControlEnrollment,
url: &str,
token: String,
expires_at: String,
) -> io::Result<()>
作用:把后台给的新令牌和过期时间写进登记对象。它负责确认过期时间格式正确,避免保存一个无法判断是否过期的令牌。
数据流:进去的是可修改的登记对象、请求 URL、令牌字符串和 RFC3339 格式的过期时间字符串。函数把过期时间解析成程序内部的时间类型;解析失败就返回带 URL 的错误。成功后,它把令牌放进 remote_control_token,把时间放进 expires_at。
调用关系:enroll_remote_control_server 初次登记成功后会调用它,refresh_remote_control_server 刷新成功后也会调用它。它是后台响应和本地可用登记对象之间的最后一道格式检查。
调用图:被 2 处调用(enroll_remote_control_server, refresh_remote_control_server);外部调用 1 个(parse)。
tests::remote_control_state_runtime575–579 ↗
async fn remote_control_state_runtime(codex_home: &TempDir) -> Arc<StateRuntime>
作用:给测试创建一个临时的本地状态库运行环境。测试需要真实读写缓存时,就用它搭一个干净的数据库。
数据流:进去的是一个临时目录。函数把这个目录当作测试用的 Codex home,调用 StateRuntime::init 初始化状态库,并指定测试 provider 名。出来的是一个可共享的 StateRuntime。
调用关系:持久化相关测试会先调用它拿到状态库,再调用 update_persisted_remote_control_enrollment 和 load_persisted_remote_control_enrollment 验证写入、读取、删除是否正确。
tests::remote_control_enrollment_refreshes_server_token_before_expiry582–601 ↗
fn remote_control_enrollment_refreshes_server_token_before_expiry()
作用:验证令牌快过期时会被提前判定为需要刷新。这个测试防止以后有人把 30 秒安全余量不小心改坏。
数据流:测试构造两个登记对象:一个令牌 29 秒后过期,一个 31 秒后过期。然后分别调用 should_refresh_server_token。结果应该是 29 秒的需要刷新,31 秒的暂时不用刷新。
调用关系:它直接覆盖 RemoteControlEnrollment::should_refresh_server_token 的关键边界行为。start_pairing 和 pairing_status 都依赖这个判断,所以这个测试保护的是配对流程的稳定性。
调用图:调用 1 个内部函数(normalize_remote_control_url);外部调用 3 个(now_utc, assert!, seconds)。
tests::preview_remote_control_response_body_redacts_server_token604–617 ↗
fn preview_remote_control_response_body_redacts_server_token()
作用:验证响应预览会把令牌和配对码打码。它防止敏感信息因为错误日志而泄露。
数据流:进去的是一段包含 server_id、remote_control_token、pairing_code、manual_pairing_code 的 JSON 字节。测试调用 preview_remote_control_response_body,再把结果重新解析成 JSON,确认三个敏感字段都变成“<redacted>”,普通字段仍然保留。
调用关系:它覆盖 preview_remote_control_response_body 和内部的 redact_remote_control_response_body。由于多个网络错误路径都会用这个预览函数,这个测试等于守住日志安全的底线。
调用图:外部调用 1 个(assert_eq!)。
tests::persisted_remote_control_enrollment_round_trips_by_target_and_account620–701 ↗
async fn persisted_remote_control_enrollment_round_trips_by_target_and_account()
作用:验证远程控制登记信息能按“目标地址 + 账号 + 客户端名”正确保存和取回。它确保不同后台地址或不同账号不会串记录。
数据流:测试创建临时状态库、两个不同远程控制目标和两份登记信息。它先把两份记录分别写入数据库,再按不同目标和账号读取。结果应该是:同一个目标和账号能读回原记录,换账号读不到,另一个目标能读回另一条记录。
调用关系:它使用 remote_control_state_runtime 搭数据库,用 update_persisted_remote_control_enrollment 写入,用 load_persisted_remote_control_enrollment 读取。它保护的是本地登记缓存的隔离规则。
调用图:调用 2 个内部函数(update_persisted_remote_control_enrollment, normalize_remote_control_url);外部调用 3 个(new, remote_control_state_runtime, assert_eq!)。
tests::clearing_persisted_remote_control_enrollment_removes_only_matching_entry704–785 ↗
async fn clearing_persisted_remote_control_enrollment_removes_only_matching_entry()
作用:验证删除登记缓存时只删除匹配的那一条,不会误删别的远程控制目标。这样用户清理一个连接时,不会把另一个连接也弄丢。
数据流:测试先在临时数据库里写入两个不同目标的登记。然后对第一个目标调用 update_persisted_remote_control_enrollment,并把 enrollment 传 None 表示删除。最后读取两条记录:第一条应该没了,第二条应该还在。
调用关系:它同样依赖 remote_control_state_runtime、update_persisted_remote_control_enrollment 和 load_persisted_remote_control_enrollment。它专门保护删除路径,和前一个“写入读取”测试互补。
调用图:调用 2 个内部函数(update_persisted_remote_control_enrollment, normalize_remote_control_url);外部调用 3 个(new, remote_control_state_runtime, assert_eq!)。
tests::enroll_remote_control_server_parse_failure_includes_response_body788–832 ↗
async fn enroll_remote_control_server_parse_failure_includes_response_body()
作用:验证登记接口返回格式不对时,错误信息里会包含响应正文预览。这样排查后台返回缺字段之类的问题时,不用猜发生了什么。
数据流:测试启动一个本地 TCP 监听器,伪装成后台接口,只返回一个缺少 remote_control_token 的 JSON。然后调用 enroll_remote_control_server。因为响应缺字段,解析会失败;测试最后检查错误文字,确认里面有 HTTP 状态、请求头占位、响应正文和具体缺字段信息。
调用关系:它调用 enroll_remote_control_server 走真实登记路径,并用 accept_http_request 和 respond_with_json 搭一个极简假 HTTP 服务。它间接覆盖 send_remote_control_server_request 的错误格式化行为。
调用图:调用 2 个内部函数(enroll_remote_control_server, normalize_remote_control_url);外部调用 8 个(bind, accept_http_request, respond_with_json, assert_eq!, unauthenticated_auth_provider, format!, json!, spawn)。
tests::accept_http_request834–858 ↗
async fn accept_http_request(listener: &TcpListener) -> TcpStream
作用:测试用的小工具:接收一条 HTTP 请求,并把请求头读完。它让假后台能等到客户端真的发来了请求。
数据流:进去的是 TCP 监听器。函数最多等 5 秒接收连接,读第一行请求行,然后一行行读请求头,直到空行表示头部结束。出来的是还能继续写响应的 TcpStream。
调用关系:它被 enroll_remote_control_server_parse_failure_includes_response_body 使用。该测试先用它拿到客户端连接,再交给 respond_with_json 写回假响应。
tests::respond_with_json860–871 ↗
async fn respond_with_json(mut stream: TcpStream, body: serde_json::Value)
作用:测试用的小工具:往 TCP 连接里写一个最简单的 JSON HTTP 响应。它用来模拟远程控制后台返回数据。
数据流:进去的是一个 TCP 连接和一个 JSON 值。函数把 JSON 转成字符串,拼出 HTTP/1.1 200 OK 响应,包括 content-type、content-length 和 close 头,然后写入连接并刷新。出来没有业务返回值,但客户端会收到这段响应。
调用关系:它和 accept_http_request 配套,被 enroll_remote_control_server_parse_failure_includes_response_body 用来搭假服务器。这样测试不需要真正访问外部后台,也能检查登记请求处理逻辑。
app-server-transport/src/transport/remote_control/client_tracker.rs源码 ↗
远程控制客户端不是一条普通的直连通道,它会带着 client_id 和 stream_id 发消息。这个文件的核心是 ClientTracker,它把“某个客户端的某条会话流”对应到服务器内部的一条连接。收到 initialize 消息时,它会开新连接;收到普通消息时,它会转给内部传输层;收到服务器要发回客户端的消息时,后台任务会包成 ServerEvent 发出去。它还会处理 ping/pong 心跳、客户端主动关闭、超时清理,以及旧客户端没有 stream_id 的兼容情况。一个重要细节是 seq_id 会用来避免重复投递,但只有消息真的成功送进内部队列后才记录,避免因为队列满导致客户端重试时被误丢。关闭连接也做了特殊处理:即使调用者被取消,关闭事件仍尽量送出去,避免服务器以为连接还活着。
ClientTracker::new55–68 ↗
fn new(
server_event_tx: mpsc::Sender<QueuedServerEnvelope>,
transport_event_tx: mpsc::Sender<TransportEvent>,
shutdown_token: &CancellationToken,
) -> Self
作用:创建一个新的客户端跟踪器。调用者把“发给客户端的队列”“发给内部传输层的队列”和总关闭信号交给它,它就能开始登记和转发远程客户端连接。
数据流:输入两个消息发送通道和一个关闭令牌 → 它建立空的客户端表、旧版 stream_id 表、后台任务集合,并从总关闭令牌派生出自己的子令牌 → 输出一个可以开始工作的 ClientTracker。
调用关系:测试和上层远程控制流程会先调用它搭好跟踪器。它内部用 child_token 做出子关闭信号,后续 handle_message、shutdown、run_client_outbound 都围绕这些状态运转。
调用图:被 11 处调用(cancelled_outbound_task_emits_connection_closed, close_client_keeps_forwarding_after_caller_is_aborted, close_client_waits_for_transport_event_queue_capacity, incoming_message_timeout_does_not_advance_seq_id, initialize_timeout_closes_open_connection, initialize_with_new_stream_id_opens_new_connection_for_same_client, legacy_initialize_without_stream_id_resets_inbound_seq_id, non_close_transport_event_send_times_out_when_queue_stays_full, shutdown_cancels_blocked_outbound_forwarding, new (+1 more));外部调用 3 个(child_token, new, new)。
ClientTracker::bookkeep_join_set70–78 ↗
async fn bookkeep_join_set(&mut self) -> Option<(ClientId, StreamId)>
作用:等待某个客户端的后台发送任务结束,并告诉外层是哪一个客户端会话结束了。它像是在收“后台工人下班通知”。
数据流:输入是跟踪器当前保存的后台任务集合 → 它不断等待任务完成,忽略异常结束的任务 → 一旦有正常结束的任务,就返回对应的 client_id 和 stream_id;如果任务集合空了,就一直等待。
调用关系:当 run_client_outbound 因断开、队列关闭或取消而退出时,这里会收到结果。外层拿到这个客户端键后,通常会再调用 close_client 发送真正的连接关闭事件。
调用图:外部调用 2 个(join_next, pending)。
ClientTracker::shutdown80–88 ↗
async fn shutdown(&mut self)
作用:关闭整个远程客户端跟踪器。它会通知所有后台任务停下,并把还登记着的客户端逐个关闭。
数据流:输入是当前 ClientTracker 的全部连接状态 → 它先取消自己的关闭令牌,再循环关闭 clients 表里的每个客户端,最后等待后台任务都结束 → 结果是所有已知客户端都被清理,后台任务也被收尾。
调用关系:系统停机时会调用它。它把具体关闭动作交给 close_client,把等待后台任务结束的工作交给 drain_join_set。
调用图:调用 2 个内部函数(close_client, drain_join_set);外部调用 1 个(cancel)。
ClientTracker::drain_join_set90–92 ↗
async fn drain_join_set(&mut self)
作用:把后台任务集合里已经或即将结束的任务都等完。它不关心每个任务返回什么,只负责别留下悬着的任务。
数据流:输入是 join_set 里的后台任务 → 它反复等待 join_next,直到没有任务可等 → 不返回业务数据,只保证任务集合被耗尽。
调用关系:shutdown 在取消所有客户端后调用它,用来做最后的清场。
调用图:被 1 处调用(shutdown);外部调用 1 个(join_next)。
ClientTracker::handle_message94–242 ↗
async fn handle_message(
&mut self,
client_envelope: ClientEnvelope,
) -> Result<(), Stopped>
作用:处理远程客户端发来的每一封“信”。它决定这封信是开新连接、转发普通消息、回应心跳,还是关闭连接。
数据流:输入一个 ClientEnvelope,里面有客户端编号、会话流编号、事件内容和可选 seq_id → 它先确定 stream_id,兼容旧客户端;再按事件类型处理:initialize 会建连接,普通消息会转给内部传输层,ping 会刷新活跃时间或回 unknown pong,关闭事件会关闭客户端;成功投递后才记录 seq_id → 输出成功或 Stopped,并可能新增/删除客户端、发送传输事件、启动后台发送任务。
调用关系:这是本文件最核心的入口。它会调用 close_client、send_transport_event、record_inbound_message_delivery、remove_client、spawn_connection_closed 等函数;新连接打开后,它还会启动 run_client_outbound 负责服务器到客户端方向的消息。
调用图:调用 5 个内部函数(close_client, record_inbound_message_delivery, remove_client, send_transport_event, spawn_connection_closed);外部调用 9 个(child_token, now, spawn, run_client_outbound, clone, matches!, next_connection_id, spawn, channel)。
ClientTracker::run_client_outbound244–290 ↗
async fn run_client_outbound(
client_id: ClientId,
stream_id: StreamId,
server_event_tx: mpsc::Sender<QueuedServerEnvelope>,
mut writer_rx: mpsc::Receiver<QueuedOutgoin
作用:为某个客户端会话运行一个后台发送任务,把服务器内部产生的消息转成远程客户端能收到的事件。它也会把客户端状态变化转成 pong 发回去。
数据流:输入客户端编号、会话流编号、服务器事件发送队列、内部写出队列、状态监听器和断开令牌 → 它循环等待三类事情:被取消、内部有消息要发、pong 状态变化;拿到消息后包成 QueuedServerEnvelope 发到 server_event_tx → 结束时返回这个客户端会话的 key。
调用关系:handle_message 在新连接打开时启动它。bookkeep_join_set 后续会收它的退出结果,再由外层决定是否调用 close_client 做连接关闭清理。
调用图:外部调用 1 个(select!)。
ClientTracker::close_expired_clients292–307 ↗
async fn close_expired_clients(
&mut self,
) -> Result<Vec<(ClientId, StreamId)>, Stopped>
作用:清理太久没有动静的远程客户端。这样可以避免断网或崩溃的客户端永远占着服务器资源。
数据流:输入当前所有客户端状态 → 它读取当前时间,找出最后活动时间超过空闲上限的客户端,逐个调用 close_client → 输出被关闭的客户端会话列表;如果传输层已经停止,则返回 Stopped。
调用关系:通常由定时清扫逻辑周期性调用。它用 remote_control_client_is_alive 判断是否还活着,用 close_client 做实际关闭。
调用图:调用 1 个内部函数(close_client);外部调用 1 个(now)。
ClientTracker::close_client309–321 ↗
async fn close_client(
&mut self,
client_key: &(ClientId, StreamId),
) -> Result<(), Stopped>
作用:关闭某一个客户端会话。它会从登记簿中移除该客户端,通知后台任务停下,并告诉内部传输层连接已经关了。
数据流:输入一个由 client_id 和 stream_id 组成的客户端键 → 它先 remove_client;如果客户端不存在就直接成功;存在则取消该客户端的断开令牌,再发送 ConnectionClosed 事件 → 输出成功或 Stopped,同时 clients 表被更新。
调用关系:handle_message 收到 ClientClosed 或重开同一会话时会调用它;close_expired_clients 清理超时客户端时会调用它;shutdown 停机时也会调用它。
调用图:调用 2 个内部函数(remove_client, send_transport_event);被 3 处调用(close_expired_clients, handle_message, shutdown)。
ClientTracker::remove_client323–333 ↗
fn remove_client(&mut self, client_key: &(ClientId, StreamId)) -> Option<ClientState>
作用:只做“从登记簿删除客户端”这件事,不负责发关闭通知。它还会顺手清理旧版客户端的 stream_id 记录。
数据流:输入客户端键 → 它从 clients 表拿走对应 ClientState;如果 legacy_stream_ids 里记录的也是这条流,就一起删除 → 输出被移除的 ClientState,或在找不到时输出 None。
调用关系:close_client 用它作为关闭的第一步。handle_message 在 initialize 投递失败回滚时也会直接用它撤销刚创建的连接。
调用图:被 2 处调用(close_client, handle_message)。
ClientTracker::send_transport_event335–367 ↗
async fn send_transport_event(&self, event: TransportEvent) -> Result<(), Stopped>
作用:把远程控制侧发生的事情发送给内部传输层,比如连接打开、收到消息、连接关闭。它给发送设置了超时,防止队列堵住后整个远程控制卡死。
数据流:输入一个 TransportEvent → 如果是连接关闭,就交给 send_connection_closed 特殊处理;其他事件会在限定时间内发送到 transport_event_tx,并按结果记录警告或返回 Stopped → 输出发送成功或失败。
调用关系:handle_message 和 close_client 都靠它把事件交给内部系统。它用 transport_event_name 只为日志取一个好读的名字;关闭事件会走 send_connection_closed,因为关闭通知需要更强的送达保障。
调用图:调用 2 个内部函数(send_connection_closed, transport_event_name);被 2 处调用(close_client, handle_message);外部调用 3 个(send, timeout, warn!)。
ClientTracker::record_inbound_message_delivery369–380 ↗
fn record_inbound_message_delivery(
&mut self,
client_key: &(ClientId, StreamId),
seq_id: Option<u64>,
)
作用:记录某个客户端最近一次已经成功送达服务器内部的消息序号。这样客户端重试同一条旧消息时,可以安全忽略,避免重复执行。
数据流:输入客户端键和可选 seq_id → 如果 seq_id 存在且客户端还在表里,就把 last_inbound_seq_id 更新成这个值 → 输出为空,只改变客户端状态。
调用关系:handle_message 只有在 IncomingMessage 真正发送成功后才调用它。这个顺序很重要:如果发送失败,就不记录,客户端之后重试还能被正常处理。
调用图:被 1 处调用(handle_message)。
ClientTracker::send_connection_closed382–395 ↗
async fn send_connection_closed(&self, connection_id: ConnectionId) -> Result<(), Stopped>
作用:专门发送“连接已关闭”事件。它会把发送动作放进独立任务里,避免调用者被取消时关闭通知也跟着消失。
数据流:输入 connection_id → 它调用 spawn_connection_closed 启动一个发送任务,然后等待这个任务结果;如果任务失败会写警告 → 输出成功或 Stopped。
调用关系:send_transport_event 遇到 ConnectionClosed 时会转到这里。它再把实际发送交给 spawn_connection_closed。
调用图:调用 1 个内部函数(spawn_connection_closed);被 1 处调用(send_transport_event);外部调用 1 个(warn!)。
ClientTracker::spawn_connection_closed397–418 ↗
fn spawn_connection_closed(
&self,
connection_id: ConnectionId,
) -> JoinHandle<Result<(), Stopped>>
作用:启动一个独立小任务去发送连接关闭事件。这样即使当前关闭流程被中断,这个小任务仍有机会把“门已经关了”的消息送出去。
数据流:输入 connection_id → 它复制 transport_event_tx,记录日志,然后启动 tokio 任务发送 TransportEvent::ConnectionClosed → 输出这个任务的 JoinHandle,也就是之后可以等待的任务句柄。
调用关系:send_connection_closed 用它来可靠发送关闭事件。handle_message 在 initialize 投递失败回滚时也会直接调用它,因为那种情况下不能再被队列阻塞影响重连。
调用图:被 2 处调用(handle_message, send_connection_closed);外部调用 3 个(clone, info!, spawn)。
transport_event_name421–427 ↗
fn transport_event_name(event: &TransportEvent) -> &'static str
作用:把传输事件变成简短的英文名字,方便写日志。比如连接打开会变成 connection_opened。
数据流:输入一个 TransportEvent 引用 → 它按事件种类匹配 → 输出固定字符串名称,不修改任何状态。
调用关系:send_transport_event 在发送失败或超时时调用它,把日志写得更清楚。
调用图:被 1 处调用(send_transport_event)。
remote_control_message_starts_connection429–435 ↗
fn remote_control_message_starts_connection(message: &JSONRPCMessage) -> bool
作用:判断一条客户端 JSON-RPC 消息是不是用来开始连接的 initialize 请求。JSON-RPC 可以理解为一种用 JSON 包起来的远程调用格式。
数据流:输入一条 JSONRPCMessage → 它检查这是不是 Request,并且 method 是否等于 initialize → 输出 true 或 false。
调用关系:handle_message 用它判断是否应该新建连接,或在同一会话重新 initialize 时先关掉旧连接。
调用图:外部调用 1 个(matches!)。
remote_control_client_is_alive437–439 ↗
fn remote_control_client_is_alive(client: &ClientState, now: Instant) -> bool
作用:判断某个客户端最近是否还算活跃。它用最后活动时间和当前时间比较,超过 10 分钟没动静就认为不活跃。
数据流:输入客户端状态和当前时间 → 它计算当前时间距离 last_activity_at 过去了多久 → 输出 true 表示还没超时,false 表示该清理。
调用关系:close_expired_clients 用它筛选需要关闭的客户端。
调用图:外部调用 1 个(duration_since)。
tests::initialize_envelope455–457 ↗
fn initialize_envelope(client_id: &str) -> ClientEnvelope
作用:测试用的小工具,快速造一个不带 stream_id 的 initialize 客户端消息。它模拟旧版客户端发起连接。
数据流:输入 client_id 字符串 → 它调用 initialize_envelope_with_stream_id,并传入 None 作为 stream_id → 输出一个 ClientEnvelope。
调用关系:多个测试用它来验证旧版客户端兼容、连接打开、关闭任务等行为。
调用图:外部调用 1 个(initialize_envelope_with_stream_id)。
tests::initialize_envelope_with_stream_id459–482 ↗
fn initialize_envelope_with_stream_id(
client_id: &str,
stream_id: Option<&str>,
) -> ClientEnvelope
作用:测试用的小工具,造一个 initialize 客户端消息,并可选择带上 stream_id。它让测试能方便模拟新旧两种客户端。
数据流:输入 client_id 和可选 stream_id → 它组装 JSON-RPC initialize 请求,填入客户端信息、seq_id 和流编号 → 输出完整 ClientEnvelope。
调用关系:大部分测试通过它触发 handle_message 的开连接流程,用来检查连接编号、重连、超时和关闭行为。
tests::initialized_notification484–489 ↗
fn initialized_notification() -> JSONRPCMessage
作用:测试用的小工具,造一条 initialized 通知消息。它代表客户端在初始化后继续发来的普通消息。
数据流:没有业务输入 → 它创建一个 JSON-RPC Notification,method 是 initialized → 输出 JSONRPCMessage。
调用关系:测试用它验证普通入站消息转发、队列满时重试、关闭队列阻塞等场景。
调用图:外部调用 1 个(Notification)。
tests::cancelled_outbound_task_emits_connection_closed492–549 ↗
async fn cancelled_outbound_task_emits_connection_closed()
作用:这个测试确认:如果客户端的后台发送任务被取消,系统最终会发出连接关闭事件。否则内部服务器可能误以为连接还在。
数据流:测试先创建跟踪器和通道,再发送 initialize 打开连接 → 读取连接打开和初始化转发事件后,取消 disconnect_sender,并通过 bookkeep_join_set 拿到结束的客户端 → 调用 close_client 后,检查 transport_event_rx 收到正确的 ConnectionClosed。
调用关系:它覆盖 ClientTracker::new、handle_message、bookkeep_join_set 和 close_client 的配合,重点验证 run_client_outbound 退出后的清理链路。
调用图:调用 1 个内部函数(new);外部调用 7 个(new, from_secs, initialize_envelope, assert_eq!, channel, panic!, timeout)。
tests::shutdown_cancels_blocked_outbound_forwarding552–606 ↗
async fn shutdown_cancels_blocked_outbound_forwarding()
作用:这个测试确认:即使服务器到客户端的发送队列被堵住,shutdown 也不会卡死。停机时最怕等一个永远发不出去的消息。
数据流:测试先把 server_event_tx 队列填满,再打开一个客户端并向 writer 塞一条服务器通知 → 后台发送任务会被堵在转发上 → 调用 shutdown,并检查它能在限定时间内结束。
调用关系:它验证 ClientTracker::shutdown 会取消 run_client_outbound,并通过 drain_join_set 收尾,而不是被满队列拖住。
调用图:调用 2 个内部函数(new, new);外部调用 10 个(new, from_secs, ConfigWarning, AppServerNotification, initialize_envelope, new, new, channel, panic!, timeout)。
tests::non_close_transport_event_send_times_out_when_queue_stays_full609–631 ↗
async fn non_close_transport_event_send_times_out_when_queue_stays_full()
作用:这个测试确认:普通传输事件在内部队列一直满时会超时失败,而不是无限等待。
数据流:测试创建容量很小的 transport_event_tx,并先塞满它 → 再调用 send_transport_event 发送 IncomingMessage → 结果应该是错误。
调用关系:它直接验证 send_transport_event 的超时保护,也间接说明为什么 handle_message 可能返回 Stopped。
调用图:调用 1 个内部函数(new);外部调用 5 个(new, initialized_notification, assert!, channel, next_connection_id)。
tests::incoming_message_timeout_does_not_advance_seq_id634–693 ↗
async fn incoming_message_timeout_does_not_advance_seq_id()
作用:这个测试确认:消息因为队列满而没送进去时,不会记录 seq_id。这样客户端重试时不会被当成重复消息丢掉。
数据流:测试先打开带 stream_id 的连接,再把 transport_event_tx 塞满 → 第一次发送 seq_id 为 1 的消息会失败 → 清空队列后再次发送同一条消息,应该成功转发到原连接。
调用关系:它重点验证 handle_message 和 record_inbound_message_delivery 的顺序:只有 send_transport_event 成功后才记录序号。
调用图:调用 1 个内部函数(new);外部调用 10 个(new, initialize_envelope_with_stream_id, initialized_notification, new, new, assert!, assert_eq!, channel, panic!, next_connection_id)。
tests::initialize_timeout_closes_open_connection696–730 ↗
async fn initialize_timeout_closes_open_connection()
作用:这个测试确认:initialize 时如果连接已经打开但初始化消息投递超时,系统会回滚并发送关闭事件。
数据流:测试用容量很小的传输队列启动 handle_message → initialize 过程中先产生 ConnectionOpened,随后 IncomingMessage 发送超时 → 测试检查结果是错误,并且还能收到对应 ConnectionClosed。
调用关系:它覆盖 handle_message 中“开连接后投递初始化失败”的补救路径,验证 remove_client 和 spawn_connection_closed 会被用来撤销半开的连接。
调用图:调用 1 个内部函数(new);外部调用 7 个(new, initialize_envelope_with_stream_id, assert!, assert_eq!, channel, panic!, spawn)。
tests::close_client_waits_for_transport_event_queue_capacity733–795 ↗
async fn close_client_waits_for_transport_event_queue_capacity()
作用:这个测试确认:正常 close_client 会等待传输事件队列有空位后再发送关闭事件。也就是说,它不会悄悄丢掉关闭通知。
数据流:测试先打开连接,再把 transport_event_tx 塞满 → 调用 close_client 后短时间内应仍在等待 → 清掉队列里的消息后,close_client 完成,并收到 ConnectionClosed。
调用关系:它验证 close_client 通过 send_transport_event 走关闭路径时,会保留关闭事件的送达,而不是像普通事件那样快速超时丢弃。
调用图:调用 1 个内部函数(new);外部调用 10 个(new, initialize_envelope_with_stream_id, initialized_notification, new, new, assert!, assert_eq!, channel, panic!, pin!)。
tests::close_client_keeps_forwarding_after_caller_is_aborted798–856 ↗
async fn close_client_keeps_forwarding_after_caller_is_aborted()
作用:这个测试确认:关闭客户端的调用者就算被强行中止,连接关闭事件仍会继续发送。这样可以避免清理动作半路消失。
数据流:测试打开连接并塞满传输队列 → 在单独任务里调用 close_client,让它阻塞等待队列空位,然后中止这个任务 → 清空队列后,仍然应该收到 ConnectionClosed。
调用关系:它验证 send_connection_closed 和 spawn_connection_closed 的设计目的:关闭事件在独立任务中继续转发,不完全依赖原调用者活着。
调用图:调用 1 个内部函数(new);外部调用 12 个(new, from_secs, initialize_envelope_with_stream_id, initialized_notification, new, new, assert!, assert_eq!, channel, panic! (+2 more))。
tests::initialize_with_new_stream_id_opens_new_connection_for_same_client859–892 ↗
async fn initialize_with_new_stream_id_opens_new_connection_for_same_client()
作用:这个测试确认:同一个 client_id 如果使用新的 stream_id initialize,会打开一条新的服务器连接。这样同一个客户端可以区分不同会话。
数据流:测试先用 stream-1 初始化并读取第一个 connection_id → 再用 stream-2 对同一 client_id 初始化 → 读取第二个 connection_id,并确认两个编号不同。
调用关系:它验证 handle_message 以 client_id 加 stream_id 作为客户端键,而不是只按 client_id 合并所有连接。
调用图:调用 1 个内部函数(new);外部调用 5 个(new, initialize_envelope_with_stream_id, assert_ne!, channel, panic!)。
tests::legacy_initialize_without_stream_id_resets_inbound_seq_id895–937 ↗
async fn legacy_initialize_without_stream_id_resets_inbound_seq_id()
作用:这个测试确认:旧版客户端不带 stream_id 时,initialize 后的后续消息仍能走到同一连接,并且不会被 seq_id 误判为重复。
数据流:测试用不带 stream_id 的 initialize 打开连接 → 再发送一个同样不带 stream_id、seq_id 为 0 的 initialized 通知 → 检查它被转发到原来的 connection_id。
调用关系:它覆盖 handle_message 中 legacy_stream_ids 的兼容逻辑,确保旧客户端升级前仍能正常通信。
调用图:调用 1 个内部函数(new);外部调用 7 个(new, Notification, initialize_envelope, new, assert_eq!, channel, panic!)。
app-server-transport/src/transport/remote_control/websocket.rs源码 ↗
可以把这个文件想成“远程控制电话线”的总机。它先确认用户已登录,再把本机登记到远程控制服务,之后建立 WebSocket 长连接。连接上以后,它一边把本地服务器产生的事件发给远端,一边接收远端客户端发来的指令,并交给 ClientTracker 去处理。为了避免断线丢消息,它会给发出去的消息编号,放进一个待确认缓冲区,等远端发来 Ack(确认收到)后再删掉。大消息会被切片传输,收到时再拼回去;重复、过大、过期的片段会被丢掉。连接失败时它不会立刻疯狂重试,而是用退避重连(失败越多等得越久)保护服务。它还会处理登录过期、服务器登记失效、用户切换账号、手动禁用远程控制等情况。
BoundedOutboundBuffer::new93–100 ↗
fn new() -> (Self, watch::Receiver<usize>)
作用:创建一个“待确认消息盒子”,用来暂存已经发给远端但还没被确认收到的消息。同时给外面一个计数观察器,让发送端知道盒子里现在有多少条。
数据流:进去没有业务数据 → 它建立一个按客户端和流分组的空缓冲区,并创建一个 watch 通道(能把最新数字通知给监听者) → 出来是缓冲区本体和一个可观察的使用量接收器。
调用关系:RemoteControlWebsocket::new 启动远程控制时会建它;写入线程用它限制未确认消息数量;多组测试也直接创建它来验证确认和容量行为。
调用图:被 17 处调用(new, outbound_buffer_acks_by_stream_id, outbound_buffer_advances_segmented_acks_by_wire_cursor, outbound_buffer_retains_unacked_messages_until_ack_advances, outbound_buffer_treats_segmentless_acks_as_seq_level_acks, run_server_writer_inner_assigns_contiguous_seq_ids_per_stream, run_server_writer_inner_sends_periodic_ping_frames, run_websocket_reader_inner_times_out_without_pong_frames, websocket_state_allows_replay_after_later_chunk_drops, websocket_state_allows_replay_after_rejected_out_of_order_chunk (+7 more));外部调用 2 个(new, channel)。
BoundedOutboundBuffer::insert102–111 ↗
fn insert(&mut self, server_envelope: &ServerEnvelope)
作用:把一条刚发出去的服务器消息记进待确认列表,防止连接断了以后忘记它还没被远端确认。
数据流:进去是一条 ServerEnvelope(服务器发给远端的消息信封) → 它按客户端 ID 和流 ID 找到对应队列,把消息副本放进去,并把已用数量加一 → 出来没有返回值,但缓冲区和计数都变了。
调用关系:RemoteControlWebsocket::run_server_writer_inner 在真正发送消息前后会调用它;之后 WebsocketState::record_client_message_delivery 收到 Ack 时会间接把这些记录删掉。
调用图:外部调用 2 个(send_modify, clone)。
BoundedOutboundBuffer::ack113–139 ↗
fn ack(
&mut self,
client_id: &ClientId,
stream_id: &StreamId,
acked_seq_id: u64,
acked_segment_id: Option<usize>,
)
作用:处理远端发来的 Ack(确认收到),把已经确认的消息从待确认列表里清掉。
数据流:进去是客户端 ID、流 ID、已确认的序号,以及可选的分片编号 → 它只检查同一个客户端同一条流里的消息,删掉序号不晚于确认位置的记录,并同步减少使用量 → 出来没有返回值,但待确认消息变少,空队列会被移除。
调用关系:WebsocketState::record_client_message_delivery 在读到客户端确认消息时调用它;这样写入端就能继续发送新消息,不会被旧消息占满容量。
调用图:被 1 处调用(record_client_message_delivery);外部调用 2 个(clone, clone)。
BoundedOutboundBuffer::server_envelopes141–145 ↗
fn server_envelopes(&self) -> impl Iterator<Item = &ServerEnvelope>
作用:拿到当前所有还没确认的服务器消息,通常用于重连后补发。
数据流:进去是当前缓冲区 → 它把各个客户端、各个流里的队列摊平成一个迭代器 → 出来是一串待确认消息的只读引用。
调用关系:RemoteControlWebsocket::run_server_writer_inner 在新连接刚开始时用它,把上次没确认的消息重新发给远端。
WebsocketState::observe_client_message157–200 ↗
fn observe_client_message(
&mut self,
client_envelope: ClientEnvelope,
wire_size_bytes: usize,
) -> ClientSegmentObservation
作用:检查一条从远端来的客户端消息能不能继续处理。它会挡掉重复分片、过大分片、已经处理过的旧分片,并负责把分片拼成完整消息。
数据流:进去是客户端消息信封和它在网络上的大小 → 它先算出消息的客户端、流和序号,查重、查大小,再交给分片重组器 → 出来是“可转发完整消息”“还在等待更多分片”或“丢弃”。
调用关系:RemoteControlWebsocket::run_websocket_reader_inner 每读到一条文本消息后调用它;它把复杂的分片安全检查集中在 WebsocketState 里。
调用图:调用 3 个内部函数(invalidate_stream, observe, should_ignore_chunk);被 1 处调用(observe_client_message);外部调用 2 个(client_message_key, warn!)。
WebsocketState::record_client_message_delivery202–225 ↗
fn record_client_message_delivery(
&mut self,
client_envelope: &ClientEnvelope,
client_message_key: Option<((ClientId, Option<StreamId>), u64)>,
)
作用:记录一条客户端消息已经成功交给本地处理了,并顺手处理它里面带来的游标和确认信息。
数据流:进去是已投递的客户端消息,以及可选的分片消息键 → 它更新订阅游标,记住这个分片序号已完成;如果消息是 Ack,就通知 outbound_buffer 删除已确认的服务器消息 → 出来没有返回值,但状态被推进。
调用关系:RemoteControlWebsocket::run_websocket_reader_inner 在 ClientTracker 成功处理消息后调用它,确保只有真正送达本地的消息才算完成。
调用图:调用 1 个内部函数(ack)。
WebsocketState::invalidate_client_message_stream227–230 ↗
WebsocketState::invalidate_client_message_client232–235 ↗
fn invalidate_client_message_client(&mut self, client_id: &ClientId)
作用:当整个客户端关闭时,清掉这个客户端所有流的消息进度。
数据流:进去是客户端 ID → 它遍历记录,只保留其他客户端的进度 → 出来没有返回值,但这个客户端的旧进度全部消失。
调用关系:RemoteControlWebsocket::run_websocket_reader_inner 在收到没有指定流的 ClientClosed 时使用它,防止客户端重连后被旧状态误伤。
WebsocketState::client_message_key237–251 ↗
fn client_message_key(
client_envelope: &ClientEnvelope,
) -> Option<((ClientId, Option<StreamId>), u64)>
作用:从客户端消息里提取“查重用的钥匙”。只有客户端消息分片才需要这把钥匙。
数据流:进去是一条 ClientEnvelope → 如果它是 ClientMessageChunk 且带序号,就取出客户端 ID、可选流 ID 和序号;否则返回空 → 出来是可选的查重键。
调用关系:WebsocketState::observe_client_message 和 RemoteControlWebsocket::run_websocket_reader_inner 都用它来决定分片消息是否已经处理过。
调用图:被 1 处调用(run_websocket_reader_inner)。
RemoteControlStatusPublisher::new323–325 ↗
fn new(tx: watch::Sender<RemoteControlStatusChangedNotification>) -> Self
作用:包装一个状态通知通道,做成专门发布远程控制状态的小工具。
数据流:进去是 watch::Sender(保存最新状态并通知订阅者的发送端) → 它放进结构体 → 出来是 RemoteControlStatusPublisher。
调用关系:远程控制启动流程和测试会创建它;之后连接、登记、环境变化都通过它广播状态。
调用图:被 4 处调用(enable, start_remote_control, plain_start_resolves_persisted_remote_control_preference, remote_control_status_channel)。
RemoteControlStatusPublisher::status327–329 ↗
RemoteControlStatusPublisher::publish_status331–355 ↗
fn publish_status(&self, connection_status: RemoteControlConnectionStatus)
作用:发布连接状态变化,并避免重复通知同一个状态。
数据流:进去是新的连接状态 → 它基于旧状态生成新状态;如果真的变了,就写入通道并打印日志 → 出来没有返回值,但订阅者会看到状态更新。
调用关系:RemoteControlWebsocket::connect 和 RemoteControlWebsocket::run 在连接中、已连接、出错、禁用等时刻调用它。
调用图:被 2 处调用(connect, run);外部调用 2 个(send_if_modified, info!)。
RemoteControlStatusPublisher::publish_environment_id357–387 ↗
fn publish_environment_id(&self, environment_id: Option<String>)
作用:发布当前远程控制环境 ID 的变化。环境 ID 可以理解为远端给这台登记服务器分配的所属环境标识。
数据流:进去是可选的 environment_id → 如果当前不是禁用状态,它把环境 ID 写进状态;没变化就不通知 → 出来没有返回值,但状态订阅者可能收到更新。
调用关系:prepare_remote_control_enrollment 和 enroll_and_persist_remote_control_server 在加载、创建或清空登记信息时调用它。
调用图:被 2 处调用(enroll_and_persist_remote_control_server, prepare_remote_control_enrollment);外部调用 2 个(send_if_modified, info!)。
RemoteControlWebsocket::new401–449 ↗
fn new(
config: RemoteControlWebsocketConfig,
state_db: Option<Arc<StateRuntime>>,
auth_manager: Arc<AuthManager>,
channels: RemoteControlChannels,
shutdown_tok
作用:组装远程控制 WebSocket 运行所需的全部零件。
数据流:进去是配置、状态数据库、登录管理器、各种通道、关闭令牌和目标状态通道 → 它创建客户端追踪器、待确认缓冲区、共享状态、消息队列和鉴权监听器 → 出来是一个可运行的 RemoteControlWebsocket。
调用关系:start_remote_control 等启动代码会调用它;生成的对象随后由 RemoteControlWebsocket::run 驱动整个生命周期。
调用图:调用 2 个内部函数(new, new);被 3 处调用(start_remote_control, plain_start_resolves_persisted_remote_control_preference, run_remote_control_websocket_loop_shutdown_cancels_reconnect_backoff);外部调用 6 个(new, child_token, new, new, default, channel)。
RemoteControlWebsocket::run455–557 ↗
async fn run(
mut self,
app_server_client_name_rx: Option<oneshot::Receiver<String>>,
)
作用:这是远程控制 WebSocket 的主循环。它等待启用,连接远端,断了就重连,关闭时清理客户端。
数据流:进去是 WebSocket 运行对象和可选的 app server 客户端名接收器 → 它先等名称、解析未知偏好,再反复等待启用、连接、运行连接、处理结束原因 → 出来没有业务返回值;退出前会关闭 ClientTracker。
调用关系:由远程控制启动流程作为后台任务运行;它把工作分给 wait_for_app_server_client_name、resolve_unknown_desired_state、connect 和 run_connection。
调用图:调用 7 个内部函数(publish_status, status, connect, resolve_unknown_desired_state, run_connection, wait_for_app_server_client_name, wait_until_enabled);外部调用 5 个(child_token, send_replace, info!, matches!, warn!)。
RemoteControlWebsocket::wait_for_app_server_client_name559–575 ↗
async fn wait_for_app_server_client_name(
&self,
app_server_client_name_rx: Option<oneshot::Receiver<String>>,
) -> Result<Option<String>, ()>
作用:等待外部告诉它 app server 的客户端名;如果不需要名字就马上继续。
数据流:进去是可选的一次性接收器 → 如果有接收器,它在“收到名字”和“被要求关闭”之间二选一;如果没有就返回 None → 出来是可选名字,或表示中止的错误。
调用关系:RemoteControlWebsocket::run 启动早期调用它,拿到名字后会用于持久化登记选择。
RemoteControlWebsocket::resolve_unknown_desired_state577–643 ↗
async fn resolve_unknown_desired_state(
&mut self,
app_server_client_name: Option<&str>,
) -> bool
作用:当远程控制开关状态还是“未知”时,它根据本地保存的登记记录判断应该启用还是禁用。
数据流:进去是可选客户端名 → 它规范化远程 URL、检查数据库和登录信息,读取持久化登记记录,转换成期望状态 → 出来是布尔值,表示是否成功完成或继续运行;过程中会更新 desired_state。
调用关系:RemoteControlWebsocket::run 在主循环前调用它;它依赖 load_remote_control_auth、数据库读取和 desired_state_from_persisted_enrollment。
调用图:调用 5 个内部函数(load_remote_control_auth, desired_state_from_persisted_enrollment, normalize_remote_control_url, transition_unknown_to, wait_for_preference_resolution_retry);被 1 处调用(run);外部调用 3 个(info!, matches!, warn!)。
RemoteControlWebsocket::transition_unknown_to645–653 ↗
fn transition_unknown_to(&self, desired_state: RemoteControlDesiredState)
作用:只在当前状态仍是 Unknown 时,把它改成一个明确状态。
数据流:进去是目标状态 → 它检查共享的 desired_state;如果别人已经改过就不动,否则写入新状态 → 出来没有返回值。
调用关系:RemoteControlWebsocket::resolve_unknown_desired_state 用它避免覆盖用户或其他任务刚刚做出的状态决定。
调用图:被 1 处调用(resolve_unknown_desired_state)。
RemoteControlWebsocket::wait_for_preference_resolution_retry655–661 ↗
async fn wait_for_preference_resolution_retry(&mut self) -> bool
作用:解析偏好失败后等一小会儿再试,同时能被关闭或状态变化打断。
数据流:进去是当前对象 → 它等待关闭信号、desired_state 改变,或固定重试间隔到期 → 出来 true 表示可以再试,false 表示应该停下。
调用关系:RemoteControlWebsocket::resolve_unknown_desired_state 在缺少登录信息或读数据库失败时调用它。
调用图:被 1 处调用(resolve_unknown_desired_state);外部调用 1 个(select!)。
RemoteControlWebsocket::wait_until_enabled663–668 ↗
RemoteControlWebsocket::connect670–830 ↗
async fn connect(
&mut self,
shutdown_token: &CancellationToken,
app_server_client_name: Option<&str>,
) -> ConnectOutcome
作用:尝试建立远程控制 WebSocket 连接,并处理失败重试、禁用、登录变化和 URL 错误。
数据流:进去是本轮连接的关闭令牌和可选客户端名 → 它发布 Connecting,准备远程目标和订阅游标,调用真正的连接函数;失败时按错误类型等待后重试 → 出来是已连接的 WebSocket、Disabled 或 Shutdown。
调用关系:RemoteControlWebsocket::run 每个连接周期调用它;它把底层握手交给 connect_remote_control_websocket,把重连等待交给 next_reconnect_delay。
调用图:调用 3 个内部函数(normalize_remote_control_url, publish_status, next_reconnect_delay);被 1 处调用(run);外部调用 8 个(new, snapshot, Connected, borrow, clone, info!, select!, warn!)。
RemoteControlWebsocket::run_connection832–875 ↗
async fn run_connection(
&self,
websocket_connection: WebSocketStream<MaybeTlsStream<TcpStream>>,
shutdown_token: CancellationToken,
) -> ConnectionEndReason
作用:在一条已经连上的 WebSocket 上启动读写两个工人,并等待它们结束或被禁用。
数据流:进去是一条 WebSocket 连接和关闭令牌 → 它拆成写端和读端,分别启动发送任务和接收任务;然后等待关闭、禁用或任一任务停止 → 出来是连接结束原因,并会取消本轮任务。
调用关系:RemoteControlWebsocket::run 在 connect 成功后调用它;它驱动 run_server_writer 和 run_websocket_reader,最后用 join_connection_workers 收尾。
调用图:被 1 处调用(run);外部调用 9 个(cancel, clone, join_connection_workers, run_server_writer, run_websocket_reader, split, clone, select!, new)。
RemoteControlWebsocket::join_connection_workers877–895 ↗
async fn join_connection_workers(
join_set: &mut tokio::task::JoinSet<()>,
shutdown_timeout: std::time::Duration,
)
作用:等待连接读写任务正常结束;如果等太久,就强制取消它们。
数据流:进去是任务集合和超时时间 → 它先限时等待 drain_join_set;超时就记录警告并 abort_all 强制结束,再继续清空结果 → 出来没有返回值,任务集合会被清干净。
调用关系:RemoteControlWebsocket::run_connection 在每轮连接结束时调用它;测试验证卡住的任务会被终止。
调用图:被 1 处调用(join_connection_workers_aborts_stuck_worker_after_timeout);外部调用 4 个(abort_all, drain_join_set, timeout, warn!)。
RemoteControlWebsocket::drain_join_set897–899 ↗
async fn drain_join_set(join_set: &mut tokio::task::JoinSet<()>)
作用:把任务集合里已经结束或即将结束的任务结果全部取完。
数据流:进去是 JoinSet(多个异步任务的集合) → 它循环等待 join_next,直到没有任务 → 出来没有返回值,集合被排空。
调用关系:RemoteControlWebsocket::join_connection_workers 用它做正常等待和强制取消后的收尾。
调用图:外部调用 1 个(join_next)。
RemoteControlWebsocket::run_server_writer901–926 ↗
async fn run_server_writer(
state: Arc<Mutex<WebsocketState>>,
server_event_rx: Arc<Mutex<mpsc::Receiver<super::QueuedServerEnvelope>>>,
used_rx: watch::Receiver<usize>,
作用:包装服务器消息写入循环,负责记录它是正常停止还是断开出错。
数据流:进去是共享状态、服务器事件队列、缓冲区计数、WebSocket 写端、ping 间隔和关闭令牌 → 它调用 run_server_writer_inner 做实际发送 → 出来没有返回值,只在结束时写日志。
调用关系:RemoteControlWebsocket::run_connection 把它作为写入工人启动。
调用图:外部调用 2 个(run_server_writer_inner, warn!)。
RemoteControlWebsocket::run_server_writer_inner932–1064 ↗
async fn run_server_writer_inner(
state: Arc<Mutex<WebsocketState>>,
server_event_rx: Arc<Mutex<mpsc::Receiver<super::QueuedServerEnvelope>>>,
mut used_rx: watch::Receiver<usiz
作用:真正把本地服务器事件写到 WebSocket。它会补发未确认消息、定时发 Ping(心跳包),并给新消息编号和分片。
数据流:进去是共享 WebSocket 状态、事件接收队列、缓冲区使用量、WebSocket 写端、ping 间隔和关闭令牌 → 它先重发待确认消息,再循环等待新事件或心跳时间;新事件会被包装成 ServerEnvelope、必要时切片、序列化成 JSON 并发出,同时记入待确认缓冲区 → 出来是成功停止或 IO 错误。
调用关系:由 run_server_writer 调用;它和读线程配合,读线程收到 Ack 后释放它的缓冲区容量。
调用图:调用 1 个内部函数(split_server_envelope_for_transport);被 2 处调用(run_server_writer_inner_assigns_contiguous_seq_ids_per_stream, run_server_writer_inner_sends_periodic_ping_frames);外部调用 8 个(set_missed_tick_behavior, with_capacity, error!, borrow, to_string, select!, now, interval_at)。
RemoteControlWebsocket::run_websocket_reader1066–1086 ↗
async fn run_websocket_reader(
client_tracker: Arc<Mutex<ClientTracker>>,
state: Arc<Mutex<WebsocketState>>,
websocket_reader: SplitStream<WebSocketStream<MaybeTlsStream<TcpStr
作用:包装 WebSocket 读取循环,负责记录读取端停止或出错。
数据流:进去是客户端追踪器、共享状态、WebSocket 读端、pong 超时和关闭令牌 → 它调用 run_websocket_reader_inner 做实际读取 → 出来没有返回值,只写日志。
调用关系:RemoteControlWebsocket::run_connection 把它作为读取工人启动。
调用图:外部调用 2 个(run_websocket_reader_inner, warn!)。
RemoteControlWebsocket::run_websocket_reader_inner1092–1235 ↗
async fn run_websocket_reader_inner(
client_tracker: Arc<Mutex<ClientTracker>>,
state: Arc<Mutex<WebsocketState>>,
mut websocket_reader: SplitStream<WebSocketStream<MaybeTlsStr
作用:真正读取远端发来的消息。它处理 Pong 心跳回复、客户端消息、客户端清理和超时断线。
数据流:进去是 ClientTracker、WebSocket 状态、读端、Pong 超时和关闭令牌 → 它循环等待网络消息、心跳超时、客户端任务结束或空闲清理;文本消息会解析成 ClientEnvelope,经过分片检查后交给 ClientTracker;成功后更新游标、Ack 和清理状态 → 出来是成功停止或 IO 错误。
调用关系:由 run_websocket_reader 调用;它是远端指令进入本地系统的入口,并与 WebsocketState 和 ClientTracker 紧密配合。
调用图:调用 1 个内部函数(client_message_key);被 1 处调用(run_websocket_reader_inner_times_out_without_pong_frames);外部调用 9 个(new, format!, matches!, pin!, select!, now, interval, sleep, warn!)。
set_remote_control_header1238–1251 ↗
fn set_remote_control_header(
headers: &mut tungstenite::http::HeaderMap,
name: &'static str,
value: &str,
) -> io::Result<()>
作用:安全地往 WebSocket HTTP 请求里加一个远程控制需要的请求头。
数据流:进去是请求头表、头名和字符串值 → 它先把字符串转换成合法 HeaderValue;失败就变成输入错误,成功就插入头表 → 出来是 Ok 或错误。
调用关系:build_remote_control_websocket_request 用它逐个设置服务器 ID、名称、协议版本、授权令牌等头。
调用图:被 1 处调用(build_remote_control_websocket_request);外部调用 2 个(insert, from_str)。
build_remote_control_websocket_request1253–1301 ↗
fn build_remote_control_websocket_request(
websocket_url: &str,
enrollment: &RemoteControlEnrollment,
installation_id: &str,
subscribe_cursor: Option<&str>,
) -> io::Result<tungstenite
作用:构造连接远程控制 WebSocket 所需的 HTTP 请求,带上登记信息和授权信息。
数据流:进去是 WebSocket URL、登记记录、安装 ID 和可选订阅游标 → 它把 URL 变成请求,加入服务器 ID、base64 编码的服务器名、协议版本、Bearer 令牌、安装 ID 和游标 → 出来是可用于握手的请求,或错误。
调用关系:connect_remote_control_websocket 在真正 connect_async 前调用它。
调用图:调用 1 个内部函数(set_remote_control_header);被 1 处调用(connect_remote_control_websocket);外部调用 1 个(format!)。
next_reconnect_delay1303–1312 ↗
fn next_reconnect_delay(reconnect_attempt: &mut u64) -> (std::time::Duration, bool)
作用:计算下一次重连前要等多久,避免失败时高频打爆远端服务。
数据流:进去是当前重连次数的可变引用 → 它用 backoff 算等待时间,并限制最大 30 秒;如果到达上限就重置次数 → 出来是等待时长和是否重置的标记,同时更新次数。
调用关系:RemoteControlWebsocket::connect 在连接失败后调用它;测试验证达到上限后会重新从短等待开始。
调用图:调用 1 个内部函数(backoff);被 2 处调用(connect, next_reconnect_delay_resets_after_cap)。
connect_remote_control_websocket1314–1430 ↗
async fn connect_remote_control_websocket(
remote_control_target: &RemoteControlTarget,
state_db: Option<&StateRuntime>,
mut auth_context: RemoteControlAuthContext<'_>,
current_enrollm
作用:完成一次底层 WebSocket 握手:先准备登记和授权,再发起连接,并处理常见 HTTP 错误。
数据流:进去是远程目标、状态数据库、鉴权上下文、当前登记、连接选项和状态发布器 → 它确保 TLS 加密库可用,准备或刷新登记,构造请求,限时连接;如果 404/401/403 等错误出现,会按情况替换登记或清掉令牌 → 出来是 WebSocket 流和响应头,或带详细原因的 IO 错误。
调用关系:RemoteControlWebsocket::connect 调用它;它再调 prepare_remote_control_enrollment、build_remote_control_websocket_request 和若干错误修复函数。
调用图:调用 6 个内部函数(build_remote_control_websocket_request, clear_remote_control_server_token_if_matches, format_remote_control_websocket_connect_error, prepare_remote_control_enrollment, replace_remote_control_enrollment_if_matches, websocket_response_reports_missing_remote_app_server);被 6 处调用(connect_remote_control_websocket_includes_http_error_details, connect_remote_control_websocket_invalidates_unauthorized_server_token, connect_remote_control_websocket_recovers_after_unauthorized_enrollment, connect_remote_control_websocket_recovers_after_unauthorized_refresh, connect_remote_control_websocket_requires_chatgpt_auth, connect_remote_control_websocket_requires_sqlite_state_db);外部调用 10 个(as_ref, lock, other, ensure_rustls_crypto_provider, format!, info!, matches!, timeout, connect_async, warn!)。
prepare_remote_control_enrollment1432–1586 ↗
async fn prepare_remote_control_enrollment(
remote_control_target: &RemoteControlTarget,
state_db: Option<&StateRuntime>,
auth_context: &mut RemoteControlAuthContext<'_>,
enrollment: &
作用:确保本机有一份可用于远程控制连接的“登记证”。没有就创建,快过期就刷新,账号变了就重新判断。
数据流:进去是远程目标、状态库、鉴权上下文、可变登记、连接选项和状态发布器 → 它加载登录信息,检查账号是否匹配,读取本地持久化登记,必要时新建或刷新服务器令牌,并发布环境 ID → 出来是可用于连接的认证信息,登记也会被更新。
调用关系:connect_remote_control_websocket 在握手前调用它;它会调用 enroll_and_persist_remote_control_server、refresh_remote_control_server 和 resolve_desired_state_after_account_change。
调用图:调用 7 个内部函数(load_remote_control_auth, recover_remote_control_auth, load_persisted_remote_control_enrollment, refresh_remote_control_server, publish_environment_id, enroll_and_persist_remote_control_server, resolve_desired_state_after_account_change);被 1 处调用(connect_remote_control_websocket);外部调用 5 个(clone, new, other, format!, info!)。
resolve_desired_state_after_account_change1588–1631 ↗
async fn resolve_desired_state_after_account_change(
state_db: &StateRuntime,
remote_control_target: &RemoteControlTarget,
auth_manager: &Arc<AuthManager>,
account_id: &str,
connec
作用:用户切换账号后,重新确认远程控制是否仍应保持启用。
数据流:进去是状态库、远程目标、登录管理器、新账号 ID 和连接选项 → 如果当前是“持久启用”,它加锁读取该账号的登记记录,再确认账号没又变,最后把 desired_state 改成根据记录推导出的状态 → 出来是 Ok 或错误。
调用关系:prepare_remote_control_enrollment 在发现内存登记账号和当前登录账号不一致时调用它。
调用图:调用 3 个内部函数(load_remote_control_auth, acquire_persistence_lock, desired_state_from_persisted_enrollment);被 1 处调用(prepare_remote_control_enrollment);外部调用 2 个(new, get_remote_control_enrollment)。
websocket_response_reports_missing_remote_app_server1633–1643 ↗
fn websocket_response_reports_missing_remote_app_server(
response: &tungstenite::http::Response<Option<Vec<u8>>>,
) -> bool
作用:判断一个 404 响应是不是明确表示“远端那台 app server 不存在”。
数据流:进去是 HTTP 响应 → 它要求状态码是 404,并尝试把响应体当 JSON 读出 detail 字段;只有 detail 精确匹配指定文字才返回 true → 出来是布尔值。
调用关系:connect_remote_control_websocket 用它区分“登记过期该替换”和普通 404 错误。
调用图:被 1 处调用(connect_remote_control_websocket);外部调用 2 个(body, status)。
replace_remote_control_enrollment_if_matches1645–1677 ↗
async fn replace_remote_control_enrollment_if_matches(
state_db: Option<&StateRuntime>,
remote_control_target: &RemoteControlTarget,
auth_context: RemoteControlEnrollmentAuthContext<'_, '_
作用:如果当前内存登记正好就是出错的那份,就重新登记并保存;如果已经被别人更新过,就不乱动。
数据流:进去是状态库、远程目标、鉴权上下文、当前登记、旧登记、连接选项和状态发布器 → 它加锁检查当前登记是否和旧登记相同;相同就用 ReplaceExisting 创建新登记并持久化 → 出来是 Ok 或错误。
调用关系:connect_remote_control_websocket 在远端明确说服务器不存在时调用它,避免继续使用失效登记。
调用图:调用 1 个内部函数(enroll_and_persist_remote_control_server);被 1 处调用(connect_remote_control_websocket);外部调用 3 个(as_ref, lock, new)。
clear_remote_control_server_token_if_matches1679–1692 ↗
async fn clear_remote_control_server_token_if_matches(
current_enrollment: &CurrentRemoteControlEnrollment,
enrollment: &RemoteControlEnrollment,
) -> io::Result<()>
作用:当 WebSocket 握手返回未授权时,清掉当前登记里的服务器令牌,让下次连接去刷新。
数据流:进去是当前登记和出错时使用的登记 → 它加锁确认二者仍是同一份登记,然后调用 clear_server_token → 出来是 Ok;如果找不到匹配登记则报错。
调用关系:connect_remote_control_websocket 在收到 HTTP 401 或 403 时调用它。
调用图:被 1 处调用(connect_remote_control_websocket);外部调用 2 个(as_mut, lock)。
enroll_and_persist_remote_control_server1694–1784 ↗
async fn enroll_and_persist_remote_control_server(
remote_control_target: &RemoteControlTarget,
state_db: &StateRuntime,
auth_context: RemoteControlEnrollmentAuthContext<'_, '_>,
enrol
作用:向远程控制服务登记这台本地服务器,并把登记结果写进本地数据库。
数据流:进去是远程目标、状态库、鉴权上下文、可变登记、连接选项、状态发布器和选择策略 → 如果允许复用且已有登记就直接返回;否则检查仍启用,调用远端登记接口,拿锁后把新登记和用户偏好保存到数据库,发布环境 ID,并更新内存登记 → 出来是 Ok 或错误。
调用关系:prepare_remote_control_enrollment 用它创建缺失登记;replace_remote_control_enrollment_if_matches 用它替换过期登记。
调用图:调用 5 个内部函数(recover_remote_control_auth, acquire_persistence_lock, enroll_remote_control_server, update_persisted_remote_control_enrollment, publish_environment_id);被 2 处调用(prepare_remote_control_enrollment, replace_remote_control_enrollment_if_matches);外部调用 4 个(new, other, format!, info!)。
format_remote_control_websocket_connect_error1786–1805 ↗
fn format_remote_control_websocket_connect_error(
websocket_url: &str,
err: &tungstenite::Error,
) -> String
作用:把 WebSocket 连接错误整理成更有用的人类可读文本,尤其补上 HTTP 响应头和响应体摘要。
数据流:进去是 WebSocket URL 和 tungstenite 错误 → 它先写基础错误;如果错误里有 HTTP 响应,就追加格式化后的头和 body 预览 → 出来是一段错误字符串。
调用关系:connect_remote_control_websocket 在底层连接失败后调用它,保证日志和返回错误包含足够排查信息。
调用图:调用 1 个内部函数(preview_remote_control_response_body);被 1 处调用(connect_remote_control_websocket);外部调用 1 个(format!)。
tests::remote_control_enrollment1853–1865 ↗
fn remote_control_enrollment(remote_control_token: Option<&str>) -> RemoteControlEnrollment
作用:测试用:快速造一份远程控制登记记录,可选择是否带服务器令牌。
数据流:进去是可选令牌 → 它填入固定账号、环境、服务器 ID、服务器名和过期时间,并规范化测试 URL → 出来是一份 RemoteControlEnrollment。
调用关系:多个连接和缓冲区测试用它准备当前登记。
调用图:调用 1 个内部函数(normalize_remote_control_url)。
tests::test_current_enrollment1867–1871 ↗
fn test_current_enrollment(
enrollment: Option<RemoteControlEnrollment>,
) -> CurrentRemoteControlEnrollment
作用:测试用:把一份可选登记包装成线程安全的当前登记状态。
数据流:进去是 Option<RemoteControlEnrollment> → 它创建 RemoteControlEnrollmentState 并放进 Arc(引用计数共享指针) → 出来是 CurrentRemoteControlEnrollment。
调用关系:连接相关测试用它模拟运行时共享的登记状态。
tests::next_reconnect_delay_resets_after_cap1874–1891 ↗
fn next_reconnect_delay_resets_after_cap()
作用:测试重连退避到达最大等待时间后会重置。
数据流:进去没有外部输入 → 它把重连次数设到较大值,调用 next_reconnect_delay 两次,检查第一次达到上限并重置,第二次回到短等待 → 出来是测试通过或断言失败。
调用关系:直接覆盖 next_reconnect_delay 的边界行为。
调用图:调用 1 个内部函数(next_reconnect_delay);外部调用 2 个(assert!, assert_eq!)。
tests::websocket_404_only_reports_explicit_missing_remote_app_server1894–1931 ↗
fn websocket_404_only_reports_explicit_missing_remote_app_server()
作用:测试只有特定格式的 404 响应才会被当成“远端 app server 不存在”。
数据流:进去是一组内置响应体案例 → 它构造 HTTP 响应并调用 websocket_response_reports_missing_remote_app_server → 出来是每个案例的断言结果。
调用关系:保护 connect_remote_control_websocket 的错误分支,避免普通 404 误删登记。
调用图:外部调用 4 个(new, assert!, assert_eq!, builder)。
tests::remote_control_status_channel1933–1944 ↗
fn remote_control_status_channel() -> (
RemoteControlStatusPublisher,
watch::Receiver<RemoteControlStatusChangedNotification>,
)
作用:测试用:创建一对状态发布器和状态接收器。
数据流:进去没有参数 → 它用固定服务器名、安装 ID 和 Connecting 初始状态创建 watch 通道 → 出来是 RemoteControlStatusPublisher 和接收器。
调用关系:状态发布和连接测试都用它观察状态变化。
tests::enabled_desired_state_sender1946–1951 ↗
fn enabled_desired_state_sender() -> watch::Sender<RemoteControlDesiredState>
作用:测试用:创建一个初始就是启用状态的 desired_state 发送端。
数据流:进去没有参数 → 它创建 watch 通道,值为 Enabled 且没有持久化偏好 → 出来是发送端。
调用关系:连接函数测试用它表示远程控制当前允许连接。
调用图:外部调用 1 个(channel)。
tests::mark_recovery_auth_change_seen_marks_only_recovery_revision_seen1954–1966 ↗
fn mark_recovery_auth_change_seen_marks_only_recovery_revision_seen()
作用:测试鉴权恢复只会标记自己触发的那次登录变化已处理。
数据流:进去没有外部输入 → 它创建修订号通道,模拟一次变化,调用 mark_recovery_auth_change_seen,再检查接收器没有待处理变化 → 出来是断言结果。
调用关系:虽然函数来自 auth 模块,但这里验证它和 WebSocket 重连监听配合不会误触发。
调用图:调用 1 个内部函数(mark_recovery_auth_change_seen);外部调用 2 个(assert!, channel)。
tests::mark_recovery_auth_change_seen_preserves_racing_auth_change1969–1982 ↗
fn mark_recovery_auth_change_seen_preserves_racing_auth_change()
作用:测试如果恢复期间又发生新的登录变化,不能被误吞掉。
数据流:进去没有外部输入 → 它模拟两次修订号变化,标记恢复前那次已见,再检查仍有一次变化待处理 → 出来是断言结果。
调用关系:保障 connect 的 auth_change_rx 逻辑不会漏掉真正的账号或令牌更新。
调用图:调用 1 个内部函数(mark_recovery_auth_change_seen);外部调用 2 个(assert!, channel)。
tests::remote_control_state_runtime1984–1988 ↗
tests::remote_control_auth_manager1990–1992 ↗
fn remote_control_auth_manager() -> Arc<AuthManager>
作用:测试用:创建一个带假 ChatGPT 登录信息的 AuthManager。
数据流:进去没有参数 → 它生成测试认证对象并包装成 AuthManager → 出来是共享登录管理器。
调用关系:大多数连接测试用它绕过真实登录流程。
调用图:调用 2 个内部函数(auth_manager_from_auth, create_dummy_chatgpt_auth_for_testing)。
tests::remote_control_url_for_listener1994–1999 ↗
fn remote_control_url_for_listener(listener: &TcpListener) -> String
作用:测试用:根据本地 TCP 监听器地址拼出远程控制基础 URL。
数据流:进去是 TcpListener → 它读取本地地址并拼成 http://地址/backend-api/ → 出来是 URL 字符串。
调用关系:本地假服务器测试用它让客户端连到测试监听器。
调用图:外部调用 2 个(local_addr, format!)。
tests::remote_control_auth_dot_json2001–2039 ↗
fn remote_control_auth_dot_json(access_token: &str) -> AuthDotJson
作用:测试用:构造一份像真实 auth.json 的登录文件内容,里面带指定 access token。
数据流:进去是 access token 字符串 → 它造一个无签名假 JWT,解析出账号声明,并填入 AuthDotJson → 出来是可保存的认证文件对象。
调用关系:未授权后恢复登录的测试用它先保存旧 token,再保存新 token。
调用图:调用 1 个内部函数(parse_chatgpt_jwt_claims);外部调用 4 个(now, format!, json!, to_vec)。
tests::connect_remote_control_websocket_includes_http_error_details2042–2114 ↗
async fn connect_remote_control_websocket_includes_http_error_details()
作用:测试 WebSocket 连接遇到 HTTP 错误时,错误信息包含状态、关键头和响应体摘要。
数据流:进去没有外部输入 → 它启动本地假 HTTP 服务返回 503,调用 connect_remote_control_websocket,然后检查错误字符串和状态 → 出来是断言结果。
调用关系:覆盖 format_remote_control_websocket_connect_error 被连接函数使用的路径。
调用图:调用 2 个内部函数(normalize_remote_control_url, connect_remote_control_websocket);外部调用 17 个(new, bind, new, accept_http_request, enabled_desired_state_sender, remote_control_auth_manager, remote_control_enrollment, remote_control_state_runtime, remote_control_status_channel, remote_control_url_for_listener (+7 more))。
tests::connect_remote_control_websocket_invalidates_unauthorized_server_token2117–2182 ↗
async fn connect_remote_control_websocket_invalidates_unauthorized_server_token()
作用:测试 WebSocket 握手返回 401 时,会清掉登记里的服务器令牌。
数据流:进去没有外部输入 → 它让假服务器返回 401,执行连接,检查错误文案、状态和当前登记中的令牌已变成 None → 出来是断言结果。
调用关系:验证 connect_remote_control_websocket 调用 clear_remote_control_server_token_if_matches。
调用图:调用 2 个内部函数(normalize_remote_control_url, connect_remote_control_websocket);外部调用 14 个(new, bind, new, accept_http_request, enabled_desired_state_sender, remote_control_auth_manager, remote_control_enrollment, remote_control_state_runtime, remote_control_status_channel, remote_control_url_for_listener (+4 more))。
tests::connect_remote_control_websocket_recovers_after_unauthorized_enrollment2185–2279 ↗
async fn connect_remote_control_websocket_recovers_after_unauthorized_enrollment()
作用:测试登记接口返回 401 时,会尝试恢复登录,并要求外层之后重试。
数据流:进去没有外部输入 → 它先保存旧 token,假服务器让 enroll 返回 401,再保存新 token,执行连接并检查错误提示和 AuthManager 已读到新 token → 出来是断言结果。
调用关系:覆盖 enroll_and_persist_remote_control_server 中 recover_remote_control_auth 的路径。
调用图:调用 4 个内部函数(normalize_remote_control_url, connect_remote_control_websocket, default, shared);外部调用 15 个(new, bind, new, accept_http_request, enabled_desired_state_sender, remote_control_auth_dot_json, remote_control_state_runtime, remote_control_status_channel, remote_control_url_for_listener, respond_with_status_and_headers (+5 more))。
tests::connect_remote_control_websocket_recovers_after_unauthorized_refresh2282–2382 ↗
async fn connect_remote_control_websocket_recovers_after_unauthorized_refresh()
作用:测试刷新服务器令牌返回 401 时,也会恢复登录并让连接重试。
数据流:进去没有外部输入 → 它准备一份需要刷新的登记,假服务器让 refresh 返回 401,连接失败后检查新 token 生效且恢复产生的变更不会额外唤醒重连 → 出来是断言结果。
调用关系:覆盖 prepare_remote_control_enrollment 中 refresh_remote_control_server 的未授权恢复路径。
调用图:调用 4 个内部函数(normalize_remote_control_url, connect_remote_control_websocket, default, shared);外部调用 16 个(new, bind, new, accept_http_request, enabled_desired_state_sender, remote_control_auth_dot_json, remote_control_enrollment, remote_control_state_runtime, remote_control_status_channel, remote_control_url_for_listener (+6 more))。
tests::connect_remote_control_websocket_requires_sqlite_state_db2385–2421 ↗
async fn connect_remote_control_websocket_requires_sqlite_state_db()
作用:测试没有 SQLite 状态数据库时,远程控制连接会明确失败。
数据流:进去没有外部输入 → 它传入 None 作为 state_db 调用 connect_remote_control_websocket → 出来是 NotFound 错误断言,并确认当前登记被清空。
调用关系:验证 prepare_remote_control_enrollment 对持久化数据库的硬性要求。
调用图:调用 2 个内部函数(normalize_remote_control_url, connect_remote_control_websocket);外部调用 7 个(new, enabled_desired_state_sender, remote_control_auth_manager, remote_control_enrollment, remote_control_status_channel, test_current_enrollment, assert_eq!)。
tests::connect_remote_control_websocket_requires_chatgpt_auth2424–2486 ↗
async fn connect_remote_control_websocket_requires_chatgpt_auth()
作用:测试没有 ChatGPT 登录时,远程控制连接会拒绝继续。
数据流:进去没有外部输入 → 它创建空认证环境,调用连接函数,检查 PermissionDenied、错误文案、登记清空和环境 ID 清空 → 出来是断言结果。
调用关系:验证 prepare_remote_control_enrollment 处理 load_remote_control_auth 失败的行为。
调用图:调用 4 个内部函数(normalize_remote_control_url, connect_remote_control_websocket, default, shared);外部调用 8 个(new, new, enabled_desired_state_sender, remote_control_enrollment, remote_control_state_runtime, remote_control_status_channel, test_current_enrollment, assert_eq!)。
tests::run_remote_control_websocket_loop_shutdown_cancels_reconnect_backoff2489–2540 ↗
async fn run_remote_control_websocket_loop_shutdown_cancels_reconnect_backoff()
作用:测试主循环在等待重连退避时收到关闭信号,会立刻退出。
数据流:进去没有外部输入 → 它创建连不上的地址,启动 RemoteControlWebsocket::run,短暂等待后取消 shutdown_token,并要求任务很快结束 → 出来是断言结果。
调用关系:覆盖 RemoteControlWebsocket::run 和 connect 的关闭响应能力。
调用图:调用 2 个内部函数(normalize_remote_control_url, new);外部调用 14 个(new, new, from_millis, new, bind, remote_control_auth_manager, remote_control_status_channel, remote_control_url_for_listener, test_current_enrollment, channel (+4 more))。
tests::publish_status_if_changed_sends_only_status_changes2543–2627 ↗
async fn publish_status_if_changed_sends_only_status_changes()
作用:测试状态发布器只在内容真的变化时通知订阅者。
数据流:进去没有外部输入 → 它反复发布相同或不同的环境 ID 和连接状态,检查接收器是否按预期被唤醒 → 出来是断言结果。
调用关系:直接验证 RemoteControlStatusPublisher::publish_status 和 publish_environment_id。
调用图:外部调用 3 个(remote_control_status_channel, assert!, assert_eq!)。
tests::run_server_writer_inner_sends_periodic_ping_frames2630–2665 ↗
async fn run_server_writer_inner_sends_periodic_ping_frames()
作用:测试写入循环会定时发送 Ping 心跳帧。
数据流:进去没有外部输入 → 它建立一对本地 WebSocket,启动 run_server_writer_inner,等待服务端收到 Ping,然后关闭任务 → 出来是断言结果。
调用关系:覆盖 RemoteControlWebsocket::run_server_writer_inner 的心跳路径。
调用图:调用 2 个内部函数(new, run_server_writer_inner);外部调用 12 个(new, new, from_millis, from_secs, new, new, default, connected_websocket_pair, assert!, channel (+2 more))。
tests::join_connection_workers_aborts_stuck_worker_after_timeout2668–2676 ↗
async fn join_connection_workers_aborts_stuck_worker_after_timeout()
作用:测试连接收尾时,卡住不退出的任务会被超时强制取消。
数据流:进去没有外部输入 → 它创建一个永远 pending 的任务放进 JoinSet,调用 join_connection_workers,并检查集合为空 → 出来是断言结果。
调用关系:直接验证 RemoteControlWebsocket::join_connection_workers。
调用图:调用 1 个内部函数(join_connection_workers);外部调用 3 个(from_millis, assert!, new)。
tests::run_server_writer_inner_assigns_contiguous_seq_ids_per_stream2679–2755 ↗
async fn run_server_writer_inner_assigns_contiguous_seq_ids_per_stream()
作用:测试写入循环会给每个客户端流分别分配连续序号。
数据流:进去没有外部输入 → 它发送同一客户端两条不同流的三个事件,读取 WebSocket 文本消息,检查 stream-1 序号是 1、2,stream-2 从 1 开始 → 出来是断言结果。
调用关系:覆盖 RemoteControlWebsocket::run_server_writer_inner 中 next_seq_id_by_stream 的逻辑。
调用图:调用 2 个内部函数(new, run_server_writer_inner);外部调用 12 个(new, new, from_secs, new, new, new, new, default, connected_websocket_pair, assert_eq! (+2 more))。
tests::run_websocket_reader_inner_times_out_without_pong_frames2758–2795 ↗
async fn run_websocket_reader_inner_times_out_without_pong_frames()
作用:测试读取循环如果一直收不到 Pong 心跳回复,会超时报错。
数据流:进去没有外部输入 → 它建立 WebSocket 读端但不发送 Pong,启动 run_websocket_reader_inner,等待它返回 TimedOut 错误 → 出来是断言结果。
调用关系:覆盖 RemoteControlWebsocket::run_websocket_reader_inner 的心跳超时保护。
调用图:调用 3 个内部函数(new, new, run_websocket_reader_inner);外部调用 11 个(new, new, from_millis, from_secs, new, new, default, connected_websocket_pair, assert_eq!, channel (+1 more))。
tests::outbound_buffer_acks_by_stream_id2798–2843 ↗
fn outbound_buffer_acks_by_stream_id()
作用:测试 Ack 只会清理同一个客户端同一条流的消息,不会误删别的流或别的客户端。
数据流:进去没有外部输入 → 它插入三条不同客户端/流的消息,对其中一条流确认,再检查剩余消息和计数 → 出来是断言结果。
调用关系:直接验证 BoundedOutboundBuffer::ack 的分组边界。
调用图:调用 1 个内部函数(new);外部调用 4 个(new, new, server_envelope, assert_eq!)。
tests::outbound_buffer_retains_unacked_messages_until_ack_advances2846–2888 ↗
fn outbound_buffer_retains_unacked_messages_until_ack_advances()
作用:测试未被确认到的位置之前,缓冲区会保留消息。
数据流:进去没有外部输入 → 它插入多条消息,只确认某个流的较早序号,然后检查其他未确认消息仍存在 → 出来是断言结果。
调用关系:直接验证 BoundedOutboundBuffer 的确认推进规则。
调用图:调用 1 个内部函数(new);外部调用 4 个(new, new, server_envelope, assert_eq!)。
tests::outbound_buffer_advances_segmented_acks_by_wire_cursor2891–2916 ↗
fn outbound_buffer_advances_segmented_acks_by_wire_cursor()
作用:测试带分片编号的 Ack 能精确推进到某个分片位置。
数据流:进去没有外部输入 → 它插入同一序号的两个分片,确认到分片 1,检查两个分片都被删除、计数归零 → 出来是断言结果。
调用关系:覆盖 BoundedOutboundBuffer::ack 对分片消息的处理。
调用图:调用 1 个内部函数(new);外部调用 4 个(new, new, server_chunk_envelope, assert_eq!)。
tests::outbound_buffer_treats_segmentless_acks_as_seq_level_acks2919–2941 ↗
fn outbound_buffer_treats_segmentless_acks_as_seq_level_acks()
作用:测试不带分片编号的 Ack 会被当成整个序号都确认。
数据流:进去没有外部输入 → 它插入同一序号的两个分片,发送只带序号的确认,检查全部清空 → 出来是断言结果。
调用关系:保护 BoundedOutboundBuffer::ack 的兼容行为。
调用图:调用 1 个内部函数(new);外部调用 4 个(new, new, server_chunk_envelope, assert_eq!)。
tests::websocket_state_drops_duplicate_client_chunks_while_pending2944–2978 ↗
fn websocket_state_drops_duplicate_client_chunks_while_pending()
作用:测试客户端分片还没拼完时,重复分片会被丢掉,并且之后可重新开始有效拼接。
数据流:进去没有外部输入 → 它构造两个分片,先观察第一个为 Pending,再重复第一个和发送第二个,检查重复/受影响分片被丢弃,之后第一个还能重新 Pending → 出来是断言结果。
调用关系:验证 WebsocketState::observe_client_message 和 ClientSegmentReassembler 的重复处理。
调用图:调用 1 个内部函数(new);外部调用 4 个(new, default, client_chunk_envelope, assert!)。
tests::websocket_state_drops_replayed_client_chunks_after_completion2981–3037 ↗
fn websocket_state_drops_replayed_client_chunks_after_completion()
作用:测试一个分片消息已经完整交付后,旧分片重放会被丢弃。
数据流:进去没有外部输入 → 它拼完一条分片消息并调用 record_client_message_delivery 记录完成,再重放第一片,检查被 Dropped → 出来是断言结果。
调用关系:覆盖 WebsocketState 的完成序号记录。
调用图:调用 1 个内部函数(new);外部调用 10 个(new, Notification, new, new, default, client_chunk_envelope, observe_client_message, assert!, panic!, to_vec)。
tests::websocket_state_allows_replay_before_completed_chunk_delivery3040–3086 ↗
fn websocket_state_allows_replay_before_completed_chunk_delivery()
作用:测试消息虽然拼完整了,但还没记录为已交付前,允许重放重新开始。
数据流:进去没有外部输入 → 它拼出完整消息但不调用 record_client_message_delivery,然后重放第一片,检查仍可 Pending → 出来是断言结果。
调用关系:确保 WebsocketState 只在真正交付后才永久挡掉旧分片。
调用图:调用 1 个内部函数(new);外部调用 6 个(new, Notification, default, client_chunk_envelope, assert!, to_vec)。
tests::websocket_state_allows_replay_after_rejected_out_of_order_chunk3089–3115 ↗
fn websocket_state_allows_replay_after_rejected_out_of_order_chunk()
作用:测试先收到乱序分片被拒后,后续正确的第一片仍能开始拼接。
数据流:进去没有外部输入 → 它先送第二片并检查 Dropped,再送第一片并检查 Pending → 出来是断言结果。
调用关系:验证 WebsocketState 对错误分片不会把整条流永久弄坏。
调用图:调用 1 个内部函数(new);外部调用 4 个(new, default, client_chunk_envelope, assert!)。
tests::websocket_state_allows_replay_after_later_chunk_drops3118–3148 ↗
fn websocket_state_allows_replay_after_later_chunk_drops()
作用:测试后续分片无效被丢弃后,可以重新发送第一片开始。
数据流:进去没有外部输入 → 它先送第一片 Pending,再送一个无效第二片 Dropped,最后重送第一片并检查 Pending → 出来是断言结果。
调用关系:覆盖分片重组失败后的恢复行为。
调用图:调用 1 个内部函数(new);外部调用 4 个(new, default, client_chunk_envelope, assert!)。
tests::websocket_state_drops_oversized_client_chunk_frames3151–3169 ↗
fn websocket_state_drops_oversized_client_chunk_frames()
作用:测试超过单个传输分片大小限制的客户端分片会被丢弃。
数据流:进去没有外部输入 → 它构造一条分片消息,用超过限制的 wire_size_bytes 调用 observe_client_message,检查 Dropped → 出来是断言结果。
调用关系:验证 WebsocketState::observe_client_message 的大小保护。
调用图:调用 1 个内部函数(new);外部调用 4 个(new, default, client_chunk_envelope, assert!)。
tests::websocket_state_ignores_oversized_stale_chunks_without_dropping_newer_assembly3172–3230 ↗
fn websocket_state_ignores_oversized_stale_chunks_without_dropping_newer_assembly()
作用:测试过大的旧序号分片不会破坏当前正在拼的新消息。
数据流:进去没有外部输入 → 它先开始拼序号 8,再送一个过大的序号 7 分片,最后送序号 8 的第二片,检查能成功 Forward → 出来是断言结果。
调用关系:保护 WebsocketState 对陈旧异常分片的处理顺序。
调用图:调用 1 个内部函数(new);外部调用 6 个(new, Notification, default, client_chunk_envelope, assert!, to_vec)。
tests::websocket_state_ignores_oversized_duplicate_chunks_without_dropping_current_assembly3233–3291 ↗
fn websocket_state_ignores_oversized_duplicate_chunks_without_dropping_current_assembly()
作用:测试过大的重复分片不会清掉当前正在拼的同序号消息。
数据流:进去没有外部输入 → 它先送第一片,随后送过大的重复第一片,再送第二片,检查完整消息仍能转发 → 出来是断言结果。
调用关系:验证 WebsocketState 在大小检查前先识别可忽略重复分片。
调用图:调用 1 个内部函数(new);外部调用 6 个(new, Notification, default, client_chunk_envelope, assert!, to_vec)。
tests::websocket_state_clears_chunk_cursor_when_stream_is_invalidated3294–3331 ↗
fn websocket_state_clears_chunk_cursor_when_stream_is_invalidated()
作用:测试流被废弃后,旧的分片进度会清空,低序号也能重新开始。
数据流:进去没有外部输入 → 它先让序号 4 的第一片 Pending,然后手动清理该流进度和重组器,再发送序号 1 的第一片并检查 Pending → 出来是断言结果。
调用关系:覆盖 WebsocketState::invalidate_client_message_stream 与分片重组器清理配合。
调用图:调用 1 个内部函数(new);外部调用 5 个(new, new, new, default, assert!)。
tests::server_envelope3333–3354 ↗
fn server_envelope(
client_id: &ClientId,
stream_id: &str,
seq_id: u64,
summary: &str,
) -> ServerEnvelope
作用:测试用:生成一条普通服务器消息信封。
数据流:进去是客户端 ID、流 ID、序号和摘要文字 → 它创建一个 ConfigWarning 通知并包装成 ServerEnvelope → 出来是服务器消息信封。
调用关系:出站缓冲区测试用它构造待确认消息。
调用图:外部调用 5 个(new, ConfigWarning, AppServerNotification, clone, new)。
tests::server_chunk_envelope3356–3373 ↗
tests::client_chunk_envelope3375–3396 ↗
fn client_chunk_envelope(
client_id: &str,
stream_id: &str,
seq_id: u64,
segment_id: usize,
segment_count: usize,
message_size_bytes: usize,
chu
作用:测试用:生成一条客户端分片消息信封。
数据流:进去是客户端、流、序号、分片信息、总大小和原始字节 → 它把字节用 base64 编码,并填入 ClientMessageChunk → 出来是 ClientEnvelope。
调用关系:WebsocketState 分片处理测试大量使用它。
tests::observe_client_message3398–3406 ↗
fn observe_client_message(
state: &mut WebsocketState,
envelope: ClientEnvelope,
) -> ClientSegmentObservation
作用:测试用:按真实网络大小计算方式调用 WebsocketState::observe_client_message。
数据流:进去是可变 WebsocketState 和客户端信封 → 它先把信封序列化成 JSON 算长度,再调用状态对象的 observe_client_message → 出来是 ClientSegmentObservation。
调用关系:多个 WebsocketState 测试用它避免手工填写 wire_size_bytes。
调用图:调用 1 个内部函数(observe_client_message);外部调用 1 个(to_vec)。
tests::accept_http_request3408–3435 ↗
tests::connected_websocket_pair3437–3463 ↗
async fn connected_websocket_pair() -> (
WebSocketStream<MaybeTlsStream<TcpStream>>,
WebSocketStream<TcpStream>,
)
作用:测试用:在本机创建一对已经握手完成的 WebSocket 连接。
数据流:进去没有参数 → 它绑定本地监听器,同时发起客户端连接并由服务端 accept_async 完成握手 → 出来是客户端 WebSocket 和服务端 WebSocket。
调用关系:写入循环和读取超时测试用它模拟真实网络连接。
调用图:外部调用 5 个(bind, format!, spawn, accept_async, connect_async)。
tests::read_server_text_event3465–3477 ↗
async fn read_server_text_event(
server_stream: &mut WebSocketStream<TcpStream>,
) -> serde_json::Value
作用:测试用:从服务端 WebSocket 读一条文本消息并解析成 JSON。
数据流:进去是服务端 WebSocket 可变引用 → 它限时读取下一条消息,要求必须是 Text,然后用 serde_json 解析 → 出来是 JSON 值。
调用关系:run_server_writer_inner_assigns_contiguous_seq_ids_per_stream 用它检查发送内容。
tests::respond_with_status_and_headers3479–3498 ↗
客户端门面与 TUI 会话层
这些文件公开共享的 app-server 客户端 API、其远程传输实现,以及构建在该客户端之上的更高层 TUI 会话封装。
app-server-client/src/lib.rs源码 ↗
这个文件像一个总接线员。界面层不直接碰底层 app-server,而是通过这里发“请求”、发“通知”、收“服务器事件”。它最重要的价值是把复杂情况包起来:启动内嵌服务器、做初始化握手、把返回值解成调用者想要的类型、处理服务器反过来问客户端的问题、在退出时尽量优雅关闭。它还特别处理“消息太多来不及看”的情况:普通进度消息可以丢,并告诉客户端漏了多少;但助手正文、完成信号这类关键消息不能丢,否则界面会显示坏掉或一直等不到结束。文件底部有大量测试,用假的本地客户端和 WebSocket 服务器确认这些行为都可靠。
migrate_personality_if_needed98–110 ↗
async fn migrate_personality_if_needed(
codex_home: &Path,
config_toml: &ConfigToml,
state_db: Option<StateDbHandle>,
) -> IoResult<bool>
作用:检查是否需要把旧的“personality”配置迁移到新格式。迁移真的改了配置时,它会告诉调用者应该重新加载配置。
数据流:输入是 codex 主目录、当前配置文件内容、可选的状态数据库句柄 → 它交给底层迁移函数检查和执行 → 输出布尔值:true 表示配置被改过,false 表示跳过;如果读写失败就返回 IO 错误。
调用关系:它是启动前后的辅助步骤,自己不做迁移细节,只调用 maybe_migrate_personality,然后把多种“跳过原因”统一翻译成 false。
调用图:调用 1 个内部函数(maybe_migrate_personality)。
AppServerEvent::from128–136 ↗
fn from(value: InProcessServerEvent) -> Self
作用:把内嵌服务器原始事件转换成这个客户端对外统一使用的事件类型。这样外面不需要知道事件来自哪种底层实现。
数据流:输入一个 InProcessServerEvent → 按类型拆开:延迟标记、服务器通知、服务器请求 → 输出对应的 AppServerEvent,不改动原始内容。
调用关系:AppServerClient::next_event 在内嵌模式下会用它,把底层事件变成统一事件,再交给调用者。
调用图:外部调用 2 个(ServerNotification, ServerRequest)。
event_requires_delivery139–150 ↗
fn event_requires_delivery(event: &InProcessServerEvent) -> bool
作用:判断一个内嵌服务器事件是不是“绝对不能丢”。比如助手正在输出的正文和完成信号,一旦丢了界面就可能乱掉。
数据流:输入一个服务器事件 → 如果它是通知,就继续检查通知种类;其他事件默认不属于必须送达 → 输出 true 或 false。
调用关系:forward_in_process_event 每次转发事件前都会问它:该阻塞等待送达,还是可以在队列满时丢掉。
调用图:调用 1 个内部函数(server_notification_requires_delivery);被 1 处调用(forward_in_process_event)。
server_notification_requires_delivery163–175 ↗
fn server_notification_requires_delivery(notification: &ServerNotification) -> bool
作用:列出哪些服务器通知必须可靠送到客户端。它是“哪些消息不能丢”的统一规则表。
数据流:输入一个 ServerNotification → 和一组关键通知类型做匹配 → 输出 true 表示必须送达,false 表示可以尽力而为。
调用关系:event_requires_delivery 调用它;内嵌和远程传输都依赖这套分类,避免两边规则不一致。
调用图:被 1 处调用(event_requires_delivery);外部调用 1 个(matches!)。
forward_in_process_event196–262 ↗
async fn forward_in_process_event(
event_tx: &mpsc::Sender<InProcessServerEvent>,
skipped_events: &mut usize,
event: InProcessServerEvent,
mut reject_server_request: F,
) -> ForwardEve
作用:把内嵌服务器事件送进客户端事件队列,并在队列满时做取舍。关键消息会等到能送进去,普通消息可能被丢,并记录漏掉数量。
数据流:输入事件发送通道、已跳过数量、一个新事件、以及“拒绝服务器请求”的回调 → 它先尝试补发 Lagged 漏消息标记,再根据事件重要性选择等待发送或快速尝试发送 → 输出 Continue 或 DisableStream,同时可能增加 skipped_events,或拒绝被丢掉的服务器请求。
调用关系:InProcessAppServerClient::start 创建的后台 worker 在收到服务器事件后会用它;测试 forward_in_process_event_preserves_transcript_notifications_under_backpressure 专门验证它不会丢关键正文事件。
调用图:调用 1 个内部函数(event_requires_delivery);被 1 处调用(forward_in_process_event_preserves_transcript_notifications_under_backpressure);外部调用 3 个(send, try_send, warn!)。
TypedRequestError::fmt286–306 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
作用:把 typed request 的错误写成人能看懂的文字。它会区分是连接/通道问题、服务器返回错误,还是返回内容解码失败。
数据流:输入一个 TypedRequestError 和格式化器 → 根据错误种类拼出包含方法名、错误消息、错误码等信息的文本 → 输出格式化结果。
调用关系:当调用者打印或记录 TypedRequestError 时自动使用它;测试 typed_request_error_exposes_sources 会检查这些文字是否包含关键信息。
调用图:外部调用 1 个(write!)。
TypedRequestError::source310–316 ↗
fn source(&self) -> Option<&(dyn Error + 'static)>
作用:告诉 Rust 错误系统,这个错误背后有没有更底层的原始错误。这样日志或调试工具可以继续追到根因。
数据流:输入一个 TypedRequestError → 对传输错误和解码错误返回内部错误引用,对服务器 JSON-RPC 错误不返回底层 source → 输出可选的错误来源。
调用关系:这是标准 Error trait 的一部分;测试 typed_request_error_exposes_sources 会确认不同错误类型的 source 行为。
configured_thread_config_loader359–364 ↗
fn configured_thread_config_loader(config: &Config) -> Arc<dyn ThreadConfigLoader>
作用:根据配置决定线程配置从哪里加载。如果配置了实验接口,就用远程加载器;否则用什么都不做的加载器。
数据流:输入全局 Config → 读取 experimental_thread_config_endpoint → 输出一个 ThreadConfigLoader:可能是真远程加载器,也可能是空加载器。
调用关系:InProcessClientStartArgs::into_runtime_start_args 调用它,把合适的加载器塞进内嵌服务器启动参数。
调用图:调用 1 个内部函数(new);被 1 处调用(into_runtime_start_args);外部调用 1 个(new)。
InProcessClientStartArgs::initialize_params368–387 ↗
fn initialize_params(&self) -> InitializeParams
作用:把启动参数里的客户端名称、版本、能力开关整理成 initialize 握手参数。服务器启动后需要这些信息知道客户端是谁、支持什么。
数据流:输入 self 中的 client_name、client_version、experimental_api、opt_out_notification_methods 等字段 → 组装 ClientInfo 和 InitializeCapabilities → 输出 InitializeParams。
调用关系:into_runtime_start_args 调用它;这些参数最终随内嵌 app-server 启动握手一起使用。
调用图:被 1 处调用(into_runtime_start_args)。
InProcessClientStartArgs::into_runtime_start_args389–410 ↗
fn into_runtime_start_args(self) -> InProcessStartArgs
作用:把这个门面层的启动参数转换成底层内嵌 app-server 真正需要的启动参数。它是两套参数结构之间的翻译器。
数据流:输入 InProcessClientStartArgs 的所有配置、数据库、环境、反馈、队列容量等信息 → 先生成 initialize 参数,再选择线程配置加载器 → 输出 InProcessStartArgs。
调用关系:InProcessAppServerClient::start 在真正启动底层服务器前调用它。
调用图:调用 2 个内部函数(initialize_params, configured_thread_config_loader);被 1 处调用(start)。
InProcessAppServerClient::start480–597 ↗
async fn start(args: InProcessClientStartArgs) -> IoResult<Self>
作用:启动内嵌 app-server,并启动一个后台 worker 负责转发请求和事件。调用成功后,客户端就可以发请求、收事件了。
数据流:输入启动参数 → 限制队列容量至少为 1,启动底层 in-process server,建立命令队列和事件队列,再创建后台任务 → 输出 InProcessAppServerClient,里面带着发送命令的入口、接收事件的入口和 worker 任务句柄。
调用关系:这是内嵌模式的核心入口;run_exec_session、widget_forced_chatgpt 和测试启动客户端时会调用它。后台 worker 会处理 ClientCommand,并把服务器事件交给 forward_in_process_event。
调用图:调用 2 个内部函数(into_runtime_start_args, start);被 3 处调用(start_test_client_with_capacity, run_exec_session, widget_forced_chatgpt);外部调用 2 个(select!, spawn)。
InProcessAppServerClient::request_handle599–603 ↗
fn request_handle(&self) -> InProcessAppServerRequestHandle
作用:生成一个轻量的请求句柄,让别的任务也能向同一个客户端发请求。它不拿走客户端本体。
数据流:输入当前客户端 → 克隆内部命令发送通道 → 输出 InProcessAppServerRequestHandle。
调用关系:AppServerClient::request_handle 在内嵌模式下会调用它,方便多个调用点共享请求能力。
调用图:外部调用 1 个(clone)。
InProcessAppServerClient::request609–629 ↗
async fn request(&self, request: ClientRequest) -> IoResult<RequestResult>
作用:向内嵌 app-server 发送一个原始客户端请求,并等待 JSON-RPC 风格的返回。适合还想自己处理返回值的人用。
数据流:输入 ClientRequest → 创建一次性回复通道,把请求包成 ClientCommand::Request 发给 worker → 等 worker 回传结果 → 输出成功响应或 IO 错误。
调用关系:request_typed 会先调用它拿到原始响应;worker 收到命令后再交给底层 request_sender.request。
调用图:被 1 处调用(request_typed);外部调用 3 个(new, send, channel)。
InProcessAppServerClient::request_typed637–655 ↗
async fn request_typed(&self, request: ClientRequest) -> Result<T, TypedRequestError>
作用:发送请求,并把成功返回的 JSON 内容解成调用者指定的具体类型。这样调用者不用手动解析 JSON。
数据流:输入 ClientRequest 和目标类型 T → 先取出请求方法名用于报错,再调用 request → 如果服务器返回成功,就用 serde_json 转成 T;如果失败,包装成 TypedRequestError → 输出 T 或分层错误。
调用关系:上层 send_request_with_response 会用它;它依赖 request_method_name 生成更清楚的错误信息。
调用图:调用 2 个内部函数(request, request_method_name);被 1 处调用(send_request_with_response);外部调用 1 个(from_value)。
InProcessAppServerClient::notify658–678 ↗
async fn notify(&self, notification: ClientNotification) -> IoResult<()>
作用:发送一个不需要业务返回值的客户端通知。比如告诉服务器某件事发生了。
数据流:输入 ClientNotification → 创建一次性确认通道,把通知发给 worker → 等待 worker 确认是否成功送出 → 输出空结果或 IO 错误。
调用关系:AppServerClient::notify 在内嵌模式下转到这里;worker 最终调用底层 request_sender.notify。
InProcessAppServerClient::resolve_server_request684–709 ↗
async fn resolve_server_request(
&self,
request_id: RequestId,
result: JsonRpcResult,
) -> IoResult<()>
作用:回应服务器反过来发给客户端的请求,告诉服务器“这个请求成功了,结果是这个”。
数据流:输入服务器请求 ID 和结果 JSON → 包成 ResolveServerRequest 命令发给 worker → worker 转给底层服务器请求响应机制 → 输出是否成功。
调用关系:AppServerClient::resolve_server_request 在内嵌模式下调用它;外层 resolve_server_request 流程会在用户或界面给出答案后用到。
调用图:被 1 处调用(resolve_server_request);外部调用 2 个(send, channel)。
InProcessAppServerClient::reject_server_request712–737 ↗
async fn reject_server_request(
&self,
request_id: RequestId,
error: JSONRPCErrorError,
) -> IoResult<()>
作用:回应服务器反向请求,但表示失败,并带上 JSON-RPC 错误。比如客户端无法处理某个审批问题。
数据流:输入服务器请求 ID 和错误对象 → 包成 RejectServerRequest 命令发给 worker → worker 调用底层失败响应 → 输出是否成功。
调用关系:AppServerClient::reject_server_request 在内嵌模式下调用它;外层 reject_server_request 流程会用它把失败原因传回服务器。
调用图:被 1 处调用(reject_server_request);外部调用 2 个(send, channel)。
InProcessAppServerClient::next_event744–746 ↗
async fn next_event(&mut self) -> Option<InProcessServerEvent>
作用:取下一个来自内嵌 app-server 的事件。界面层靠它持续刷新状态、显示消息、处理服务器请求。
数据流:输入可变客户端引用 → 从事件接收队列等待一条消息 → 输出 Some(event),如果 worker 结束则输出 None。
调用关系:AppServerClient::next_event 在内嵌模式下调用它,然后再把事件转换成统一 AppServerEvent。
调用图:调用 1 个内部函数(recv)。
InProcessAppServerClient::shutdown752–784 ↗
async fn shutdown(self) -> IoResult<()>
作用:关闭内嵌客户端和后台 worker,并设置超时兜底。这样程序退出时不会被后台任务无限拖住。
数据流:输入客户端本体 → 先丢掉事件接收端解除可能的阻塞,再发送 Shutdown 命令并等待最多 5 秒,然后等待 worker 结束;超时就强制 abort → 输出 IO 结果。
调用关系:AppServerClient::shutdown 在内嵌模式下调用它;测试 shutdown_completes_promptly_without_retained_managers 验证它能快速完成。
InProcessAppServerRequestHandle::request788–808 ↗
async fn request(&self, request: ClientRequest) -> IoResult<RequestResult>
作用:用轻量请求句柄发送一个原始请求。它让没有完整客户端所有权的代码也能发请求。
数据流:输入 ClientRequest → 创建回复通道,把请求命令发进共享 worker 队列 → 等待对应回复 → 输出 JSON-RPC 风格结果或 IO 错误。
调用关系:InProcessAppServerRequestHandle::request_typed 会调用它;AppServerRequestHandle::request 在内嵌分支会转到这里。
调用图:被 1 处调用(request_typed);外部调用 3 个(new, send, channel)。
InProcessAppServerRequestHandle::request_typed810–828 ↗
async fn request_typed(&self, request: ClientRequest) -> Result<T, TypedRequestError>
作用:用轻量请求句柄发送请求,并把成功结果解成指定类型。适合后台任务或辅助模块调用服务器 API。
数据流:输入请求和目标类型 T → 调 request 得到原始响应 → 区分传输错误、服务器错误、解码错误 → 输出 T 或 TypedRequestError。
调用关系:AppServerRequestHandle::request_typed 在内嵌分支会调用它;它和完整客户端的 request_typed 行为保持一致。
调用图:调用 2 个内部函数(request, request_method_name);外部调用 1 个(from_value)。
AppServerRequestHandle::request832–837 ↗
async fn request(&self, request: ClientRequest) -> IoResult<RequestResult>
作用:在统一请求句柄上发送原始请求,不让调用者关心背后是内嵌服务器还是远程服务器。
数据流:输入 ClientRequest → 根据枚举分支转发给 InProcess 或 Remote 句柄 → 输出对应的请求结果。
调用关系:这是跨传输方式的分发层;上层拿到 AppServerRequestHandle 后直接调用它即可。
AppServerRequestHandle::request_typed839–847 ↗
async fn request_typed(&self, request: ClientRequest) -> Result<T, TypedRequestError>
作用:在统一请求句柄上发送 typed request,并自动解析返回值。许多业务读取账号、限额、连接器状态的地方会用它。
数据流:输入请求和目标类型 T → 按当前句柄是内嵌还是远程,转发到对应 request_typed → 输出解析后的 T 或 TypedRequestError。
调用关系:被 consume_rate_limit_reset_credit_request、fetch_account_rate_limits、fetch_all_mcp_server_statuses 等很多功能调用,是共享请求入口。
调用图:被 25 处调用(consume_rate_limit_reset_credit_request, fetch_account_rate_limits, fetch_account_token_activity, fetch_all_mcp_server_statuses, fetch_connectors_list, fetch_feedback_upload, fetch_marketplace_add, fetch_marketplace_remove, fetch_marketplace_upgrade, fetch_plugin_detail (+15 more))。
AppServerClient::codex_home851–858 ↗
fn codex_home(&self, local_codex_home: &AbsolutePathBuf) -> Option<AppServerPath>
作用:取得服务器眼里的 codex home 路径。内嵌模式直接用本地路径,远程模式则使用远程服务器告诉客户端的路径。
数据流:输入统一客户端和本地 codex_home → 内嵌分支把本地路径转成 AppServerPath;远程分支读取远端 codex_home 后再转换 → 输出可选路径。
调用关系:codex_home_path 会调用它,用来在界面或命令里显示/使用正确的服务器侧路径。
调用图:调用 2 个内部函数(from_app_server, display);被 1 处调用(codex_home_path)。
AppServerClient::request860–865 ↗
async fn request(&self, request: ClientRequest) -> IoResult<RequestResult>
作用:统一发送原始请求,隐藏内嵌和远程两种实现差别。
数据流:输入 ClientRequest → match 当前客户端类型 → 转发给对应客户端的 request → 输出原始请求结果。
调用关系:这是 AppServerClient 的公共分发方法;需要原始 JSON-RPC 结果的调用点会用它。
AppServerClient::request_typed867–875 ↗
async fn request_typed(&self, request: ClientRequest) -> Result<T, TypedRequestError>
作用:统一发送 typed request,并把结果解析成指定类型。上层大多数功能不用关心连接方式。
数据流:输入请求和目标类型 T → 根据 InProcess 或 Remote 分支调用对应 request_typed → 输出解析后的响应或 TypedRequestError。
调用关系:bootstrap、read_account、reload_user_config、logout_account 等许多上层流程都通过它调用 app-server API。
调用图:被 34 处调用(bootstrap, external_agent_config_detect, external_agent_config_import, fork_thread, logout_account, memory_reset, read_account, reload_user_config, resume_thread, review_start (+15 more))。
AppServerClient::notify877–882 ↗
async fn notify(&self, notification: ClientNotification) -> IoResult<()>
作用:统一发送通知。调用者不用知道当前服务器在本进程还是远端。
数据流:输入 ClientNotification → 分支转发给内嵌或远程客户端 → 输出发送是否成功。
调用关系:用于上层把无需回复的事件告知服务器;它只是薄薄的统一入口。
AppServerClient::resolve_server_request884–893 ↗
async fn resolve_server_request(
&self,
request_id: RequestId,
result: JsonRpcResult,
) -> IoResult<()>
作用:统一回复服务器发来的请求,表示成功完成。比如界面收到服务器提问后,把用户选择传回去。
数据流:输入请求 ID 和结果 JSON → 按客户端类型转发到对应 resolve_server_request → 输出 IO 结果。
调用关系:外部 resolve_server_request 流程会调用它;内嵌和远程都遵守同样的服务器请求响应模型。
调用图:被 1 处调用(resolve_server_request)。
AppServerClient::reject_server_request895–904 ↗
async fn reject_server_request(
&self,
request_id: RequestId,
error: JSONRPCErrorError,
) -> IoResult<()>
作用:统一拒绝服务器发来的请求,并带上错误说明。适合用户取消或客户端无法处理的情况。
数据流:输入请求 ID 和 JSON-RPC 错误 → 分支转发给内嵌或远程客户端 → 输出是否成功送回错误。
调用关系:外部 reject_server_request 流程会调用它;保证不同连接方式的拒绝行为一致。
调用图:被 1 处调用(reject_server_request)。
AppServerClient::next_event906–911 ↗
async fn next_event(&mut self) -> Option<AppServerEvent>
作用:统一等待下一条服务器事件。界面主循环通常靠它不断拿消息。
数据流:输入可变客户端 → 内嵌分支读取 InProcess 事件并转换成 AppServerEvent;远程分支直接读取 AppServerEvent → 输出可选事件。
调用关系:外层 next_event 包装会调用它;它是客户端事件流的统一出口。
调用图:被 1 处调用(next_event)。
AppServerClient::shutdown913–918 ↗
async fn shutdown(self) -> IoResult<()>
作用:统一关闭客户端连接或内嵌运行时。调用者不必分别写两套退出逻辑。
数据流:输入客户端本体 → 按类型调用内嵌或远程 shutdown → 输出关闭结果。
调用关系:外层 shutdown 流程会调用它;它是资源释放的统一入口。
调用图:被 1 处调用(shutdown)。
AppServerClient::request_handle920–925 ↗
fn request_handle(&self) -> AppServerRequestHandle
作用:从统一客户端拿到一个可克隆的请求句柄。这样其他任务能发请求,但不用拥有整个事件接收客户端。
数据流:输入客户端引用 → 内嵌分支生成 InProcess 句柄,远程分支生成 Remote 句柄 → 输出 AppServerRequestHandle。
调用关系:外层 request_handle 会调用它;之后许多后台功能可以用该句柄调用 AppServerRequestHandle::request_typed。
调用图:被 1 处调用(request_handle);外部调用 2 个(InProcess, Remote)。
request_method_name930–940 ↗
fn request_method_name(request: &ClientRequest) -> String
作用:从请求对象里提取 JSON-RPC 方法名,只用于生成更清楚的错误信息。提取不到时返回“<unknown>”。
数据流:输入 ClientRequest 引用 → 先转成 JSON 值,再读取 method 字段 → 输出方法名字符串或默认字符串。
调用关系:InProcessAppServerClient::request_typed 和 InProcessAppServerRequestHandle::request_typed 都会调用它,用于标记错误来自哪个 API 方法。
调用图:被 2 处调用(request_typed, request_typed);外部调用 1 个(to_value)。
tests::build_test_config977–984 ↗
async fn build_test_config() -> Config
作用:给测试构造一份默认配置。正常构建失败时,它会退回到另一种默认加载方式。
数据流:无业务输入 → 尝试 ConfigBuilder::default().build(),失败后调用默认配置加载 → 输出 Config。
调用关系:多个运行时参数测试会用它准备可用配置。
调用图:外部调用 3 个(new, load_default_with_cli_overrides, default)。
tests::build_test_config_for_codex_home986–1000 ↗
async fn build_test_config_for_codex_home(codex_home: &Path) -> Config
作用:给指定临时 codex home 构造测试配置。这样测试不会污染真实用户目录。
数据流:输入一个目录路径 → 用 ConfigBuilder 指定 codex_home 构建,失败则用带 codex_home 的默认加载 → 输出 Config。
调用关系:start_test_client_with_capacity 调用它,为内嵌测试客户端准备隔离环境。
调用图:外部调用 4 个(to_path_buf, new, load_default_with_cli_overrides_for_codex_home, default)。
tests::TestClient::deref1010–1012 ↗
fn deref(&self) -> &Self::Target
作用:让测试里的 TestClient 可以像 InProcessAppServerClient 一样直接使用。这样测试代码更短。
数据流:输入 TestClient 引用 → 返回内部 client 的引用 → 不改变任何状态。
调用关系:测试调用 client.request_typed 等方法时会自动用到它。
tests::TestClient::shutdown1016–1018 ↗
async fn shutdown(self) -> IoResult<()>
作用:关闭测试客户端。它把包装结构里的真实客户端拿出来执行 shutdown。
数据流:输入 TestClient 本体 → 调用内部 InProcessAppServerClient::shutdown → 输出关闭结果。
调用关系:内嵌客户端相关测试结束时调用它清理后台任务。
调用图:调用 1 个内部函数(shutdown)。
tests::start_test_client_with_capacity1021–1057 ↗
async fn start_test_client_with_capacity(
session_source: SessionSource,
channel_capacity: usize,
) -> TestClient
作用:按指定队列容量启动一个内嵌测试客户端。它负责搭好临时目录、配置、状态数据库和环境管理器。
数据流:输入 session_source 和 channel_capacity → 创建临时 codex_home,加载配置,初始化状态数据库,组装 InProcessClientStartArgs 并启动客户端 → 输出 TestClient。
调用关系:start_test_client 和多项内嵌测试调用它;它最终调用 InProcessAppServerClient::start。
调用图:调用 4 个内部函数(start, default, default_for_tests, new);外部调用 7 个(new, new, new, build_test_config_for_codex_home, default, init_state_db, default)。
tests::start_test_client1059–1061 ↗
async fn start_test_client(session_source: SessionSource) -> TestClient
作用:用默认队列容量启动测试客户端。它是更省事的测试辅助函数。
数据流:输入 session_source → 调用 start_test_client_with_capacity 并传入默认容量 → 输出 TestClient。
调用关系:多数内嵌客户端测试通过它启动环境。
调用图:外部调用 1 个(start_test_client_with_capacity)。
tests::start_test_remote_server1063–1071 ↗
async fn start_test_remote_server(handler: F) -> String
作用:启动一个本地假的远程 WebSocket 服务器,不带鉴权要求。测试远程客户端时用它模拟服务器。
数据流:输入一个处理 WebSocket 连接的异步 handler → 转给 start_test_remote_server_with_auth,鉴权参数为 None → 输出可连接的 ws:// 地址。
调用关系:很多 remote_* 测试用它搭临时服务器。
调用图:外部调用 1 个(start_test_remote_server_with_auth)。
tests::start_test_remote_server_with_auth1073–1109 ↗
async fn start_test_remote_server_with_auth(
expected_auth_token: Option<String>,
handler: F,
) -> String
作用:启动一个可检查 Authorization 头的假的远程 WebSocket 服务器。用于测试远程连接和鉴权头是否正确。
数据流:输入期望的 token 和连接处理函数 → 绑定本地随机端口,等待一个连接,升级为 WebSocket,并校验请求头 → 输出服务器 URL。
调用关系:start_test_remote_server 调它;remote_connect_includes_auth_header_when_configured 等测试直接用它。
tests::expect_remote_initialize1111–1136 ↗
async fn expect_remote_initialize(websocket: &mut tokio_tungstenite::WebSocketStream<S>)
作用:在假远程服务器里验证客户端会先发 initialize 请求,并回复初始化成功。它模拟真实服务器握手。
数据流:输入 WebSocket 流 → 读取第一条 JSON-RPC 请求并确认 method 是 initialize,写回带 userAgent 和 codexHome 的响应,再确认收到 initialized 通知 → 无返回,失败则 panic。
调用关系:几乎所有远程 WebSocket 测试都会先调用它建立连接前提。
调用图:外部调用 6 个(read_websocket_message, write_websocket_message, Response, assert_eq!, panic!, json!)。
tests::read_websocket_message1138–1161 ↗
async fn read_websocket_message(
websocket: &mut tokio_tungstenite::WebSocketStream<S>,
) -> JSONRPCMessage
作用:从测试 WebSocket 中读取一条文本 JSON-RPC 消息。它会跳过 ping、pong、二进制等无关帧。
数据流:输入 WebSocket 流 → 循环读取帧,遇到文本就反序列化成 JSONRPCMessage → 输出消息;遇到关闭或非法内容则测试失败。
调用关系:远程服务器测试 handler 用它观察客户端发来的请求或响应。
tests::write_websocket_message1163–1177 ↗
async fn write_websocket_message(
websocket: &mut tokio_tungstenite::WebSocketStream<S>,
message: JSONRPCMessage,
)
作用:把一条 JSON-RPC 测试消息写进 WebSocket。用于假服务器给客户端发响应、通知或请求。
数据流:输入 WebSocket 流和 JSONRPCMessage → 序列化成字符串,包装成文本帧发送 → 无返回,失败则测试失败。
调用关系:expect_remote_initialize 和各种远程测试 handler 都用它发送模拟服务器消息。
tests::command_execution_output_delta_notification1179–1188 ↗
fn command_execution_output_delta_notification(delta: &str) -> ServerNotification
作用:构造一条“命令输出片段”通知。测试用它模拟 stdout/stderr 这类可以尽力发送的消息。
数据流:输入 delta 文本 → 填入固定 thread、turn、item ID → 输出 ServerNotification::CommandExecutionOutputDelta。
调用关系:背压测试用它制造普通可丢弃事件。
调用图:外部调用 1 个(CommandExecutionOutputDelta)。
tests::agent_message_delta_notification1190–1199 ↗
fn agent_message_delta_notification(delta: &str) -> ServerNotification
作用:构造一条“助手消息正文片段”通知。测试用它模拟不能丢的流式回答文本。
数据流:输入 delta 文本 → 填入固定上下文 ID → 输出 ServerNotification::AgentMessageDelta。
调用关系:内嵌和远程背压测试用它确认正文片段会被保住。
调用图:外部调用 1 个(AgentMessageDelta)。
tests::item_completed_notification1201–1213 ↗
fn item_completed_notification(text: &str) -> ServerNotification
作用:构造一条“某个消息项完成了”的通知。测试用它模拟最终权威内容。
数据流:输入完整文本 → 组装 AgentMessage 类型的 ThreadItem → 输出 ServerNotification::ItemCompleted。
调用关系:背压测试用它确认完成项不会因为队列满被丢。
调用图:外部调用 1 个(ItemCompleted)。
tests::turn_completed_notification1215–1229 ↗
fn turn_completed_notification() -> ServerNotification
作用:构造一条“整个回合完成了”的通知。测试用它模拟客户端等待的结束信号。
数据流:无业务输入 → 创建一个 completed 状态的 Turn → 输出 ServerNotification::TurnCompleted。
调用关系:背压测试用它确认完成信号必须送达,否则界面可能一直等待。
调用图:外部调用 2 个(TurnCompleted, new)。
tests::test_remote_connect_args1231–1243 ↗
fn test_remote_connect_args(websocket_url: String) -> RemoteAppServerConnectArgs
作用:为远程客户端测试快速生成一组连接参数。这样每个测试只要提供 WebSocket URL。
数据流:输入 websocket_url → 填入测试客户端名、版本、实验 API 开关、队列容量等默认值 → 输出 RemoteAppServerConnectArgs。
调用关系:多数远程测试用它减少重复配置。
调用图:外部调用 1 个(new)。
tests::typed_request_roundtrip_works1246–1256 ↗
async fn typed_request_roundtrip_works()
作用:测试内嵌客户端能完成一次 typed request 往返。它确认请求能发出去,响应也能解成指定类型。
数据流:启动测试客户端 → 发送 ConfigRequirementsRead 请求 → 期待得到 ConfigRequirementsReadResponse → 最后关闭客户端。
调用关系:验证 InProcessAppServerClient::request_typed 的基本成功路径。
调用图:外部调用 2 个(start_test_client, Integer)。
tests::typed_request_reports_json_rpc_errors1259–1276 ↗
async fn typed_request_reports_json_rpc_errors()
作用:测试服务器返回 JSON-RPC 错误时,typed request 会把错误包装清楚。不是把它误当成传输失败或解码失败。
数据流:启动客户端 → 请求读取一个不存在的 thread → 得到错误 → 检查错误文字以 thread/read 开头 → 关闭客户端。
调用关系:覆盖 InProcessAppServerClient::request_typed 的服务器错误分支和 TypedRequestError::fmt。
调用图:外部调用 3 个(start_test_client, Integer, assert!)。
tests::caller_provided_session_source_is_applied1279–1298 ↗
async fn caller_provided_session_source_is_applied()
作用:测试启动参数里的 session_source 会真正写进新建线程元数据。不同入口应该被服务器正确记录。
数据流:分别用 Exec 和 Cli 启动客户端 → 发 thread/start 请求 → 检查返回 thread.source 是否符合预期 → 关闭客户端。
调用关系:验证 InProcessClientStartArgs::into_runtime_start_args 没有丢掉 session_source。
调用图:外部调用 4 个(start_test_client, Integer, default, assert_eq!)。
tests::threads_started_via_app_server_are_visible_through_typed_requests1301–1329 ↗
async fn threads_started_via_app_server_are_visible_through_typed_requests()
作用:测试通过 app-server 新建的线程,之后能再通过 typed request 读回来。
数据流:启动客户端 → thread/start 新建线程 → thread/read 读取同一个 ID → 比较 ID 一致 → 关闭客户端。
调用关系:验证内嵌 app-server 的线程创建和读取 API 能串起来。
调用图:外部调用 4 个(start_test_client, Integer, default, assert_eq!)。
tests::tiny_channel_capacity_still_supports_request_roundtrip1332–1343 ↗
async fn tiny_channel_capacity_still_supports_request_roundtrip()
作用:测试队列容量小到 1 时,请求往返仍然能工作。它防止极小容量配置把基本请求卡死。
数据流:用 channel_capacity=1 启动客户端 → 发送配置需求读取请求 → 期待成功响应 → 关闭客户端。
调用关系:覆盖 InProcessAppServerClient::start 对队列容量和 worker 流程的稳健性。
调用图:外部调用 2 个(start_test_client_with_capacity, Integer)。
tests::forward_in_process_event_preserves_transcript_notifications_under_backpressure1346–1431 ↗
async fn forward_in_process_event_preserves_transcript_notifications_under_backpressure()
作用:测试内嵌事件队列满时,助手正文和完成事件不会被丢。普通输出可以被跳过,但要发 Lagged 标记。
数据流:创建容量为 1 的事件队列并先塞满 → 发送普通输出导致 skipped_events 增加 → 再发送正文、item completed、turn completed → 读取事件并确认顺序里有 Lagged 且关键事件都在。
调用关系:直接测试 forward_in_process_event 和 event_requires_delivery 的核心背压规则。
调用图:调用 1 个内部函数(forward_in_process_event);外部调用 12 个(from_secs, new, agent_message_delta_notification, command_execution_output_delta_notification, item_completed_notification, turn_completed_notification, ServerNotification, assert!, assert_eq!, channel (+2 more))。
tests::remote_typed_request_roundtrip_works1434–1475 ↗
async fn remote_typed_request_roundtrip_works()
作用:测试远程 WebSocket 客户端能完成初始化和 typed request 往返。还检查服务器版本和 codexHome 能被记录。
数据流:启动假 WebSocket 服务器 → 客户端连接并握手 → 发送 account/read → 假服务器回响应 → 客户端解析 GetAccountResponse → 关闭。
调用关系:覆盖 RemoteAppServerClient::connect 和远程 request_typed 的基本成功路径。
调用图:调用 1 个内部函数(connect);外部调用 4 个(start_test_remote_server, test_remote_connect_args, Integer, assert_eq!)。
tests::remote_unix_socket_typed_request_roundtrip_works1478–1533 ↗
async fn remote_unix_socket_typed_request_roundtrip_works()
作用:测试远程客户端也能通过 Unix socket 连接并发 typed request。Unix socket 是本机进程间通信的一种方式。
数据流:创建临时 socket 路径并监听 → 假服务器接受连接、WebSocket 升级、完成握手和 account/read 响应 → 客户端解析结果 → 关闭。
调用关系:验证 RemoteAppServerEndpoint::UnixSocket 分支能正常工作。
调用图:调用 3 个内部函数(connect, bind, from_absolute_path);外部调用 12 个(new, new, expect_remote_initialize, read_websocket_message, write_websocket_message, Response, Integer, assert_eq!, panic!, to_value (+2 more))。
tests::remote_typed_request_accepts_large_single_frame_response1536–1582 ↗
async fn remote_typed_request_accepts_large_single_frame_response()
作用:测试远程客户端能接收很大的单帧响应。防止大 JSON 响应被 WebSocket 限制误伤。
数据流:假服务器返回带大量 padding 的 account/read 响应 → 客户端只解析自己需要的字段 → 检查响应正确 → 关闭。
调用关系:覆盖远程传输对大消息的承受能力。
调用图:调用 1 个内部函数(connect);外部调用 4 个(start_test_remote_server, test_remote_connect_args, Integer, assert_eq!)。
tests::remote_connect_includes_auth_header_when_configured1585–1610 ↗
async fn remote_connect_includes_auth_header_when_configured()
作用:测试配置了远程鉴权 token 时,客户端连接会带 Authorization 头。
数据流:启动期望某个 token 的假服务器 → 客户端用带 token 的连接参数连接 → 服务器在握手时校验请求头 → 关闭客户端。
调用关系:验证 RemoteAppServerClient::connect 的鉴权头发送逻辑。
调用图:调用 1 个内部函数(connect);外部调用 2 个(new, start_test_remote_server_with_auth)。
tests::remote_connect_rejects_non_loopback_ws_when_auth_configured1613–1635 ↗
async fn remote_connect_rejects_non_loopback_ws_when_auth_configured()
作用:测试带鉴权 token 的普通 ws:// 外网地址会被拒绝。这样避免 token 明文发到不安全网络。
数据流:构造 ws://example.com 且带 token 的连接参数 → 调 connect → 期待 InvalidInput 错误并检查错误文字。
调用关系:覆盖远程连接的安全策略:token 只能走 wss:// 或本机 ws://。
调用图:调用 1 个内部函数(connect);外部调用 4 个(new, assert!, assert_eq!, panic!)。
tests::remote_auth_token_transport_policy_allows_wss_and_loopback_ws1638–1648 ↗
fn remote_auth_token_transport_policy_allows_wss_and_loopback_ws()
作用:测试远程鉴权 token 的传输安全判断。安全的 wss 和本机 ws 允许,普通外网 ws 不允许。
数据流:输入三种 URL → 调 websocket_url_supports_auth_token → 分别断言 true、true、false。
调用关系:直接测试 remote 模块里的 URL 安全策略辅助函数。
调用图:外部调用 1 个(assert!)。
tests::remote_duplicate_request_id_keeps_original_waiter1651–1736 ↗
async fn remote_duplicate_request_id_keeps_original_waiter()
作用:测试远程客户端遇到重复请求 ID 时,不会把第二个请求发给服务器,也不会弄乱第一个等待者。
数据流:启动假服务器并等待第一个 account/read → 用两个句柄发送相同 ID 请求 → 第二个立刻得到重复 ID 错误,第一个仍收到服务器响应 → 关闭。
调用关系:验证远程请求登记表对重复 ID 的保护,避免响应错配。
调用图:调用 1 个内部函数(connect);外部调用 6 个(start_test_remote_server, test_remote_connect_args, Integer, assert_eq!, spawn, channel)。
tests::remote_notifications_arrive_over_websocket1739–1771 ↗
async fn remote_notifications_arrive_over_websocket()
作用:测试远程服务器发来的通知能被客户端作为事件读到。
数据流:假服务器握手后发送 AccountUpdated 通知 → 客户端 next_event → 检查收到 ServerNotification::AccountUpdated → 关闭。
调用关系:覆盖 RemoteAppServerClient 的事件接收路径。
调用图:调用 1 个内部函数(connect);外部调用 3 个(start_test_remote_server, test_remote_connect_args, assert!)。
tests::remote_backpressure_preserves_transcript_notifications1774–1868 ↗
async fn remote_backpressure_preserves_transcript_notifications()
作用:测试远程模式下队列拥堵时,助手正文和完成信号仍会保留。普通命令输出可能被丢或伴随 Lagged 标记。
数据流:用容量 1 的远程客户端连接假服务器 → 服务器连续发送普通输出、正文、完成项、回合完成 → 客户端读取并确认关键三类通知都出现 → 关闭。
调用关系:验证远程传输和本文件的 server_notification_requires_delivery 分类保持一致。
调用图:调用 1 个内部函数(connect);外部调用 10 个(from_secs, new, start_test_remote_server, test_remote_connect_args, assert!, assert_eq!, matches!, panic!, channel, timeout)。
tests::remote_server_request_resolution_roundtrip_works1871–1923 ↗
async fn remote_server_request_resolution_roundtrip_works()
作用:测试远程服务器向客户端发请求时,客户端能收到并成功回复。比如工具需要用户输入的场景。
数据流:假服务器发送 item/tool/requestUserInput 请求 → 客户端 next_event 收到 ServerRequest → 调 resolve_server_request 回空结果 → 假服务器收到响应 → 关闭。
调用关系:覆盖远程服务器请求的接收和成功响应路径。
调用图:调用 1 个内部函数(connect);外部调用 4 个(start_test_remote_server, test_remote_connect_args, panic!, json!)。
tests::remote_server_request_received_during_initialize_is_delivered1926–2001 ↗
async fn remote_server_request_received_during_initialize_is_delivered()
作用:测试服务器在 initialize 握手期间就发来的请求不会丢。早到的请求也要交给客户端处理。
数据流:假服务器收到 initialize 后先发服务器请求,再发 initialize 响应 → 客户端连接后读取该请求并回复 → 假服务器确认收到响应。
调用关系:验证远程客户端在启动握手和事件流之间没有空窗期。
调用图:调用 1 个内部函数(connect);外部调用 4 个(start_test_remote_server, test_remote_connect_args, panic!, json!)。
tests::remote_unknown_server_request_is_rejected2004–2036 ↗
async fn remote_unknown_server_request_is_rejected()
作用:测试远程服务器发来不支持的请求方法时,客户端会自动回 JSON-RPC 方法不存在错误。
数据流:假服务器发送 method 为 thread/unknown 的请求 → 客户端处理后回 Error 响应 → 服务器检查错误码和消息。
调用关系:覆盖远程服务器请求分派的兜底拒绝逻辑。
调用图:调用 1 个内部函数(connect);外部调用 2 个(start_test_remote_server, test_remote_connect_args)。
tests::remote_disconnect_surfaces_as_event2039–2054 ↗
async fn remote_disconnect_surfaces_as_event()
作用:测试远程服务器断开连接时,客户端会把它变成 Disconnected 事件。界面可以据此提示用户。
数据流:假服务器握手后关闭 WebSocket → 客户端 next_event → 期待 AppServerEvent::Disconnected。
调用关系:验证远程连接关闭不会静默消失,而是进入事件流。
调用图:调用 1 个内部函数(connect);外部调用 3 个(start_test_remote_server, test_remote_connect_args, assert!)。
tests::typed_request_error_exposes_sources2057–2084 ↗
fn typed_request_error_exposes_sources()
作用:测试 TypedRequestError 的错误来源和显示文字符合预期。这样上层日志和用户提示都更可靠。
数据流:手工构造传输错误、服务器错误、解码错误 → 调 Error::source 和 to_string → 检查哪些有 source、文字是否包含 code 和 data。
调用关系:覆盖 TypedRequestError::fmt 和 TypedRequestError::source。
调用图:外部调用 3 个(new, assert_eq!, json!)。
tests::next_event_surfaces_lagged_markers2087–2112 ↗
tests::event_requires_delivery_marks_transcript_and_terminal_events2115–2189 ↗
fn event_requires_delivery_marks_transcript_and_terminal_events()
作用:测试哪些事件被标成必须送达。它确保正文、完成、配置导入完成等不会被误判为可丢。
数据流:构造多种 InProcessServerEvent → 调 event_requires_delivery → 对关键事件断言 true,对 Lagged 和命令输出片段断言 false。
调用关系:直接保护 event_requires_delivery 和 server_notification_requires_delivery 的分类表。
调用图:外部调用 1 个(assert!)。
tests::runtime_start_args_forward_environment_manager2192–2242 ↗
async fn runtime_start_args_forward_environment_manager()
作用:测试启动参数转换时不会丢掉 environment_manager。这个对象决定命令执行和文件操作使用什么环境。
数据流:构造带远程默认环境的 EnvironmentManager → 调 into_runtime_start_args → 检查 runtime_args 里仍是同一个 Arc,且默认环境是远程的。
调用关系:覆盖 InProcessClientStartArgs::into_runtime_start_args 对环境管理器字段的传递。
调用图:调用 4 个内部函数(default, create_for_tests, new, new);外部调用 8 个(new, new, build_test_config, default, assert!, assert_eq!, default, current_exe)。
tests::runtime_start_args_use_remote_thread_config_loader_when_configured2245–2280 ↗
async fn runtime_start_args_use_remote_thread_config_loader_when_configured()
作用:测试配置了实验线程配置 endpoint 时,会使用远程线程配置加载器。没有这个,相关实验配置可能永远不会生效。
数据流:构造 config 并设置 experimental_thread_config_endpoint → 转成 runtime_args → 调 thread_config_loader.load → 期待它尝试远程请求并返回 RequestFailed。
调用关系:覆盖 configured_thread_config_loader 被 into_runtime_start_args 调用后的实际效果。
调用图:调用 3 个内部函数(default, default_for_tests, new);外部调用 7 个(new, default, new, build_test_config, default, assert_eq!, default)。
tests::shutdown_completes_promptly_without_retained_managers2283–2290 ↗
async fn shutdown_completes_promptly_without_retained_managers()
作用:测试关闭内嵌客户端不会无故等满 5 秒超时。它防止后台资源引用导致退出变慢。
数据流:启动测试客户端 → 用 1 秒 timeout 包住 shutdown → 期待及时完成且没有错误。
调用关系:验证 InProcessAppServerClient::shutdown 的优雅关闭路径足够快。
调用图:外部调用 3 个(from_secs, start_test_client, timeout)。
app-server-client/src/remote.rs源码 ↗
可以把这个文件想成一部“电话交换机”。它先拨通远程 app-server,做一次初始化握手,告诉服务器“我是谁、我支持什么能力”。之后它一直守着连接:客户端要发请求或通知时,它把内容包装成 JSON-RPC(一种用 JSON 表示请求、响应和通知的通用格式)再通过 WebSocket 发出去;服务器回消息时,它按请求编号找到等待的人,或者把服务器通知变成 AppServerEvent 送给上层。它还处理服务器主动发来的请求,让上层可以答复或拒绝。这个文件很重视断线、超时、重复请求编号、关闭连接这些边角情况,因为远程通信不像函数调用那样可靠,必须把各种失败变成清楚的错误或断线事件。
RemoteAppServerConnectArgs::initialize_params93–112 ↗
fn initialize_params(&self) -> InitializeParams
作用:把连接参数整理成初始化握手要发送给服务器的内容。它说明客户端名字、版本,以及是否开启实验功能、是否不要某些通知。
数据流:进去的是 RemoteAppServerConnectArgs 里保存的客户端信息和能力开关 → 它组装成 InitializeParams → 出来的是一份可以直接放进 initialize 请求里的参数,不改动原对象。
调用关系:RemoteAppServerClient::connect 在真正连服务器前会先用它准备握手材料。它不负责联网,只负责把“自我介绍”写成协议认识的格式。
调用图:被 1 处调用(connect)。
websocket_url_supports_auth_token115–123 ↗
fn websocket_url_supports_auth_token(url: &Url) -> bool
作用:判断某个 WebSocket 地址能不能安全地带认证令牌。令牌就像门禁卡号码,不能随便发到不安全或陌生地址上。
数据流:进去的是一个解析好的 URL → 它查看协议是 wss 还是 ws,以及主机是不是 localhost 或本机回环地址 → 出来 true 或 false,表示是否允许附带 auth token。
调用关系:connect_websocket_endpoint 在设置 Authorization 请求头前会问它。它把安全规则集中在一个地方,避免调用者不小心把令牌发到普通远程 ws 地址。
调用图:被 1 处调用(connect_websocket_endpoint);外部调用 2 个(host, scheme)。
RemoteAppServerClient::connect164–183 ↗
async fn connect(args: RemoteAppServerConnectArgs) -> IoResult<Self>
作用:这是远程客户端的主要入口:根据配置连接 WebSocket 地址或 Unix socket,并完成初始化。上层只要调用它,就能拿到一个可收发请求和事件的客户端。
数据流:进去的是 RemoteAppServerConnectArgs → 它先生成初始化参数,再按 endpoint 类型选择网络 WebSocket 或本地 Unix socket 连接 → 出来 RemoteAppServerClient;如果地址错、超时或握手失败,就返回错误。
调用关系:测试和上层会在需要远程 app-server 会话时调用它。它把具体连接工作交给 connect_websocket_endpoint 或 connect_unix_socket_endpoint,拿到流之后交给 connect_with_stream 建立后台收发循环。
调用图:调用 3 个内部函数(initialize_params, connect_unix_socket_endpoint, connect_websocket_endpoint);被 13 处调用(remote_backpressure_preserves_transcript_notifications, remote_connect_includes_auth_header_when_configured, remote_connect_rejects_non_loopback_ws_when_auth_configured, remote_disconnect_surfaces_as_event, remote_duplicate_request_id_keeps_original_waiter, remote_notifications_arrive_over_websocket, remote_server_request_received_during_initialize_is_delivered, remote_server_request_resolution_roundtrip_works, remote_typed_request_accepts_large_single_frame_response, remote_typed_request_roundtrip_works (+3 more));外部调用 1 个(connect_with_stream)。
RemoteAppServerClient::server_version185–187 ↗
fn server_version(&self) -> Option<&str>
作用:读取远程服务器在初始化时报告的版本号。上层可以用它显示连接到的服务版本,或者判断能力差异。
数据流:进去的是客户端自身 → 它查看初始化时保存的 server_version → 出来 Option<&str>,有版本就给字符串,没有就给 None,不改变任何状态。
调用关系:它通常在连接成功后被界面或诊断代码读取。版本信息来自 connect_with_stream 里的 initialize_remote_connection。
RemoteAppServerClient::codex_home189–191 ↗
fn codex_home(&self) -> Option<&str>
作用:读取远程服务器告诉客户端的 Codex 主目录。这个目录可以帮助上层知道远端服务实际使用哪份工作环境。
数据流:进去的是客户端自身 → 它查看初始化时保存的 codex_home → 出来 Option<&str>,没有报告或为空时就是 None。
调用关系:它和 server_version 类似,是连接完成后的元信息读取口。数据由 initialize_remote_connection 在握手响应里提取。
RemoteAppServerClient::connect_with_stream193–483 ↗
async fn connect_with_stream(
channel_capacity: usize,
endpoint: String,
stream: WebSocketStream<S>,
initialize_params: InitializeParams,
) -> IoResult<Self>
作用:在已经拿到 WebSocket 流之后,把它变成完整可用的远程客户端。它会做初始化,并启动一个后台任务,专门负责收消息、发消息和分发结果。
数据流:进去的是通道容量、端点名称、WebSocket 流和初始化参数 → 它先执行 initialize_remote_connection,保存握手期间积累的事件和服务器信息,然后创建命令通道、事件通道和后台 worker → 出来 RemoteAppServerClient,内部带着这些通道和后台任务。
调用关系:RemoteAppServerClient::connect 连接成功后会调用它。它是这个文件的核心装配点:上层后续的 request、notify、next_event、shutdown 都是通过这里建立的后台循环来运转。
调用图:调用 1 个内部函数(initialize_remote_connection);外部调用 4 个(new, new, select!, spawn)。
RemoteAppServerClient::request_handle485–489 ↗
fn request_handle(&self) -> RemoteAppServerRequestHandle
作用:生成一个轻量的请求把手,让别的任务也能向同一个远程服务器发请求。它像给同一部电话分出一个分机。
数据流:进去的是客户端自身 → 它复制内部命令发送端 command_tx → 出来 RemoteAppServerRequestHandle,不会移动或关闭原客户端。
调用关系:RemoteAppServerClient::request 会用它来复用请求发送逻辑。需要在多个异步任务里发请求时,上层也可以拿这个 handle 使用。
RemoteAppServerClient::request491–493 ↗
async fn request(&self, request: ClientRequest) -> IoResult<RequestResult>
作用:从客户端直接向远程服务器发送一个普通请求,并等待结果。它是最常用的“问服务器一件事,然后等答复”的接口。
数据流:进去的是 ClientRequest → 它先拿 request_handle,再把请求交给 handle 发送 → 出来 IoResult<RequestResult>,外层错误表示传输失败,内层结果表示服务器成功或返回 JSON-RPC 错误。
调用关系:RemoteAppServerClient::request_typed 会调用它。它本身不直接写 WebSocket,而是通过 RemoteAppServerRequestHandle 把活交给后台 worker。
调用图:调用 1 个内部函数(request_handle);被 1 处调用(request_typed)。
RemoteAppServerClient::request_typed495–513 ↗
async fn request_typed(&self, request: ClientRequest) -> Result<T, TypedRequestError>
作用:发送请求并把服务器返回的 JSON 数据转成调用者想要的具体类型。这样上层不用自己手动解析返回值。
数据流:进去的是 ClientRequest 和期望的类型 T → 它记录方法名,调用 request,区分传输错误和服务器错误,再用 serde_json 把返回值转换成 T → 出来 T,或带有方法名的 TypedRequestError。
调用关系:它建立在 RemoteAppServerClient::request 之上,是更方便、更安全的封装。需要强类型结果的上层代码会用它。
调用图:调用 1 个内部函数(request);外部调用 2 个(request_method_name, from_value)。
RemoteAppServerClient::notify515–535 ↗
async fn notify(&self, notification: ClientNotification) -> IoResult<()>
作用:向服务器发送一个通知。通知和请求不同,它只是“告诉你一声”,不期待业务结果,只确认消息有没有写出去。
数据流:进去的是 ClientNotification → 它创建一次性回信通道,把 Notify 命令发给后台 worker → worker 写 WebSocket 后回传成功或失败 → 出来 IoResult<()>。
调用关系:它通过 connect_with_stream 创建的 command_tx 和后台 worker 协作。后台 worker 会把通知转换成 JSON-RPC notification 后发送。
RemoteAppServerClient::resolve_server_request537–562 ↗
async fn resolve_server_request(
&self,
request_id: RequestId,
result: JsonRpcResult,
) -> IoResult<()>
作用:当服务器主动向客户端发来请求时,用这个函数把“成功结果”回给服务器。它相当于接到对方提问后正式作答。
数据流:进去的是服务器请求编号和结果 JSON → 它创建一次性回信通道,把 ResolveServerRequest 命令放进后台 worker 队列 → worker 写出 JSON-RPC response → 出来写入是否成功。
调用关系:上层通常先通过 next_event 收到 AppServerEvent::ServerRequest,再处理,然后调用它回复。后台 worker 负责真正写 WebSocket。
RemoteAppServerClient::reject_server_request564–589 ↗
async fn reject_server_request(
&self,
request_id: RequestId,
error: JSONRPCErrorError,
) -> IoResult<()>
作用:当服务器主动发来的请求无法处理时,用这个函数把错误回给服务器。它告诉对方“这个请求我不能完成,原因是……”。
数据流:进去的是请求编号和 JSON-RPC 错误对象 → 它把 RejectServerRequest 命令发到后台 worker,并等待写入结果 → 出来 IoResult<()>。
调用关系:它和 resolve_server_request 是一对:一个回成功,一个回失败。上层收到服务器请求后根据处理结果选择调用哪一个。
RemoteAppServerClient::next_event591–596 ↗
async fn next_event(&mut self) -> Option<AppServerEvent>
作用:等待远程服务器产生的下一个事件,比如通知、服务器请求或断线消息。界面或会话循环靠它知道远端发生了什么。
数据流:进去的是可变客户端 → 它先检查初始化阶段暂存的 pending_events,有就先吐出来;没有就从事件通道等待后台 worker 送来的事件 → 出来 Option<AppServerEvent>,通道关闭时返回 None。
调用关系:connect_with_stream 会把初始化时提前收到的事件放进 pending_events,后台 worker 后续继续往 event_rx 送事件。上层主循环通常反复调用它。
调用图:外部调用 2 个(recv, pop_front)。
RemoteAppServerClient::shutdown598–624 ↗
async fn shutdown(self) -> IoResult<()>
作用:优雅关闭远程连接和后台任务。它尽量通知 worker 关闭 WebSocket;如果等太久,就强制停止,避免程序卡住。
数据流:进去的是客户端本身,并取得其所有权 → 它关闭事件接收端,发送 Shutdown 命令,限时等待关闭结果,再限时等待 worker 结束;超时就 abort → 出来 IoResult<()>。
调用关系:会话结束或程序退出时调用它。测试 tests::shutdown_tolerates_worker_exit_after_command_is_queued 专门验证 worker 先退出时它也不会失败。
RemoteAppServerRequestHandle::request628–631 ↗
async fn request(&self, request: ClientRequest) -> IoResult<RequestResult>
作用:通过请求把手发送一个 ClientRequest。它让没有完整客户端所有权的代码也能发请求。
数据流:进去的是 ClientRequest → 它先转换成通用 JSONRPCRequest,再调用 request_json_rpc → 出来服务器结果或传输错误。
调用关系:RemoteAppServerRequestHandle::request_typed 会调用它。RemoteAppServerClient::request 也会先创建 handle,再复用这条路径。
调用图:调用 2 个内部函数(request_json_rpc, jsonrpc_request_from_client_request);被 1 处调用(request_typed)。
RemoteAppServerRequestHandle::request_json_rpc633–653 ↗
async fn request_json_rpc(&self, request: JSONRPCRequest) -> IoResult<RequestResult>
作用:发送已经包装好的 JSON-RPC 请求,并按请求编号等待对应响应。它是请求发送最贴近后台 worker 的接口。
数据流:进去的是 JSONRPCRequest → 它创建一次性响应通道,把 Request 命令连同请求和回信通道发给 worker → worker 收到服务器响应后把结果发回来 → 出来 IoResult<RequestResult>。
调用关系:RemoteAppServerRequestHandle::request 会调用它。connect_with_stream 启动的 worker 会接收这里发出的命令、记录待处理请求并写入 WebSocket。
RemoteAppServerRequestHandle::request_typed655–673 ↗
async fn request_typed(&self, request: ClientRequest) -> Result<T, TypedRequestError>
作用:用请求把手发送请求,并把 JSON 返回值转成指定类型。它适合在共享 handle 的场景下获得强类型结果。
数据流:进去的是 ClientRequest 和期望类型 T → 它调用 request,区分网络错误、服务器错误和解析错误 → 出来 T 或 TypedRequestError。
调用关系:它和 RemoteAppServerClient::request_typed 逻辑相同,只是入口是 request handle。需要跨任务发请求时会用它。
调用图:调用 1 个内部函数(request);外部调用 2 个(request_method_name, from_value)。
connect_websocket_endpoint676–737 ↗
async fn connect_websocket_endpoint(
websocket_url: String,
auth_token: Option<String>,
) -> IoResult<(String, WebSocketStream<MaybeTlsStream<TcpStream>>)>
作用:连接一个普通 WebSocket 或加密 WebSocket 地址,并可选地带上认证令牌。它负责把 URL 变成真正可读写的 WebSocket 流。
数据流:进去的是 websocket_url 和可选 auth_token → 它解析 URL,检查令牌是否允许使用,必要时加 Authorization 头,设置 TLS 加密库和消息大小限制,并在超时时间内发起连接 → 出来端点字符串和 WebSocketStream。
调用关系:RemoteAppServerClient::connect 在 endpoint 是 WebSocket 时调用它。它会使用 websocket_url_supports_auth_token 做安全判断,并使用 remote_websocket_config 统一配置消息大小。
调用图:调用 2 个内部函数(remote_websocket_config, websocket_url_supports_auth_token);被 1 处调用(connect);外部调用 7 个(from_str, parse, new, ensure_rustls_crypto_provider, format!, timeout, connect_async_with_config)。
connect_unix_socket_endpoint739–784 ↗
async fn connect_unix_socket_endpoint(
socket_path: AbsolutePathBuf,
) -> IoResult<(String, WebSocketStream<UnixStream>)>
作用:通过本机 Unix socket 连接远程 app-server,然后在这条本地连接上升级成 WebSocket。Unix socket 可以理解为同一台机器里两个程序之间的专用管道。
数据流:进去的是 socket_path → 它生成显示用的 unix:// 地址,连接本地 socket,再用固定握手 URL 完成 WebSocket 握手,并设置消息大小限制 → 出来端点字符串和 WebSocketStream。
调用关系:RemoteAppServerClient::connect 在 endpoint 是 UnixSocket 时调用它。它和 connect_websocket_endpoint 最后都会把 WebSocket 流交给 connect_with_stream。
调用图:调用 3 个内部函数(remote_websocket_config, connect, as_path);被 1 处调用(connect);外部调用 3 个(format!, timeout, client_async_with_config)。
remote_websocket_config786–790 ↗
fn remote_websocket_config() -> WebSocketConfig
作用:给远程连接生成统一的 WebSocket 配置,主要是允许较大的消息。这样大 transcript 或大结果不容易因为默认限制被截断。
数据流:没有输入 → 它从默认 WebSocketConfig 开始,把单帧和整条消息最大大小都设为 128MB → 出来 WebSocketConfig。
调用关系:connect_websocket_endpoint 和 connect_unix_socket_endpoint 都会调用它。它保证两种连接方式使用同一套大小限制。
调用图:被 2 处调用(connect_unix_socket_endpoint, connect_websocket_endpoint);外部调用 1 个(default)。
initialize_remote_connection792–934 ↗
async fn initialize_remote_connection(
stream: &mut WebSocketStream<S>,
endpoint: &str,
params: InitializeParams,
initialize_timeout: Duration,
) -> IoResult<(Vec<AppServerEvent>, Opti
作用:完成连接后的初始化握手。它向服务器发送 initialize 请求,等待对应响应,再发送 initialized 通知,表示双方可以正式开始工作。
数据流:进去的是 WebSocket 流、端点名、初始化参数和超时时间 → 它写出 initialize 请求,在超时内读取服务器消息;如果提前收到通知或服务器请求,就暂存成事件;如果收到初始化响应,就提取服务器版本和 codexHome;最后发 initialized 通知 → 出来暂存事件、服务器版本和 codex_home。
调用关系:connect_with_stream 在启动后台 worker 前调用它。它会使用 jsonrpc_request_from_client_request、write_jsonrpc_message、app_server_event_from_notification 和 jsonrpc_notification_from_client_notification。
调用图:调用 4 个内部函数(app_server_event_from_notification, jsonrpc_notification_from_client_notification, jsonrpc_request_from_client_request, write_jsonrpc_message);被 1 处调用(connect_with_stream);外部调用 13 个(try_from, new, next, ServerRequest, Error, Notification, Request, String, new, other (+3 more))。
app_server_event_from_notification936–941 ↗
fn app_server_event_from_notification(notification: JSONRPCNotification) -> Option<AppServerEvent>
作用:把服务器发来的 JSON-RPC 通知转换成项目内部统一的 AppServerEvent。转换不了的通知会被忽略。
数据流:进去的是 JSONRPCNotification → 它尝试按 ServerNotification 解析 → 成功就包成 AppServerEvent::ServerNotification,失败就返回 None。
调用关系:initialize_remote_connection 会在握手期间用它保存提前到来的通知。后台 worker 读到服务器通知时也用同样思路把协议消息变成上层事件。
调用图:被 1 处调用(initialize_remote_connection);外部调用 2 个(try_from, ServerNotification)。
deliver_event943–953 ↗
fn deliver_event(
event_tx: &mpsc::UnboundedSender<AppServerEvent>,
event: AppServerEvent,
) -> IoResult<()>
作用:把一个事件送进事件通道,交给正在等待 next_event 的上层代码。它把“送不出去”转换成普通 IO 错误。
数据流:进去的是事件发送端和 AppServerEvent → 它尝试发送到无界通道 → 成功返回 Ok,失败说明接收者已经关了,返回 BrokenPipe 错误。
调用关系:connect_with_stream 启动的后台 worker 在收到通知、服务器请求或断线时会用它通知上层。它是 worker 和客户端事件循环之间的小桥。
调用图:外部调用 1 个(send)。
jsonrpc_request_from_client_request955–964 ↗
fn jsonrpc_request_from_client_request(request: ClientRequest) -> JSONRPCRequest
作用:把项目自己的 ClientRequest 转成通用 JSON-RPC 请求。它负责在“业务请求类型”和“线上传输格式”之间换包装。
数据流:进去的是 ClientRequest → 它先序列化成 JSON 值,再反序列化成 JSONRPCRequest → 出来 JSONRPCRequest;如果内部类型定义不匹配会 panic,因为这代表程序自身的协议定义坏了。
调用关系:RemoteAppServerRequestHandle::request 和 initialize_remote_connection 会调用它。前者用于普通请求,后者用于 initialize 握手请求。
调用图:被 2 处调用(request, initialize_remote_connection);外部调用 3 个(panic!, from_value, to_value)。
jsonrpc_notification_from_client_notification966–977 ↗
fn jsonrpc_notification_from_client_notification(
notification: ClientNotification,
) -> JSONRPCNotification
作用:把项目自己的 ClientNotification 转成通用 JSON-RPC 通知。通知没有响应编号,所以和请求分开转换。
数据流:进去的是 ClientNotification → 它先转成 JSON 值,再转成 JSONRPCNotification → 出来可发送的通知;如果转换失败会 panic,表示协议类型定义有问题。
调用关系:initialize_remote_connection 会用它发送 initialized 通知。后台 worker 发送普通 notify 命令时也需要同类转换。
调用图:被 1 处调用(initialize_remote_connection);外部调用 3 个(panic!, from_value, to_value)。
write_jsonrpc_message979–996 ↗
async fn write_jsonrpc_message(
stream: &mut WebSocketStream<S>,
message: JSONRPCMessage,
endpoint: &str,
) -> IoResult<()>
作用:把一条 JSON-RPC 消息写到 WebSocket 上。它是实际“发话”的底层小工具。
数据流:进去的是可写 WebSocket 流、JSONRPCMessage 和端点名 → 它把消息转成 JSON 字符串,再作为 WebSocket 文本帧发送 → 成功返回 Ok,失败返回带端点信息的 IO 错误。
调用关系:initialize_remote_connection 用它发送握手请求和通知。connect_with_stream 的后台 worker 也用同样方式发送请求、响应、错误和通知。
调用图:被 1 处调用(initialize_remote_connection);外部调用 3 个(send, to_string, Text)。
websocket_close_error_is_already_closed998–1007 ↗
fn websocket_close_error_is_already_closed(err: &TungsteniteError) -> bool
作用:判断关闭 WebSocket 时遇到的错误是不是“其实已经关了”。这种情况不该当成真正失败,因为目标状态已经达到。
数据流:进去的是 TungsteniteError → 它检查错误类型是否为连接已关、已关闭,或底层 IO 的 broken pipe、connection reset、not connected → 出来 true 或 false。
调用关系:RemoteAppServerClient::shutdown 的 worker 关闭逻辑会用这个判断来容忍重复关闭。这样退出流程更稳,不会因为对方先断开就报错。
调用图:外部调用 1 个(matches!)。
tests::shutdown_tolerates_worker_exit_after_command_is_queued1013–1032 ↗
async fn shutdown_tolerates_worker_exit_after_command_is_queued()
作用:这是一个测试,确认 shutdown 在后台 worker 收到关闭命令后立刻退出时也能正常完成。它防止以后改代码时把这个边界情况弄坏。
数据流:进去没有外部输入 → 它搭一个假的命令通道、事件通道和 worker,worker 只收一条命令就结束;然后构造 RemoteAppServerClient 并调用 shutdown → 如果没有报错,测试通过。
调用关系:它专门覆盖 RemoteAppServerClient::shutdown 的收尾行为。这个测试不连真实服务器,只验证关闭流程对 worker 先退出的容忍度。
tui/src/app_server_session.rs源码 ↗
TUI 本身不应该到处拼网络请求、记请求编号、猜服务器返回格式。这个文件把这些脏活集中起来:它保存一个 AppServerClient,给每次请求分配编号,把本地配置翻译成服务器参数,再把服务器返回的线程、模型、账号等信息翻译成界面内部好用的状态。可以把它想成餐厅服务员:用户在界面点菜,服务员把话转成厨房能懂的单子,拿到菜后再摆成桌上能吃的样子。它还兼容本地内嵌服务器和远程服务器两种模式,远程模式下有些路径、权限、模型提供方不能照搬本地配置,所以这里会小心取舍。文件里也包含不少测试,专门防止权限、工作目录、服务档位、历史记录等关键细节被改坏。
bootstrap_request_error140–142 ↗
fn bootstrap_request_error(context: &'static str, err: TypedRequestError) -> color_eyre::Report
作用:把启动阶段的请求错误包装成一条带上下文的错误信息。这样用户或日志不会只看到“失败”,还能知道是哪一步失败了。
数据流:进去的是一段固定说明文字和一个请求错误 → 它把两者拼成统一的错误报告 → 出来的是可继续向上传递的错误对象,不改动外部状态。
调用关系:它是启动相关请求的错误翻译器。bootstrap、read_account、启动线程等流程在请求 app server 失败时会用同样的方式给错误加说明。
调用图:外部调用 1 个(eyre!)。
is_thread_settings_update_unsupported144–148 ↗
fn is_thread_settings_update_unsupported(source: &JSONRPCErrorError) -> bool
作用:判断服务器是不是不支持 thread/settings/update 这个改线程设置的方法。它用来区分“老服务器不认识这个功能”和“真的出错了”。
数据流:进去的是服务器返回的 JSON-RPC 错误(JSON-RPC 是一种用 JSON 发请求和响应的协议)→ 它检查错误码和错误文字 → 出来 true 表示可以当作功能不支持,false 表示应当按普通错误处理。
调用关系:AppServerSession::thread_settings_update 调用它。发现老远程服务器不支持时,后续设置更新会安静跳过,避免用户每改一次设置就弹错。
调用图:被 1 处调用(thread_settings_update)。
ThreadParamsMode::model_provider_from_config190–195 ↗
fn model_provider_from_config(self, config: &Config) -> Option<String>
作用:决定开线程时要不要把“模型提供方”从本地配置传给服务器。内嵌服务器需要,本来就在远端的服务器则不需要。
数据流:进去的是线程参数模式和本地配置 → 如果是 Embedded 就取出配置里的 model_provider_id,如果是 Remote 就返回空 → 出来的是可选的模型提供方字符串。
调用关系:thread_start_params_from_config、thread_resume_params_from_config、thread_fork_params_from_config 都靠它统一处理本地和远程的差异。
调用图:被 3 处调用(thread_fork_params_from_config, thread_resume_params_from_config, thread_start_params_from_config)。
AppServerSession::new215–226 ↗
fn new(client: AppServerClient, thread_params_mode: ThreadParamsMode) -> Self
作用:创建一个新的 app server 会话外壳。它把客户端、请求编号、远程路径、可用模型缓存等初始状态准备好。
数据流:进去的是 AppServerClient 和线程参数模式 → 它设置请求编号从 1 开始,清空缓存和远程路径,打开设置更新支持标记 → 出来的是一个可被 TUI 使用的 AppServerSession。
调用关系:TUI 启动、归档命令、选择器和若干测试会先调用它。之后所有 app server 请求基本都从这个会话对象发出。
调用图:被 6 处调用(run_ratatui_app, start_app_server_for_archive_command, start_app_server_for_picker, fork_last_filters_latest_session_by_cwd_unless_show_all, latest_session_lookup_falls_back_for_rollout_missing_from_state_db, lookup_session_target_by_name_uses_backend_title_search);外部调用 2 个(new, new)。
AppServerSession::with_remote_cwd_override228–231 ↗
fn with_remote_cwd_override(mut self, remote_cwd_override: Option<PathBuf>) -> Self
作用:给会话补上远程工作目录覆盖值。远程服务器运行在另一台机器或容器里时,本地路径不一定能直接用。
数据流:进去的是已有会话和一个可选路径 → 它把路径保存到会话里 → 出来的是更新后的会话对象。
调用关系:通常在创建 AppServerSession 后链式调用。后面的启动、恢复、分叉线程参数会通过 thread_cwd_from_config 用到这个路径。
AppServerSession::remote_cwd_override233–235 ↗
fn remote_cwd_override(&self) -> Option<&std::path::Path>
作用:读取当前会话设置的远程工作目录覆盖值。调用方可以用它判断服务器那边应该从哪个目录开始工作。
数据流:进去的是会话自身 → 它查看内部保存的路径 → 出来的是可选的路径引用,不修改状态。
调用关系:启动线程、恢复选择器和分叉选择器会问它。它只是提供信息,真正组装请求在其他函数里完成。
调用图:被 3 处调用(spawn_startup_thread_start, run_fork_picker_with_app_server, run_resume_picker_with_launch_context)。
AppServerSession::uses_remote_workspace237–239 ↗
fn uses_remote_workspace(&self) -> bool
作用:告诉调用方当前线程是不是使用远程工作区。远程工作区意味着路径和权限处理要更谨慎。
数据流:进去的是会话自身 → 它检查 thread_params_mode 是否为 Remote → 出来 true 或 false。
调用关系:外部代理配置迁移提示、最近会话查找、恢复和分叉选择器会用它决定走本地逻辑还是远程逻辑。
调用图:被 4 处调用(handle_external_agent_config_migration_prompt, lookup_latest_session_target_with_app_server, run_fork_picker_with_app_server, run_resume_picker_with_launch_context);外部调用 1 个(matches!)。
AppServerSession::uses_embedded_app_server241–243 ↗
fn uses_embedded_app_server(&self) -> bool
作用:判断当前 app server 是不是嵌在本进程里的。内嵌服务器和远程服务器在能力和路径处理上不完全一样。
数据流:进去的是会话自身 → 它查看 client 的具体类型 → 出来 true 表示内嵌,false 表示不是。
调用关系:外部代理配置迁移提示会用它决定哪些迁移体验可用。
调用图:被 1 处调用(handle_external_agent_config_migration_prompt);外部调用 1 个(matches!)。
AppServerSession::codex_home_path245–250 ↗
fn codex_home_path(
&self,
local_codex_home: &AbsolutePathBuf,
) -> Option<AppServerPath>
作用:把本地 Codex 主目录转换成 app server 侧能理解的路径表示。这样编辑目标或草稿时不会搞错文件位置。
数据流:进去的是本地绝对路径 → 它交给 client 做本地/远程路径映射 → 出来的是可选的 AppServerPath。
调用关系:线程目标编辑器和目标草稿保存会用它。真正的路径转换细节由 AppServerClient 负责。
调用图:调用 1 个内部函数(codex_home);被 2 处调用(open_thread_goal_editor, set_thread_goal_draft)。
AppServerSession::server_version252–257 ↗
fn server_version(&self) -> Option<&str>
作用:读取远程 app server 的版本号。只有远程客户端才有这个信息。
数据流:进去的是会话自身 → 如果 client 是 Remote,就取出远程版本字符串;否则返回空 → 不修改状态。
调用关系:主运行流程 run 会调用它,用于展示或兼容判断。
调用图:被 1 处调用(run)。
AppServerSession::bootstrap259–347 ↗
async fn bootstrap(&mut self, config: &Config) -> Result<AppServerBootstrap>
作用:做 TUI 启动时必须的预热工作。它读取账号和模型列表,算出默认模型、登录显示、反馈对象等首屏需要的信息。
数据流:进去的是本地配置 → 它先读账号,再请求模型列表,选择默认模型,缓存可用模型,并根据账号类型整理邮箱、登录方式、套餐和反馈受众 → 出来的是 AppServerBootstrap,记录启动耗时和首屏数据,同时更新会话里的默认模型缓存。
调用关系:run 在启动时调用它。它内部调用 read_account、next_request_id 和 request_typed,把账号和模型两类服务器请求串起来。
调用图:调用 3 个内部函数(request_typed, next_request_id, read_account);被 1 处调用(run);外部调用 2 个(now, plan_type_display_name)。
AppServerSession::read_account353–364 ↗
async fn read_account(&mut self) -> Result<GetAccountResponse>
作用:读取当前账号信息,但不刷新登录令牌。它适合快速知道用户现在以什么方式登录。
数据流:进去的是会话自身 → 它生成请求编号,发送 GetAccount 请求且 refresh_token 为 false → 出来的是账号响应,失败时带上启动阶段的错误说明。
调用关系:bootstrap 用它填充初始界面,get_login_status 用它单独检查登录状态。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 2 处调用(bootstrap, get_login_status)。
AppServerSession::external_agent_config_detect366–375 ↗
async fn external_agent_config_detect(
&mut self,
params: ExternalAgentConfigDetectParams,
) -> Result<ExternalAgentConfigDetectResponse>
作用:让服务器检查外部代理配置,比如 Claude Code 配置是否能迁移。它只是探测,不真正导入。
数据流:进去的是检测参数 → 它生成请求编号并发送 ExternalAgentConfigDetect → 出来的是服务器检测结果,错误会说明发生在 Claude Code 导入检测阶段。
调用关系:handle_external_agent_config_migration_prompt 调用它,先看有哪些东西可迁移,再决定是否提示用户导入。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 1 处调用(handle_external_agent_config_migration_prompt)。
AppServerSession::external_agent_config_import377–406 ↗
async fn external_agent_config_import(
&mut self,
migration_items: Vec<ExternalAgentConfigMigrationItem>,
) -> Result<()>
作用:发起外部代理配置导入,并防止同一时间重复导入。重复导入可能让配置互相覆盖或状态混乱。
数据流:进去的是要迁移的项目列表 → 它先用 AtomicBool(原子布尔值,可安全记录并发状态)标记“导入中”,如果已经在导入就直接报错;然后发送导入请求 → 成功返回空结果,失败会清掉导入中标记并返回错误。
调用关系:handle_external_agent_config_migration_prompt 在用户确认导入时调用它。完成通知到达后,由 consume_external_agent_config_import_completion 清理等待状态。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 1 处调用(handle_external_agent_config_migration_prompt);外部调用 3 个(store, swap, bail!)。
AppServerSession::external_agent_config_import_in_progress408–411 ↗
fn external_agent_config_import_in_progress(&self) -> bool
作用:查询外部代理配置导入是否还在进行。界面可以据此避免重复按钮或重复提示。
数据流:进去的是会话自身 → 它读取原子布尔标记 → 出来 true 表示还有导入结果待完成,false 表示没有。
调用关系:迁移提示流程会调用它,决定是否告诉用户“上一次导入还没结束”。
调用图:被 1 处调用(handle_external_agent_config_migration_prompt);外部调用 1 个(load)。
AppServerSession::consume_external_agent_config_import_completion413–416 ↗
fn consume_external_agent_config_import_completion(&self) -> bool
作用:消费一次“导入完成”的状态。也就是看有没有待处理完成通知,并顺手把标记清掉。
数据流:进去的是会话自身 → 它把导入等待标记从当前值换成 false → 出来的是旧值,true 表示这次确实消费到了完成事件。
调用关系:handle_server_notification_event 收到服务器通知时调用它。它把异步通知和界面状态对上号。
调用图:被 1 处调用(handle_server_notification_event);外部调用 1 个(swap)。
AppServerSession::next_event418–420 ↗
async fn next_event(&mut self) -> Option<AppServerEvent>
作用:从 app server 等待下一条事件。事件可以是线程输出、通知或服务器请求。
数据流:进去的是会话自身 → 它把等待事件的工作交给 client → 出来的是可选的 AppServerEvent,没事件或通道结束时为空。
调用关系:next_thread_settings_updated 等事件等待逻辑会用它。主循环更广义上也依赖 client 的事件流。
调用图:调用 1 个内部函数(next_event);被 1 处调用(next_thread_settings_updated)。
AppServerSession::start_thread423–426 ↗
async fn start_thread(&mut self, config: &Config) -> Result<AppServerStartedThread>
作用:测试用的便捷方法,用当前配置启动一个新线程。正式代码通常会调用带来源参数的版本。
数据流:进去的是配置 → 它把 session_start_source 设为空并转交 start_thread_with_session_start_source → 出来的是新线程状态和已有回合列表。
调用关系:它只在测试编译时存在,是 AppServerSession::start_thread_with_session_start_source 的薄包装。
调用图:调用 1 个内部函数(start_thread_with_session_start_source)。
AppServerSession::start_thread_with_session_start_source428–451 ↗
async fn start_thread_with_session_start_source(
&mut self,
config: &Config,
session_start_source: Option<ThreadStartSource>,
) -> Result<AppServerStartedThread>
作用:启动一个全新的 app server 线程,并把服务器返回的信息变成 TUI 的线程状态。
数据流:进去的是配置和可选启动来源 → 它先根据可用模型修正服务档位,再组装 ThreadStartParams,发 ThreadStart 请求 → 出来的是 AppServerStartedThread,里面有界面状态和服务器返回的 turns。
调用关系:start_fresh_session_with_summary_hint 和测试包装 start_thread 会调用它。它依次使用 session_config_with_effective_service_tier、thread_start_params_from_config、started_thread_from_start_response。
调用图:调用 6 个内部函数(request_typed, next_request_id, session_config_with_effective_service_tier, thread_params_mode, started_thread_from_start_response, thread_start_params_from_config);被 2 处调用(start_fresh_session_with_summary_hint, start_thread)。
AppServerSession::resume_thread453–483 ↗
async fn resume_thread(
&mut self,
config: Config,
thread_id: ThreadId,
) -> Result<AppServerStartedThread>
作用:恢复一个已有线程。它让 TUI 重新接上过去的会话,并恢复历史回合和线程元数据。
数据流:进去的是配置和线程 ID → 它调整服务档位,组装恢复参数,发送 ThreadResume 请求,再尝试读取分叉来源线程的标题 → 出来的是 AppServerStartedThread,且 session 里可能带有父线程标题。
调用关系:run、会话选择、恢复目标和快照刷新会调用它。它把请求交给 app server,再用 started_thread_from_resume_response 做本地状态转换。
调用图:调用 7 个内部函数(request_typed, fork_parent_title_from_app_server, next_request_id, session_config_with_effective_service_tier, thread_params_mode, started_thread_from_resume_response, thread_resume_params_from_config);被 4 处调用(run, attach_live_thread_for_selection, resume_target_session, refresh_snapshot_session_if_needed)。
AppServerSession::fork_thread485–514 ↗
async fn fork_thread(
&mut self,
config: Config,
thread_id: ThreadId,
) -> Result<AppServerStartedThread>
作用:从一个已有线程分叉出新线程。分叉就像从历史某条对话复制一份出来继续探索,不影响原线程。
数据流:进去的是配置和源线程 ID → 它组装 ThreadFork 请求,发送给服务器,读取父线程标题,再把响应转换成新线程状态 → 出来的是分叉后的 AppServerStartedThread。
调用关系:run、事件处理和创建 side thread 的流程会调用它。它使用 thread_fork_params_from_config 和 started_thread_from_fork_response。
调用图:调用 7 个内部函数(request_typed, fork_parent_title_from_app_server, next_request_id, session_config_with_effective_service_tier, thread_params_mode, started_thread_from_fork_response, thread_fork_params_from_config);被 3 处调用(run, handle_event, handle_start_side)。
AppServerSession::thread_params_mode516–518 ↗
fn thread_params_mode(&self) -> ThreadParamsMode
作用:返回当前会话使用 Embedded 还是 Remote 参数模式。调用方需要这个信息来决定路径和配置该怎么传。
数据流:进去的是会话自身 → 它返回内部保存的枚举值 → 不修改状态。
调用关系:启动、恢复、分叉和后台启动线程时都会读取它,保证参数组装和会话模式一致。
调用图:被 4 处调用(spawn_startup_thread_start, fork_thread, resume_thread, start_thread_with_session_start_source)。
AppServerSession::session_config_with_effective_service_tier520–544 ↗
fn session_config_with_effective_service_tier(&self, config: &Config) -> Config
作用:根据当前模型和服务器模型列表,算出真正该发给核心的服务档位。服务档位可以理解为速度/配额策略选择。
数据流:进去的是配置 → 它找出当前模型,查询 service_tier_resolution 的建议,复制配置并改写 service_tier 和提示标记 → 出来的是适合本次线程请求的配置副本。
调用关系:启动、恢复、分叉线程前都会调用它。它让用户配置、默认模型和服务器模型能力保持一致。
调用图:调用 1 个内部函数(service_tier_update_for_core);被 3 处调用(fork_thread, resume_thread, start_thread_with_session_start_source);外部调用 1 个(clone)。
AppServerSession::fork_parent_title_from_app_server546–569 ↗
async fn fork_parent_title_from_app_server(
&mut self,
forked_from_id: Option<&str>,
) -> Option<String>
作用:给分叉线程补上父线程标题。这样界面能显示“这是从哪个会话分出来的”。
数据流:进去的是可选父线程 ID 字符串 → 它解析成 ThreadId,解析失败就记警告;成功后调用 thread_read 只读元数据 → 出来的是父线程名称,失败则返回空。
调用关系:resume_thread 和 fork_thread 会调用它。它不让读取父标题失败影响主流程,只记录警告。
调用图:调用 2 个内部函数(from_string, thread_read);被 2 处调用(fork_thread, resume_thread);外部调用 1 个(warn!)。
AppServerSession::thread_list571–580 ↗
async fn thread_list(
&mut self,
params: ThreadListParams,
) -> Result<ThreadListResponse>
作用:向服务器索要线程列表。它用于会话选择、最近会话查找和分页加载。
数据流:进去的是列表查询参数 → 它生成请求编号并发送 ThreadList → 出来的是线程列表响应,失败时说明是 TUI 会话查找阶段失败。
调用关系:最近会话查找、按名字查找、页面加载等流程都会调用它。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 4 处调用(lookup_latest_session_target_with_app_server, lookup_session_target_by_name_with_app_server, load_app_server_page, lookup_session_by_exact_name)。
AppServerSession::thread_loaded_list587–596 ↗
async fn thread_loaded_list(
&mut self,
params: ThreadLoadedListParams,
) -> Result<ThreadLoadedListResponse>
作用:查询服务器当前内存里已经加载的线程 ID。这个列表帮助 TUI 发现自己连接前就已经启动的子代理线程。
数据流:进去的是查询参数 → 它发送 ThreadLoadedList 请求 → 出来的是已加载线程列表响应。
调用关系:backfill_loaded_subagent_threads 调用它,然后再用 thread_read 拉取每个线程的完整信息。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 1 处调用(backfill_loaded_subagent_threads)。
AppServerSession::thread_read598–616 ↗
async fn thread_read(
&mut self,
thread_id: ThreadId,
include_turns: bool,
) -> Result<Thread>
作用:读取某个线程的详细信息,可以选择是否带上全部回合内容。它是查元数据和加载 transcript 的基础接口。
数据流:进去的是线程 ID 和 include_turns 开关 → 它把 ID 转成字符串,发送 ThreadRead 请求 → 出来的是 Thread 对象。
调用关系:会话加载、预览、子线程回填、父线程标题读取等很多地方都会调用它。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 8 处调用(attach_live_thread_for_selection, backfill_loaded_subagent_threads, refresh_agent_picker_thread_liveness, fork_parent_title_from_app_server, lookup_session_target_with_app_server, load_transcript_preview, resolve_session_target, load_session_transcript);外部调用 1 个(to_string)。
AppServerSession::thread_archive618–631 ↗
async fn thread_archive(&mut self, thread_id: ThreadId) -> Result<()>
作用:把某个线程归档。归档通常表示从普通列表里收起来,但不一定删除数据。
数据流:进去的是线程 ID → 它发送 ThreadArchive 请求 → 成功出来空结果,失败带“归档会话失败”的说明。
调用关系:当前线程归档和批量归档命令会调用它。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 2 处调用(archive_current_thread, run_session_archive_action_with_app_server);外部调用 1 个(to_string)。
AppServerSession::thread_delete633–646 ↗
async fn thread_delete(&mut self, thread_id: ThreadId) -> Result<()>
作用:删除某个线程。和归档不同,删除通常更像真正移除会话记录。
数据流:进去的是线程 ID → 它发送 ThreadDelete 请求 → 成功返回空,失败带“删除会话失败”的说明。
调用关系:当前线程删除和归档动作命令会调用它。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 2 处调用(delete_current_thread, run_session_archive_action_with_app_server);外部调用 1 个(to_string)。
AppServerSession::thread_unarchive648–661 ↗
async fn thread_unarchive(&mut self, thread_id: ThreadId) -> Result<Thread>
作用:把已归档线程恢复出来。恢复后界面需要拿到恢复后的线程信息。
数据流:进去的是线程 ID → 它发送 ThreadUnarchive 请求 → 出来的是服务器返回的 Thread。
调用关系:会话归档动作流程会调用它,用于撤销归档。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 1 处调用(run_session_archive_action_with_app_server);外部调用 1 个(to_string)。
AppServerSession::thread_metadata_update_branch663–683 ↗
async fn thread_metadata_update_branch(
&mut self,
thread_id: ThreadId,
branch: String,
) -> Result<ThreadMetadataUpdateResponse>
作用:同步线程元数据里的 Git 分支名。Git 是常见代码版本管理工具,分支名能帮助用户知道会话对应哪个代码状态。
数据流:进去的是线程 ID 和分支名 → 它构造只更新 branch 的元数据请求 → 出来的是服务器的更新响应。
调用关系:事件处理流程 handle_event 在检测到分支变化时调用它。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 1 处调用(handle_event);外部调用 1 个(to_string)。
AppServerSession::thread_settings_update685–716 ↗
async fn thread_settings_update(
&mut self,
params: ThreadSettingsUpdateParams,
) -> Result<()>
作用:把模型、推理强度、个性、模式等线程设置变化发给服务器。它对老服务器做了兼容:不支持时以后就静默跳过。
数据流:进去的是线程设置参数 → 如果已知道服务器不支持就直接成功;否则发送 ThreadSettingsUpdate → 成功返回空;若错误表示方法不支持,就关闭支持标记并返回成功;其他错误才返回失败。
调用关系:send_thread_settings_update 调用它。它借助 is_thread_settings_update_unsupported 判断是否是兼容性问题。
调用图:调用 2 个内部函数(next_request_id, is_thread_settings_update_unsupported);被 1 处调用(send_thread_settings_update)。
AppServerSession::thread_inject_items718–739 ↗
async fn thread_inject_items(
&mut self,
thread_id: ThreadId,
items: Vec<ResponseItem>,
) -> Result<ThreadInjectItemsResponse>
作用:往线程里注入一批响应项。它常用于创建旁路对话或给线程补入上下文。
数据流:进去的是线程 ID 和 ResponseItem 列表 → 它先把每项编码成 JSON 值,再发送 ThreadInjectItems → 出来的是服务器注入响应。
调用关系:handle_start_side 调用它。它负责把内部响应项转成服务器协议能接收的 JSON。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 1 处调用(handle_start_side);外部调用 1 个(to_string)。
AppServerSession::turn_start742–789 ↗
async fn turn_start(
&mut self,
thread_id: ThreadId,
items: Vec<UserInput>,
cwd: PathBuf,
approval_policy: AskForApproval,
approvals_reviewer: codex_pro
作用:开始一个新的用户回合,也就是把用户输入交给模型处理。这里会同时带上目录、权限、模型、服务档位、输出格式等运行条件。
数据流:进去的是线程 ID、用户输入、工作目录、审批策略、权限覆盖、工作区根目录、模型和若干可选设置 → 它把权限覆盖翻译成 sandbox 或权限配置,组装 TurnStartParams 并发送 → 出来的是 TurnStartResponse。
调用关系:try_submit_active_thread_op_via_app_server 在用户提交消息时调用它。它依赖 turn_permissions_overrides 处理权限细节。
调用图:调用 3 个内部函数(request_typed, next_request_id, turn_permissions_overrides);被 1 处调用(try_submit_active_thread_op_via_app_server);外部调用 4 个(as_path, into, to_string, to_vec)。
AppServerSession::turn_interrupt791–808 ↗
async fn turn_interrupt(
&mut self,
thread_id: ThreadId,
turn_id: String,
) -> std::result::Result<(), TypedRequestError>
作用:中断正在运行的某个回合。用户点停止或切换操作时需要它。
数据流:进去的是线程 ID 和回合 ID → 它发送 TurnInterrupt 请求 → 成功返回空,失败直接返回 TypedRequestError。
调用关系:主动中断 side thread、提交操作时取消当前任务,以及 startup_interrupt 都会用它。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 3 处调用(interrupt_side_thread, try_submit_active_thread_op_via_app_server, startup_interrupt);外部调用 1 个(to_string)。
AppServerSession::startup_interrupt810–815 ↗
async fn startup_interrupt(
&mut self,
thread_id: ThreadId,
) -> std::result::Result<(), TypedRequestError>
作用:中断启动阶段的回合。它用空字符串当回合 ID,表示特殊的启动中断场景。
数据流:进去的是线程 ID → 它调用 turn_interrupt,并传入空 turn_id → 出来的是中断成功或失败。
调用关系:启动线程还没完全进入普通回合时,interrupt_side_thread 和提交操作流程会调用它。
调用图:调用 1 个内部函数(turn_interrupt);被 2 处调用(interrupt_side_thread, try_submit_active_thread_op_via_app_server);外部调用 1 个(new)。
AppServerSession::turn_steer817–837 ↗
async fn turn_steer(
&mut self,
thread_id: ThreadId,
turn_id: String,
items: Vec<UserInput>,
) -> std::result::Result<TurnSteerResponse, TypedRequestError>
作用:给正在进行的回合追加引导输入。可以理解为模型还在做事时,用户又补了一句方向。
数据流:进去的是线程 ID、期望的回合 ID 和新输入 → 它发送 TurnSteer 请求并指定 expected_turn_id → 出来的是服务器转向响应或请求错误。
调用关系:try_submit_active_thread_op_via_app_server 在需要“继续引导当前回合”时调用它。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 1 处调用(try_submit_active_thread_op_via_app_server);外部调用 1 个(to_string)。
AppServerSession::thread_set_name839–857 ↗
async fn thread_set_name(
&mut self,
thread_id: ThreadId,
name: String,
) -> Result<()>
作用:给线程改名。这样会话列表里显示的标题可以更清楚。
数据流:进去的是线程 ID 和新名字 → 它发送 ThreadSetName 请求 → 成功返回空,失败带 thread/name/set 的错误说明。
调用关系:用户在界面改会话名时,try_submit_active_thread_op_via_app_server 会调用它。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 1 处调用(try_submit_active_thread_op_via_app_server);外部调用 1 个(to_string)。
AppServerSession::thread_memory_mode_set859–877 ↗
async fn thread_memory_mode_set(
&mut self,
thread_id: ThreadId,
mode: ThreadMemoryMode,
) -> Result<()>
作用:设置某个线程的记忆模式。记忆模式决定线程怎样使用或保留长期信息。
数据流:进去的是线程 ID 和目标记忆模式 → 它发送 ThreadMemoryModeSet 请求 → 成功返回空。
调用关系:update_memory_settings_with_app_server 在用户修改记忆设置时调用它。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 1 处调用(update_memory_settings_with_app_server);外部调用 1 个(to_string)。
AppServerSession::memory_reset879–890 ↗
async fn memory_reset(&mut self) -> Result<()>
作用:重置用户记忆。它用于清空服务器侧保存的记忆状态。
数据流:进去的是会话自身 → 它发送 MemoryReset 请求,没有额外参数 → 成功返回空。
调用关系:reset_memories_with_app_server 调用它,通常来自用户的重置记忆操作。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 1 处调用(reset_memories_with_app_server)。
AppServerSession::thread_goal_get892–906 ↗
async fn thread_goal_get(
&mut self,
thread_id: ThreadId,
) -> Result<ThreadGoalGetResponse>
作用:读取线程当前目标。目标可以理解为这段会话正在追踪的任务说明和状态。
数据流:进去的是线程 ID → 它发送 ThreadGoalGet 请求 → 出来的是目标信息响应。
调用关系:恢复暂停目标提示、打开目标编辑器、目标菜单和草稿保存都会调用它。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 4 处调用(maybe_prompt_resume_paused_goal_after_resume, open_thread_goal_editor, open_thread_goal_menu, set_thread_goal_draft);外部调用 1 个(to_string)。
AppServerSession::thread_goal_set908–928 ↗
async fn thread_goal_set(
&mut self,
thread_id: ThreadId,
objective: Option<String>,
status: Option<ThreadGoalStatus>,
token_budget: Option<Option<i64>>,
)
作用:设置或更新线程目标。可以改任务描述、状态,也可以调整 token 预算。
数据流:进去的是线程 ID、可选目标文本、可选状态、可选预算 → 它发送 ThreadGoalSet 请求 → 出来的是服务器更新后的响应。
调用关系:目标草稿保存和目标状态切换会调用它。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 2 处调用(set_thread_goal_draft, set_thread_goal_status);外部调用 1 个(to_string)。
AppServerSession::thread_goal_clear930–944 ↗
async fn thread_goal_clear(
&mut self,
thread_id: ThreadId,
) -> Result<ThreadGoalClearResponse>
作用:清空某个线程的目标。用户不再需要目标追踪时会用到。
数据流:进去的是线程 ID → 它发送 ThreadGoalClear 请求 → 出来的是清空目标的响应。
调用关系:clear_thread_goal 和目标草稿流程会调用它。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 2 处调用(clear_thread_goal, set_thread_goal_draft);外部调用 1 个(to_string)。
AppServerSession::logout_account946–957 ↗
async fn logout_account(&mut self) -> Result<()>
作用:让 app server 退出当前账号登录。它是界面“退出登录”动作的服务器侧执行入口。
数据流:进去的是会话自身 → 它发送 LogoutAccount 请求 → 成功返回空,失败带 account/logout 的错误说明。
调用关系:handle_event 在用户触发退出登录时调用它。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 1 处调用(handle_event)。
AppServerSession::thread_unsubscribe959–972 ↗
async fn thread_unsubscribe(&mut self, thread_id: ThreadId) -> Result<()>
作用:取消订阅某个线程的实时事件。这样 TUI 不再接收那个线程的后续输出。
数据流:进去的是线程 ID → 它发送 ThreadUnsubscribe 请求 → 成功返回空。
调用关系:启动线程完成、开新会话、丢弃 side thread、关闭当前线程时会调用它,避免继续收到无关事件。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 4 处调用(handle_startup_thread_started, start_fresh_session_with_summary_hint, discard_side_thread, shutdown_current_thread);外部调用 1 个(to_string)。
AppServerSession::thread_compact_start974–987 ↗
async fn thread_compact_start(&mut self, thread_id: ThreadId) -> Result<()>
作用:请求服务器开始压缩线程上下文。压缩可以把长对话变短,帮助模型继续处理。
数据流:进去的是线程 ID → 它发送 ThreadCompactStart 请求 → 成功返回空。
调用关系:try_submit_active_thread_op_via_app_server 在用户触发压缩时调用它。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 1 处调用(try_submit_active_thread_op_via_app_server);外部调用 1 个(to_string)。
AppServerSession::thread_shell_command989–1007 ↗
async fn thread_shell_command(
&mut self,
thread_id: ThreadId,
command: String,
) -> Result<()>
作用:把一条 shell 命令发给线程执行或记录。shell 命令就是终端里输入的命令。
数据流:进去的是线程 ID 和命令字符串 → 它发送 ThreadShellCommand 请求 → 成功返回空。
调用关系:try_submit_active_thread_op_via_app_server 在用户从界面提交命令时调用它。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 1 处调用(try_submit_active_thread_op_via_app_server);外部调用 1 个(to_string)。
AppServerSession::thread_approve_guardian_denied_action1009–1028 ↗
async fn thread_approve_guardian_denied_action(
&mut self,
thread_id: ThreadId,
event: &GuardianAssessmentEvent,
) -> Result<()>
作用:批准一个被 Auto Review 守护检查拒绝的动作。这里的 guardian 可以理解为安全审查员,默认拦下风险动作。
数据流:进去的是线程 ID 和守护评估事件 → 它先把事件序列化成 JSON,再发送批准请求 → 成功返回空,序列化或请求失败都会返回错误。
调用关系:try_submit_active_thread_op_via_app_server 在用户明确批准被拦动作时调用它。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 1 处调用(try_submit_active_thread_op_via_app_server);外部调用 2 个(to_string, to_value)。
AppServerSession::thread_background_terminals_clean1030–1046 ↗
async fn thread_background_terminals_clean(
&mut self,
thread_id: ThreadId,
) -> Result<()>
作用:清理线程里的后台终端。后台终端如果不清理,可能残留没用的进程或显示项。
数据流:进去的是线程 ID → 它发送 ThreadBackgroundTerminalsClean 请求 → 成功返回空。
调用关系:try_submit_active_thread_op_via_app_server 在用户选择清理后台终端时调用它。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 1 处调用(try_submit_active_thread_op_via_app_server);外部调用 1 个(to_string)。
AppServerSession::thread_rollback1048–1064 ↗
async fn thread_rollback(
&mut self,
thread_id: ThreadId,
num_turns: u32,
) -> Result<ThreadRollbackResponse>
作用:把线程回退若干个回合。它用于撤销最近的对话进展,回到更早状态。
数据流:进去的是线程 ID 和要回退的回合数 → 它发送 ThreadRollback 请求 → 出来的是服务器回退响应。
调用关系:try_submit_active_thread_op_via_app_server 在用户触发回退操作时调用它。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 1 处调用(try_submit_active_thread_op_via_app_server);外部调用 1 个(to_string)。
AppServerSession::review_start1066–1083 ↗
async fn review_start(
&mut self,
thread_id: ThreadId,
target: ReviewTarget,
) -> Result<ReviewStartResponse>
作用:开始一次代码或内容审查。审查结果以 inline(嵌入当前界面)方式返回。
数据流:进去的是线程 ID 和审查目标 → 它发送 ReviewStart 请求,并指定 ReviewDelivery::Inline → 出来的是审查启动响应。
调用关系:try_submit_active_thread_op_via_app_server 在用户发起 review 时调用它。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 1 处调用(try_submit_active_thread_op_via_app_server);外部调用 1 个(to_string)。
AppServerSession::skills_list1085–1094 ↗
async fn skills_list(
&mut self,
params: SkillsListParams,
) -> Result<SkillsListResponse>
作用:向服务器查询可用技能列表。技能可以理解为模型可调用的特定能力或工具说明。
数据流:进去的是技能查询参数 → 它发送 SkillsList 请求 → 出来的是技能列表响应。
调用关系:try_submit_active_thread_op_via_app_server 在需要展示或使用技能列表时调用它。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 1 处调用(try_submit_active_thread_op_via_app_server)。
AppServerSession::reload_user_config1096–1112 ↗
async fn reload_user_config(&mut self) -> Result<()>
作用:要求服务器重新加载用户配置,但不写入任何实际改动。它相当于按一次“重新读取配置”。
数据流:进去的是会话自身 → 它发送 ConfigBatchWrite,请求里 edits 为空且 reload_user_config 为 true → 成功返回空。
调用关系:try_submit_active_thread_op_via_app_server 在需要刷新配置时调用它。
调用图:调用 2 个内部函数(request_typed, next_request_id);被 1 处调用(try_submit_active_thread_op_via_app_server);外部调用 1 个(new)。
AppServerSession::reject_server_request1114–1120 ↗
async fn reject_server_request(
&self,
request_id: RequestId,
error: JSONRPCErrorError,
) -> std::io::Result<()>
作用:拒绝服务器反向发来的请求。服务器有时会问客户端问题,客户端可以用错误回复拒绝。
数据流:进去的是服务器请求 ID 和错误对象 → 它交给 client 发送拒绝响应 → 出来的是 IO 成功或失败。
调用关系:reject_app_server_request 调用它。它是 TUI 回复 app server 的反向通道之一。
调用图:调用 1 个内部函数(reject_server_request);被 1 处调用(reject_app_server_request)。
AppServerSession::resolve_server_request1122–1128 ↗
async fn resolve_server_request(
&self,
request_id: RequestId,
result: serde_json::Value,
) -> std::io::Result<()>
作用:同意并完成服务器反向发来的请求。它把客户端算出的结果回传给服务器。
数据流:进去的是服务器请求 ID 和 JSON 结果 → 它交给 client 发送成功响应 → 出来的是 IO 成功或失败。
调用关系:try_resolve_app_server_request 调用它,和 reject_server_request 正好是一正一反。
调用图:调用 1 个内部函数(resolve_server_request);被 1 处调用(try_resolve_app_server_request)。
AppServerSession::shutdown1130–1132 ↗
async fn shutdown(self) -> std::io::Result<()>
作用:关闭和 app server 的连接或内嵌服务。程序退出或后台任务结束时需要它收尾。
数据流:进去的是会话本身,所有权被消耗 → 它调用 client.shutdown → 出来的是关闭是否成功。
调用关系:run 和页面加载后台任务会在结束时调用它。
调用图:调用 1 个内部函数(shutdown);被 2 处调用(run, spawn_app_server_page_loader)。
AppServerSession::request_handle1134–1136 ↗
fn request_handle(&self) -> AppServerRequestHandle
作用:拿到一个可复制使用的请求句柄。这样其他异步任务也能发请求,而不用独占整个会话对象。
数据流:进去的是会话自身 → 它向 client 要 AppServerRequestHandle → 出来的是请求句柄,不改变会话状态。
调用关系:run、限额重置、连接器、hooks、市场功能、MCP 清单等很多后台功能会调用它。
调用图:调用 1 个内部函数(request_handle);被 29 处调用(run, consume_rate_limit_reset_credit, fetch_connectors_list, fetch_hooks_list, fetch_marketplace_add, fetch_marketplace_remove, fetch_marketplace_upgrade, fetch_mcp_inventory, fetch_plugin_detail, fetch_plugin_install (+15 more))。
AppServerSession::next_request_id1138–1142 ↗
fn next_request_id(&mut self) -> RequestId
作用:生成下一个 JSON-RPC 请求编号。编号让服务器响应能对应回原来的请求。
数据流:进去的是可变会话 → 它取当前整数编号,内部编号加一,再包装成 RequestId::Integer → 出来的是本次请求 ID。
调用关系:几乎所有通过 AppServerSession 发出的请求都会先调用它。
调用图:被 35 处调用(bootstrap, external_agent_config_detect, external_agent_config_import, fork_thread, logout_account, memory_reset, read_account, reload_user_config, resume_thread, review_start (+15 more));外部调用 1 个(Integer)。
start_thread_with_request_handle1145–1164 ↗
async fn start_thread_with_request_handle(
request_handle: AppServerRequestHandle,
config: Config,
thread_params_mode: ThreadParamsMode,
remote_cwd_override: Option<PathBuf>,
) -> Resu
作用:不用完整 AppServerSession,只用请求句柄启动线程。它适合后台启动线程时减少对主会话对象的占用。
数据流:进去的是请求句柄、配置、参数模式和远程目录覆盖 → 它生成一个字符串请求 ID,组装 ThreadStartParams,发请求,再转换成 AppServerStartedThread → 出来的是新线程状态。
调用关系:spawn_startup_thread_start 调用它。它复用 thread_start_params_from_config 和 started_thread_from_start_response。
调用图:调用 3 个内部函数(request_typed, started_thread_from_start_response, thread_start_params_from_config);被 1 处调用(spawn_startup_thread_start);外部调用 2 个(String, format!)。
status_account_display_from_auth_mode1166–1182 ↗
fn status_account_display_from_auth_mode(
auth_mode: Option<AuthMode>,
plan_type: Option<codex_protocol::account::PlanType>,
) -> Option<StatusAccountDisplay>
作用:把服务器报告的登录方式转换成状态栏能显示的账号样式。比如 API key 显示成 API key,ChatGPT 类登录显示成 ChatGPT。
数据流:进去的是可选 AuthMode 和可选套餐类型 → 它按登录方式匹配并转换套餐显示名 → 出来的是可选 StatusAccountDisplay。
调用关系:服务器通知处理和对应测试会调用它,用于保持状态栏账号展示一致。
调用图:被 2 处调用(handle_server_notification_event, status_account_display_from_auth_mode_uses_remapped_plan_labels)。
model_preset_from_api_model1184–1236 ↗
fn model_preset_from_api_model(model: ApiModel) -> ModelPreset
作用:把服务器返回的模型描述转换成 TUI 内部使用的模型预设。内部预设更适合模型选择器和设置界面使用。
数据流:进去的是 ApiModel → 它复制模型名、说明、默认推理强度、服务档位、升级信息、可见性和输入类型等字段,并把嵌套结构转换成内部类型 → 出来的是 ModelPreset。
调用关系:bootstrap 在读取 model/list 后对每个模型调用它。它是服务器模型数据进入 TUI 的翻译层。
approvals_reviewer_override_from_config1238–1242 ↗
fn approvals_reviewer_override_from_config(
config: &Config,
) -> Option<codex_app_server_protocol::ApprovalsReviewer>
作用:从配置里取出审批审查者设置,并转换成 app server 协议类型。审批审查者决定谁来确认敏感动作。
数据流:进去的是配置 → 它读取 config.approvals_reviewer 并转换 → 出来的是 Some(ApprovalsReviewer)。
调用关系:启动、恢复、分叉线程参数组装都会调用它。
调用图:被 3 处调用(thread_fork_params_from_config, thread_resume_params_from_config, thread_start_params_from_config)。
config_request_overrides_from_config1244–1286 ↗
fn config_request_overrides_from_config(
config: &Config,
) -> Option<HashMap<String, serde_json::Value>>
作用:把本地配置里需要临时覆盖的选项打包成服务器请求里的 config 字段。比如推理强度、摘要详细度、个性、网页搜索开关等。
数据流:进去的是配置 → 它创建一个字符串到 JSON 值的表,只放明确需要传的选项,bypass_hook_trust 为真时也放进去 → 出来的是 Some(HashMap)。
调用关系:启动、恢复、分叉线程都会调用它。测试会检查隐式默认 personality 不会被误当成显式覆盖。
调用图:被 4 处调用(config_request_overrides_preserve_implicit_personality_default, thread_fork_params_from_config, thread_resume_params_from_config, thread_start_params_from_config);外部调用 1 个(new)。
service_tier_override_from_config1288–1293 ↗
fn service_tier_override_from_config(config: &Config) -> Option<Option<String>>
作用:把配置里的服务档位转换成请求格式。它还兼容一个旧的“退出 fast default”提示标记。
数据流:进去的是配置 → 如果 config.service_tier 有值,就返回 Some(Some(value));否则如果 fast_default_opt_out 为真,就返回默认请求值;都没有则返回空 → 出来的是可选的双层 Option。
调用关系:启动、恢复、分叉参数组装都用它。双层 Option 用来区分“不改”和“明确设为某值”。
调用图:被 3 处调用(thread_fork_params_from_config, thread_resume_params_from_config, thread_start_params_from_config)。
sandbox_mode_from_permission_profile1295–1318 ↗
fn sandbox_mode_from_permission_profile(
permission_profile: &PermissionProfile,
cwd: &std::path::Path,
) -> Option<codex_app_server_protocol::SandboxMode>
作用:把新的权限配置粗略投影成服务器旧接口能懂的沙盒模式。沙盒就是限制程序能读写哪些文件、能不能联网的安全边界。
数据流:进去的是 PermissionProfile 和当前工作目录 → 它判断是完全放开、外部配置、受管配置;再根据文件系统和网络权限选择 DangerFullAccess、WorkspaceWrite、ReadOnly 或空 → 出来的是可选 SandboxMode。
调用关系:线程启动、恢复、分叉参数在没有 active permissions ID 时会间接用它;多条测试专门覆盖远程工作区的投影规则。
调用图:调用 2 个内部函数(file_system_sandbox_policy, network_sandbox_policy);被 2 处调用(thread_lifecycle_params_forward_explicit_remote_cwd_override_for_remote_sessions, thread_lifecycle_params_omit_cwd_without_remote_override_for_remote_sessions)。
permission_profile_id_from_active_profile1320–1322 ↗
fn permission_profile_id_from_active_profile(active: ActivePermissionProfile) -> String
作用:从当前选中的权限档案里取出 ID。服务器用这个 ID 知道该套用哪套权限规则。
数据流:进去的是 ActivePermissionProfile → 它返回其中的 id 字符串 → 不做额外转换。
调用关系:turn_permissions_overrides 和权限相关测试会调用它。
调用图:被 3 处调用(embedded_turn_permissions_use_active_profile_selection, remote_turn_permissions_preserve_active_profile_selection, turn_permissions_overrides)。
turn_permissions_overrides1324–1351 ↗
fn turn_permissions_overrides(
permissions_override: TurnPermissionsOverride,
cwd: &std::path::Path,
) -> (
Option<codex_app_server_protocol::SandboxPolicy>,
Option<String>,
)
作用:把本回合的权限覆盖选择翻译成服务器参数。它能表示保持原样、选择某个权限档案,或使用旧式沙盒策略。
数据流:进去的是 TurnPermissionsOverride 和当前目录 → Preserve 返回两个空;ActiveProfile 返回权限档案 ID;LegacySandbox 先转成兼容旧策略再返回 sandbox policy → 出来的是 sandbox_policy 和 permissions 两个可选值。
调用关系:turn_start 在每次发用户输入时调用它。测试覆盖三种权限路径,防止权限被错误放大或丢失。
调用图:调用 2 个内部函数(permission_profile_id_from_active_profile, legacy_compatible_permission_profile);被 6 处调用(turn_start, embedded_turn_permissions_select_profile_id_only, embedded_turn_permissions_use_active_profile_selection, legacy_turn_permissions_project_to_sandbox_when_explicitly_overridden, remote_turn_permissions_preserve_active_profile_selection, turn_permissions_preserve_thread_permissions_without_override)。
permissions_selection_from_config1353–1365 ↗
fn permissions_selection_from_config(
config: &Config,
thread_params_mode: ThreadParamsMode,
) -> Option<String>
作用:决定线程级请求是否直接传 active permission profile 的 ID。远程模式不传,因为远程服务器自己掌握权限档案。
数据流:进去的是配置和线程参数模式 → Remote 直接返回空;Embedded 则从本地配置取当前 active 权限档案 ID → 出来的是可选字符串。
调用关系:启动、恢复、分叉参数组装都会调用它,并据此决定是否需要退回旧 sandbox 字段。
调用图:被 3 处调用(thread_fork_params_from_config, thread_resume_params_from_config, thread_start_params_from_config);外部调用 1 个(matches!)。
thread_start_params_from_config1367–1402 ↗
fn thread_start_params_from_config(
config: &Config,
thread_params_mode: ThreadParamsMode,
remote_cwd_override: Option<&std::path::Path>,
session_start_source: Option<ThreadStartSource
作用:把本地配置翻译成服务器启动线程所需的参数。这是新线程请求的主要装配函数。
数据流:进去的是配置、参数模式、远程目录覆盖和启动来源 → 它填入模型、模型提供方、服务档位、工作目录、工作区根、审批、权限、沙盒、配置覆盖、临时线程标记和开发者指令 → 出来的是 ThreadStartParams。
调用关系:AppServerSession::start_thread_with_session_start_source 和 start_thread_with_request_handle 都调用它;测试大量检查它对路径、权限、服务档位和指令的处理。
调用图:调用 7 个内部函数(model_provider_from_config, approvals_reviewer_override_from_config, config_request_overrides_from_config, permissions_selection_from_config, service_tier_override_from_config, thread_cwd_from_config, with_terminal_visualization_instructions);被 8 处调用(start_thread_with_session_start_source, start_thread_with_request_handle, terminal_visualization_instructions_are_gated_for_all_tui_thread_flows, thread_lifecycle_params_forward_config_overrides_and_service_tier, thread_lifecycle_params_forward_explicit_remote_cwd_override_for_remote_sessions, thread_lifecycle_params_omit_cwd_without_remote_override_for_remote_sessions, thread_start_params_can_mark_clear_source, thread_start_params_include_cwd_for_embedded_sessions);外部调用 1 个(default)。
thread_resume_params_from_config1404–1437 ↗
fn thread_resume_params_from_config(
config: Config,
thread_id: ThreadId,
thread_params_mode: ThreadParamsMode,
remote_cwd_override: Option<&std::path::Path>,
) -> ThreadResumeParams
作用:把本地配置翻译成恢复已有线程的服务器参数。它和启动参数类似,但会带上已有线程 ID。
数据流:进去的是配置、线程 ID、参数模式和远程目录覆盖 → 它填入线程 ID、模型、路径、工作区、审批、权限、配置覆盖和开发者指令 → 出来的是 ThreadResumeParams。
调用关系:resume_thread 调用它。测试会用它和启动、分叉参数一起验证远程/本地差异。
调用图:调用 7 个内部函数(model_provider_from_config, approvals_reviewer_override_from_config, config_request_overrides_from_config, permissions_selection_from_config, service_tier_override_from_config, thread_cwd_from_config, with_terminal_visualization_instructions);被 5 处调用(resume_thread, terminal_visualization_instructions_are_gated_for_all_tui_thread_flows, thread_lifecycle_params_forward_config_overrides_and_service_tier, thread_lifecycle_params_forward_explicit_remote_cwd_override_for_remote_sessions, thread_lifecycle_params_omit_cwd_without_remote_override_for_remote_sessions);外部调用 2 个(default, to_string)。
thread_fork_params_from_config1439–1476 ↗
fn thread_fork_params_from_config(
config: Config,
thread_id: ThreadId,
thread_params_mode: ThreadParamsMode,
remote_cwd_override: Option<&std::path::Path>,
) -> ThreadForkParams
作用:把本地配置翻译成分叉线程的服务器参数。它会额外保留基础指令和开发者指令覆盖。
数据流:进去的是配置、源线程 ID、参数模式和远程目录覆盖 → 它填入分叉所需的线程 ID、模型、权限、路径、指令、临时标记和来源 → 出来的是 ThreadForkParams。
调用关系:fork_thread 调用它。测试专门检查分叉时指令覆盖和终端可视化指令是否正确传递。
调用图:调用 7 个内部函数(model_provider_from_config, approvals_reviewer_override_from_config, config_request_overrides_from_config, permissions_selection_from_config, service_tier_override_from_config, thread_cwd_from_config, with_terminal_visualization_instructions);被 6 处调用(fork_thread, terminal_visualization_instructions_are_gated_for_all_tui_thread_flows, thread_fork_params_forward_instruction_overrides, thread_lifecycle_params_forward_config_overrides_and_service_tier, thread_lifecycle_params_forward_explicit_remote_cwd_override_for_remote_sessions, thread_lifecycle_params_omit_cwd_without_remote_override_for_remote_sessions);外部调用 2 个(default, to_string)。
thread_cwd_from_config1478–1489 ↗
fn thread_cwd_from_config(
config: &Config,
thread_params_mode: ThreadParamsMode,
remote_cwd_override: Option<&std::path::Path>,
) -> Option<String>
作用:决定发给服务器的工作目录。内嵌模式用本地 cwd,远程模式只在有覆盖路径时传远程路径。
数据流:进去的是配置、参数模式和可选远程 cwd → Embedded 返回 config.cwd 字符串;Remote 返回远程覆盖路径或空 → 出来的是可选字符串。
调用关系:启动、恢复、分叉参数组装都调用它。它防止把本机路径错误传给远端服务器。
调用图:被 3 处调用(thread_fork_params_from_config, thread_resume_params_from_config, thread_start_params_from_config)。
started_thread_from_start_response1491–1504 ↗
async fn started_thread_from_start_response(
response: ThreadStartResponse,
config: &Config,
thread_params_mode: ThreadParamsMode,
) -> Result<AppServerStartedThread>
作用:把服务器的启动线程响应变成 TUI 能直接使用的结构。它把线程状态和已有回合分开整理。
数据流:进去的是 ThreadStartResponse、配置和参数模式 → 它先转换 session 状态,再取出 response.thread.turns → 出来的是 AppServerStartedThread。
调用关系:start_thread_with_session_start_source 和 start_thread_with_request_handle 调用它。它把具体状态映射交给 thread_session_state_from_thread_start_response。
调用图:调用 1 个内部函数(thread_session_state_from_thread_start_response);被 2 处调用(start_thread_with_session_start_source, start_thread_with_request_handle)。
started_thread_from_resume_response1506–1519 ↗
async fn started_thread_from_resume_response(
response: ThreadResumeResponse,
config: &Config,
thread_params_mode: ThreadParamsMode,
) -> Result<AppServerStartedThread>
作用:把服务器的恢复线程响应变成 TUI 线程结构。恢复时通常会带回历史回合。
数据流:进去的是 ThreadResumeResponse、配置和参数模式 → 它转换 session 状态并保留服务器返回的 turns → 出来的是 AppServerStartedThread。
调用关系:resume_thread 和恢复响应测试会调用它。它依赖 thread_session_state_from_thread_resume_response。
调用图:调用 1 个内部函数(thread_session_state_from_thread_resume_response);被 2 处调用(resume_thread, resume_response_restores_turns_from_thread_items)。
started_thread_from_fork_response1521–1534 ↗
async fn started_thread_from_fork_response(
response: ThreadForkResponse,
config: &Config,
thread_params_mode: ThreadParamsMode,
) -> Result<AppServerStartedThread>
作用:把服务器的分叉线程响应变成 TUI 线程结构。它让新分叉会话能立刻显示和继续操作。
数据流:进去的是 ThreadForkResponse、配置和参数模式 → 它转换 session 状态,附带 turns → 出来的是 AppServerStartedThread。
调用关系:fork_thread 调用它,并把状态映射交给 thread_session_state_from_thread_fork_response。
调用图:调用 1 个内部函数(thread_session_state_from_thread_fork_response);被 1 处调用(fork_thread)。
thread_session_state_from_thread_start_response1536–1566 ↗
async fn thread_session_state_from_thread_start_response(
response: &ThreadStartResponse,
config: &Config,
thread_params_mode: ThreadParamsMode,
) -> Result<ThreadSessionState, String>
作用:从启动线程响应里抽取 TUI 的 ThreadSessionState。ThreadSessionState 是界面记录当前线程状态的核心结构。
数据流:进去的是启动响应、配置和参数模式 → 它先确定该显示哪种权限档案,再把线程 ID、模型、路径、审批、工作区等字段交给通用转换函数 → 出来的是 ThreadSessionState。
调用关系:started_thread_from_start_response 调用它。它复用 display_permission_profile_from_thread_response 和 thread_session_state_from_thread_response。
调用图:调用 2 个内部函数(display_permission_profile_from_thread_response, thread_session_state_from_thread_response);被 1 处调用(started_thread_from_start_response)。
thread_session_state_from_thread_resume_response1568–1607 ↗
async fn thread_session_state_from_thread_resume_response(
response: &ThreadResumeResponse,
config: &Config,
thread_params_mode: ThreadParamsMode,
) -> Result<ThreadSessionState, String>
作用:从恢复线程响应里抽取 TUI 的 ThreadSessionState。它额外照顾老式内嵌响应没有 active permission profile 的情况。
数据流:进去的是恢复响应、配置和参数模式 → 它根据模式和响应字段决定权限显示方式,再把其余字段交给通用转换函数 → 出来的是 ThreadSessionState。
调用关系:started_thread_from_resume_response 调用它。测试用它验证恢复历史、权限和工作区根能正确还原。
调用图:调用 3 个内部函数(from_legacy_sandbox_policy_for_cwd, display_permission_profile_from_thread_response, thread_session_state_from_thread_response);被 1 处调用(started_thread_from_resume_response);外部调用 1 个(matches!)。
thread_session_state_from_thread_fork_response1609–1639 ↗
async fn thread_session_state_from_thread_fork_response(
response: &ThreadForkResponse,
config: &Config,
thread_params_mode: ThreadParamsMode,
) -> Result<ThreadSessionState, String>
作用:从分叉线程响应里抽取 TUI 的 ThreadSessionState。它和启动流程类似,但来源是 fork 响应。
数据流:进去的是分叉响应、配置和参数模式 → 它确定权限显示,然后把线程、模型、路径、审批、工作区等字段交给通用转换 → 出来的是 ThreadSessionState。
调用关系:started_thread_from_fork_response 调用它。
调用图:调用 2 个内部函数(display_permission_profile_from_thread_response, thread_session_state_from_thread_response);被 1 处调用(started_thread_from_fork_response)。
display_permission_profile_from_thread_response1641–1653 ↗
fn display_permission_profile_from_thread_response(
sandbox: &codex_app_server_protocol::SandboxPolicy,
cwd: &std::path::Path,
config: &Config,
thread_params_mode: ThreadParamsMode,
)
作用:决定界面上该显示哪套权限档案。内嵌模式相信本地配置,远程模式从服务器返回的旧沙盒策略反推。
数据流:进去的是服务器 sandbox、cwd、本地配置和参数模式 → Embedded 返回本地有效权限档案;Remote 把 sandbox 转成核心策略再按 cwd 还原成 PermissionProfile → 出来的是用于展示的权限档案。
调用关系:三种线程响应到 session 状态的转换都会调用它。相关测试确保远程和内嵌模式不会混用权限来源。
调用图:调用 2 个内部函数(to_core, from_legacy_sandbox_policy_for_cwd);被 3 处调用(thread_session_state_from_thread_fork_response, thread_session_state_from_thread_resume_response, thread_session_state_from_thread_start_response)。
thread_session_state_from_thread_response1659–1712 ↗
async fn thread_session_state_from_thread_response(
thread_id: &str,
forked_from_id: Option<String>,
thread_name: Option<String>,
rollout_path: Option<PathBuf>,
model: String,
作用:把各种线程响应共有的字段统一组装成 ThreadSessionState。它是启动、恢复、分叉三个流程最后共同经过的转换器。
数据流:进去的是线程 ID、父线程 ID、名称、模型、服务档位、审批、权限、cwd、工作区根、指令来源、推理强度和配置 → 它校验 ID,读取本地消息历史元数据 → 出来的是完整 ThreadSessionState,包含历史日志编号和条目数。
调用关系:启动、恢复、分叉的 session 转换函数都调用它。测试也直接调用它验证历史元数据和 fork 来源 ID。
调用图:调用 2 个内部函数(new, from_string);被 5 处调用(session_configured_populates_history_metadata, session_configured_preserves_fork_source_thread_id, thread_session_state_from_thread_fork_response, thread_session_state_from_thread_resume_response, thread_session_state_from_thread_start_response);外部调用 1 个(history_metadata)。
app_server_rate_limit_snapshots1714–1732 ↗
fn app_server_rate_limit_snapshots(
response: GetAccountRateLimitsResponse,
) -> Vec<RateLimitSnapshot>
作用:把账号限额响应整理成不重复的限额快照列表。限额快照就是当前配额使用情况的一张小报表。
数据流:进去的是 GetAccountRateLimitsResponse → 它先放入主限额,再把按 limit_id 分组的其他限额加入,同时过滤掉和主限额重复的项 → 出来的是 Vec<RateLimitSnapshot>。
调用关系:对应测试验证它会去重。启动后的限额预取或展示逻辑可用它得到干净列表。
调用图:被 1 处调用(app_server_rate_limit_snapshots_deduplicates_top_level_limit_from_map);外部调用 1 个(vec!)。
tests::build_config1762–1768 ↗
tests::rate_limit_snapshot1770–1785 ↗
fn rate_limit_snapshot(limit_id: &str) -> RateLimitSnapshot
作用:测试辅助函数,造一个简单的限额快照。它让限额去重测试更容易读。
数据流:进去的是 limit_id 字符串 → 它填入默认窗口信息和该 limit_id → 出来的是 RateLimitSnapshot。
调用关系:app_server_rate_limit_snapshots_deduplicates_top_level_limit_from_map 调用它构造测试数据。
tests::app_server_rate_limit_snapshots_deduplicates_top_level_limit_from_map1788–1807 ↗
fn app_server_rate_limit_snapshots_deduplicates_top_level_limit_from_map()
作用:测试限额整理函数会去掉和主限额重复的 map 项。否则界面可能显示两条一样的限额。
数据流:进去的是测试内构造的响应 → 它调用 app_server_rate_limit_snapshots → 断言结果只保留主 codex 和另一个 other。
调用关系:它直接覆盖 app_server_rate_limit_snapshots 的关键行为。
调用图:调用 1 个内部函数(app_server_rate_limit_snapshots);外部调用 3 个(from, assert_eq!, rate_limit_snapshot)。
tests::thread_settings_update_compat_detects_unsupported_errors1810–1838 ↗
fn thread_settings_update_compat_detects_unsupported_errors()
作用:测试老服务器不支持 thread/settings/update 的几种错误能被识别。它也确认普通无效线程 ID 不会被误判。
数据流:进去的是测试列出的错误码和消息 → 它逐个调用 is_thread_settings_update_unsupported → 断言结果符合预期。
调用关系:它保护 AppServerSession::thread_settings_update 的兼容降级逻辑。
调用图:外部调用 1 个(assert_eq!)。
tests::thread_start_params_include_cwd_for_embedded_sessions1841–1875 ↗
async fn thread_start_params_include_cwd_for_embedded_sessions()
作用:测试内嵌模式启动线程时会带上本地工作目录和工作区根。内嵌服务器就在本机,所以本地路径是有效的。
数据流:进去的是临时配置 → 它调用 thread_start_params_from_config → 断言 cwd、workspace_roots、权限和模型提供方等字段正确。
调用关系:它覆盖 thread_start_params_from_config 在 Embedded 模式下的行为。
调用图:调用 1 个内部函数(thread_start_params_from_config);外部调用 4 个(assert_eq!, default, default, tempdir)。
tests::thread_start_params_can_mark_clear_source1878–1890 ↗
async fn thread_start_params_can_mark_clear_source()
作用:测试启动线程时可以标记来源为 Clear。这个来源能告诉服务器新会话是由清空动作触发的。
数据流:进去的是默认测试配置和 ThreadStartSource::Clear → 它组装启动参数 → 断言 session_start_source 被保留下来。
调用关系:它保护 thread_start_params_from_config 的来源透传。
调用图:调用 1 个内部函数(thread_start_params_from_config);外部调用 3 个(assert_eq!, tempdir, build_config)。
tests::embedded_turn_permissions_use_active_profile_selection1893–1907 ↗
fn embedded_turn_permissions_use_active_profile_selection()
作用:测试本回合选择 active permission profile 时,会传权限档案 ID 而不是沙盒策略。
数据流:进去的是工作目录和 active 权限档案 → 它调用 turn_permissions_overrides → 断言 sandbox 为空、permissions 是预期 ID。
调用关系:它覆盖 turn_permissions_overrides 的 ActiveProfile 分支。
调用图:调用 3 个内部函数(new, permission_profile_id_from_active_profile, turn_permissions_overrides);外部调用 3 个(assert_eq!, test_path_buf, ActiveProfile)。
tests::embedded_turn_permissions_select_profile_id_only1910–1925 ↗
fn embedded_turn_permissions_select_profile_id_only()
作用:测试选择内置权限档案时只传档案 ID。这样服务器能按统一权限系统处理。
数据流:进去的是工作目录和 workspace 权限档案 → 它调用 turn_permissions_overrides → 断言只得到权限 ID。
调用关系:它和前一个测试一起保护 active profile 的参数形态。
调用图:调用 2 个内部函数(new, turn_permissions_overrides);外部调用 3 个(assert_eq!, test_path_buf, ActiveProfile)。
tests::turn_permissions_preserve_thread_permissions_without_override1928–1936 ↗
fn turn_permissions_preserve_thread_permissions_without_override()
作用:测试 Preserve 模式不会覆盖线程原有权限。用户没改权限时,不应该偷偷改变安全边界。
数据流:进去的是工作目录和 Preserve → 它调用 turn_permissions_overrides → 断言 sandbox_policy 和 permissions 都为空。
调用关系:它覆盖 turn_permissions_overrides 的 Preserve 分支。
调用图:调用 1 个内部函数(turn_permissions_overrides);外部调用 2 个(assert_eq!, test_path_buf)。
tests::legacy_turn_permissions_project_to_sandbox_when_explicitly_overridden1939–1954 ↗
fn legacy_turn_permissions_project_to_sandbox_when_explicitly_overridden()
作用:测试显式使用旧式权限覆盖时,会转换成旧沙盒策略。这样旧协议路径仍然可用。
数据流:进去的是只读权限和工作目录 → 它调用 turn_permissions_overrides → 断言得到只读 sandbox policy,permissions 为空。
调用关系:它覆盖 turn_permissions_overrides 的 LegacySandbox 分支。
调用图:调用 2 个内部函数(read_only, turn_permissions_overrides);外部调用 3 个(assert_eq!, test_path_buf, LegacySandbox)。
tests::remote_turn_permissions_preserve_active_profile_selection1957–1970 ↗
fn remote_turn_permissions_preserve_active_profile_selection()
作用:测试远程场景下 active profile 选择也会保留为权限 ID。远程服务器可以按自己的权限档案解释这个 ID。
数据流:进去的是远程风格的 active 权限档案和工作目录 → 它调用 turn_permissions_overrides → 断言返回相同 ID 且没有 sandbox。
调用关系:它保护远程权限选择不会退回粗糙沙盒。
调用图:调用 3 个内部函数(new, permission_profile_id_from_active_profile, turn_permissions_overrides);外部调用 3 个(assert_eq!, test_path_buf, ActiveProfile)。
tests::thread_lifecycle_params_omit_cwd_without_remote_override_for_remote_sessions1973–2028 ↗
async fn thread_lifecycle_params_omit_cwd_without_remote_override_for_remote_sessions()
作用:测试远程模式没有远程 cwd 覆盖时,不会把本地路径传过去。否则远端可能收到一个不存在的路径。
数据流:进去的是默认配置和线程 ID → 它分别组装 start、resume、fork 参数 → 断言 cwd 为空、模型提供方为空、权限 ID 为空,但 sandbox 和工作区根按预期存在。
调用关系:它同时覆盖 thread_start_params_from_config、thread_resume_params_from_config、thread_fork_params_from_config 的 Remote 行为。
调用图:调用 5 个内部函数(new, sandbox_mode_from_permission_profile, thread_fork_params_from_config, thread_resume_params_from_config, thread_start_params_from_config);外部调用 3 个(assert_eq!, tempdir, build_config)。
tests::sandbox_mode_does_not_project_non_cwd_write_roots_for_remote_sessions2031–2057 ↗
fn sandbox_mode_does_not_project_non_cwd_write_roots_for_remote_sessions()
作用:测试远程沙盒投影不会因为某个非当前目录可写就误判为 workspace write。这样不会放大当前项目权限。
数据流:进去的是一个只允许额外路径写入、当前 cwd 只读的权限档案 → 它调用 sandbox_mode_from_permission_profile → 断言结果是 ReadOnly。
调用关系:它保护 sandbox_mode_from_permission_profile 的安全保守规则。
调用图:外部调用 3 个(assert_eq!, test_path_buf, vec!)。
tests::sandbox_mode_projects_cwd_write_for_remote_sessions2060–2087 ↗
fn sandbox_mode_projects_cwd_write_for_remote_sessions()
作用:测试如果当前项目根可写,旧沙盒模式会投影成 WorkspaceWrite。这样远程旧协议仍能表达“项目内可写”。
数据流:进去的是允许项目根写入的权限档案 → 它调用 sandbox_mode_from_permission_profile → 断言结果是 WorkspaceWrite。
调用关系:它覆盖 sandbox_mode_from_permission_profile 的 workspace write 分支。
调用图:外部调用 3 个(assert_eq!, test_path_buf, vec!)。
tests::thread_lifecycle_params_forward_explicit_remote_cwd_override_for_remote_sessions2090–2133 ↗
async fn thread_lifecycle_params_forward_explicit_remote_cwd_override_for_remote_sessions()
作用:测试远程模式如果明确给了远程 cwd,就会传给服务器。这个路径是远端能理解的路径。
数据流:进去的是配置、线程 ID 和 remote_cwd → 它组装 start、resume、fork 参数 → 断言 cwd 都是 remote_cwd,其他远程规则也正确。
调用关系:它保护 thread_cwd_from_config 和三个线程参数组装函数的配合。
调用图:调用 5 个内部函数(new, sandbox_mode_from_permission_profile, thread_fork_params_from_config, thread_resume_params_from_config, thread_start_params_from_config);外部调用 4 个(from, assert_eq!, tempdir, build_config)。
tests::thread_lifecycle_params_forward_config_overrides_and_service_tier2136–2186 ↗
async fn thread_lifecycle_params_forward_config_overrides_and_service_tier()
作用:测试启动、恢复、分叉都会把配置覆盖和服务档位传给服务器。否则用户设置的推理强度、个性、搜索等会丢失。
数据流:进去的是手动设置了多项覆盖的配置 → 它分别组装三类线程参数 → 断言 service_tier 和 config map 完全符合预期。
调用关系:它覆盖 config_request_overrides_from_config、service_tier_override_from_config 以及三种线程参数函数。
调用图:调用 4 个内部函数(new, thread_fork_params_from_config, thread_resume_params_from_config, thread_start_params_from_config);外部调用 4 个(from, assert_eq!, tempdir, build_config)。
tests::config_request_overrides_preserve_implicit_personality_default2189–2207 ↗
async fn config_request_overrides_preserve_implicit_personality_default()
作用:测试没有显式设置 personality 时不会把默认值传成覆盖。只有用户明确选了 None 才传。
数据流:进去的是先 personality 为空、后设为 None 的配置 → 它两次调用 config_request_overrides_from_config → 断言前者不含 personality,后者含 personality='none'。
调用关系:它保护配置覆盖的细节,避免默认值被误认为用户选择。
调用图:调用 1 个内部函数(config_request_overrides_from_config);外部调用 4 个(assert!, assert_eq!, tempdir, build_config)。
tests::thread_fork_params_forward_instruction_overrides2210–2229 ↗
async fn thread_fork_params_forward_instruction_overrides()
作用:测试分叉线程时会带上基础指令和开发者指令覆盖。分叉后的模型应继续使用用户指定的指令。
数据流:进去的是带 base_instructions 和 developer_instructions 的配置 → 它调用 thread_fork_params_from_config → 断言两个指令字段都被保留。
调用关系:它覆盖分叉参数独有的指令透传行为。
调用图:调用 2 个内部函数(new, thread_fork_params_from_config);外部调用 3 个(assert_eq!, tempdir, build_config)。
tests::terminal_visualization_instructions_are_gated_for_all_tui_thread_flows2232–2302 ↗
async fn terminal_visualization_instructions_are_gated_for_all_tui_thread_flows()
作用:测试终端可视化指令只在功能开关打开时加入。功能开关没开时,不应悄悄改变发给模型的开发者指令。
数据流:进去的是带开发者指令的配置,先不开功能再开启功能 → 它分别组装 start、resume、fork 参数 → 断言关闭时指令按旧规则,开启时三种流程都追加终端可视化指令。
调用关系:它覆盖 thread_start_params_from_config、thread_resume_params_from_config、thread_fork_params_from_config 与 with_terminal_visualization_instructions 的配合。
调用图:调用 4 个内部函数(new, thread_fork_params_from_config, thread_resume_params_from_config, thread_start_params_from_config);外部调用 4 个(assert_eq!, format!, tempdir, build_config)。
tests::resume_response_restores_turns_from_thread_items2305–2426 ↗
async fn resume_response_restores_turns_from_thread_items()
作用:测试恢复线程时能还原 turns、工作区根、指令来源和权限档案。恢复会话如果丢这些信息,界面会显示错或继续操作错。
数据流:进去的是手工构造的 ThreadResumeResponse → 它调用 started_thread_from_resume_response → 断言 session 字段和 turns 与响应一致,并覆盖远程和内嵌权限差异。
调用关系:它保护 started_thread_from_resume_response 和 thread_session_state_from_thread_resume_response 的核心映射。
调用图:调用 3 个内部函数(read_only, new, started_thread_from_resume_response);外部调用 8 个(new, assert_eq!, test_path_buf, default, default, tempdir, build_config, vec!)。
tests::remote_thread_response_uses_legacy_sandbox_fallback2429–2447 ↗
async fn remote_thread_response_uses_legacy_sandbox_fallback()
作用:测试远程线程响应会从旧沙盒策略还原权限档案。远程服务器可能没有新式 active permission profile 字段。
数据流:进去的是只读 sandbox、cwd 和配置 → 它调用 display_permission_profile_from_thread_response → 断言得到只读 PermissionProfile。
调用关系:它覆盖 display_permission_profile_from_thread_response 的 Remote 分支。
调用图:调用 1 个内部函数(read_only);外部调用 4 个(assert_eq!, test_path_buf, tempdir, build_config)。
tests::embedded_thread_response_uses_local_config_profile2450–2472 ↗
async fn embedded_thread_response_uses_local_config_profile()
作用:测试内嵌线程响应展示权限时优先用本地配置。即使响应 sandbox 看起来很宽,本地配置仍决定界面显示。
数据流:进去的是本地默认只读配置和 DangerFullAccess 响应 sandbox → 它调用 display_permission_profile_from_thread_response → 断言显示为本地只读。
调用关系:它覆盖 display_permission_profile_from_thread_response 的 Embedded 分支。
调用图:外部调用 5 个(assert_eq!, test_path_buf, default, default, tempdir)。
tests::session_configured_populates_history_metadata2475–2516 ↗
async fn session_configured_populates_history_metadata()
作用:测试组装 ThreadSessionState 时会带上消息历史元数据。这样界面能知道历史日志编号和已有条数。
数据流:进去的是临时配置和先写入两条的历史记录 → 它调用 thread_session_state_from_thread_response → 断言 message_history 存在、log_id 非零、entry_count 为 2。
调用关系:它保护 thread_session_state_from_thread_response 读取 codex_message_history 的行为。
调用图:调用 4 个内部函数(new, read_only, new, thread_session_state_from_thread_response);外部调用 7 个(new, assert_eq!, assert_ne!, append_entry, test_path_buf, tempdir, build_config)。
tests::session_configured_preserves_fork_source_thread_id2519–2547 ↗
async fn session_configured_preserves_fork_source_thread_id()
作用:测试组装 session 状态时会保留 fork 来源线程 ID。没有它,界面就不知道当前线程从哪里分叉出来。
数据流:进去的是线程 ID 和 forked_from_id → 它调用 thread_session_state_from_thread_response → 断言 session.forked_from_id 等于原来的父线程 ID。
调用关系:它覆盖 thread_session_state_from_thread_response 对父线程 ID 的解析和保存。
调用图:调用 3 个内部函数(read_only, new, thread_session_state_from_thread_response);外部调用 5 个(new, assert_eq!, test_path_buf, tempdir, build_config)。
tests::status_account_display_from_auth_mode_uses_remapped_plan_labels2550–2574 ↗
fn status_account_display_from_auth_mode_uses_remapped_plan_labels()
作用:测试账号状态栏里的套餐名字会按显示规则重映射。比如某些企业或商业套餐内部枚举名很长,界面要显示成更友好的名字。
数据流:进去的是 ChatGPT 登录方式和两种套餐类型 → 它调用 status_account_display_from_auth_mode → 断言显示文字分别是 Enterprise 和 Business。
调用关系:它保护 status_account_display_from_auth_mode 与 plan_type_display_name 的配合。
调用图:调用 1 个内部函数(status_account_display_from_auth_mode);外部调用 1 个(assert!)。