
Terax 终端 PTY Shell 集成架构会话模型、背压流控与跨平台 Shell 引导【免费下载链接】terax-aiLightweight (7MB) Terminal-first AI-native dev workspace项目地址: https://gitcode.com/GitHub_Trending/te/terax-ai本篇技术指南围绕 Terax轻量级 AI 原生终端开发工作台的 PTY shell 集成子系统展开完整讲解其会话模型、三线程输出管道与信用式背压流控、跨 Unix/Windows/WSL 的 Shell 引导方案以及 DA 查询过滤与 AI Agent 检测机制。读完本文你将掌握 Terax 如何做到输出零丢失、子进程不孤儿、裸 Shell 仍可用并能在 src-tauri/src/modules/pty 目录下独立追踪每条实现路径。本文是 TERAX.md架构事实来源的展开说明若与TERAX.md冲突以TERAX.md为准。会话模型一个标签页对应一个 PTY 会话Terax 采用双进程模型详见 two-process-model.mdRust 后端持有全部操作系统访问权PTY 会话由后端统一管理前端只通过invoke()调用注册在 src-tauri/src/lib.rs 中的命令。所有会话保存在PtyState中mod.rspub struct PtyState { sessions: RwLockHashMapu32, ArcSession, next_id: AtomicU32, }关键设计点ID 从 1 开始、单调递增、永不复用。前端约定0表示未设置因此新分配的 ID 永远不会与未设置冲突mod.rs 注释明确说明了这一点。一个终端标签页 一个 PTY 会话。会话一旦创建就与标签页生命周期绑定。pty_open阻塞线程中孵化会话pty_openmod.rs接收cols/rows终端尺寸、cwd工作目录经过工作区授权注册表校验见user_spawn_cwd_or_home、workspaceLocal 或 WSL 发行版、blocks是否启用命令块模式、可选的shell覆盖路径与pane_id以及两个 TauriChannelon_data: ChannelResponse输出数据流on_exit: Channeli32退出码流独立通道与输出解耦。会话在spawn_blocking线程上创建后插入 map 并返回 ID。一个重要的边界处理是Shell 可能在注册前就退出例如 rc 文件中写入了exit此时 waiter 线程的 reap 会因 ID 尚不存在而落空因此插入后立即复查finished标志若已结束则派发独立的 drop 线程清理伪终端避免伪控制台被遗弃mod.rs。pty_write原始字节通道避开 JSON 序列化输入是延迟关键路径。pty_writemod.rs刻意不走 JSON body而是从x-pty-id请求头解析会话 ID将InvokeBody::Raw原始字节直接写入管道在spawn_blocking工作线程上执行write_all保证输入管道满时也不会阻塞确认 IPC。这正是文档强调的原始 body ID 头跳过每一次按键在 IPC 两侧的 JSON 序列化。会话生命周期相关命令mod.rs 还注册了其他 PTY 命令完整目录见 two-process-model.mdpty_resize以PtySize调整 PTY 尺寸pty_close从 map 移除会话并 kill 子进程随后在分离线程上执行drop_session——Windows 上ClosePseudoConsole可能阻塞到 conhost 排空若在当前 Tauri worker 线程同步执行会冻结 IPCmod.rspty_close_allwebview 重新加载后会遗留上一代前端的孤儿会话启动时先统一 reapmod.rspty_has_foreground_process/pty_has_foreground_job检测是否有命令在运行前者用pgrep -PUnix或 Toolhelp 快照Windows统计子进程后者在 Unix 上用tcgetpgrp ! shell pgid做更严格且更廉价的前台任务判断mod.rspty_diagnostics导出每会话的发送/确认/在途字节与队列状态配合前端诊断开关使用。三线程模型Reader / Flusher / Waitersession::spawnsession.rs为每个会话启动三条线程Reader从 PTY master 读取字节依次经过 DA 过滤器DaFilter与 Agent 检测器AgentDetector将过滤后的字节压入 pending 缓冲。Flusher自适应合并输出通过on_data通道发给前端。Waiter等待子进程退出排空 flusher 后发出退出码。输出送达是**无损且信用制credit-based**的前端只有在 Ghostty 同步解析完一个 chunk 后才确认。背压参数与合并策略核心常量定义在 session.rs 与 output.rs常量值含义MAX_BUFFERED_BYTES2 MiBnative pending 在途字节总和上限MAX_IN_FLIGHT_CHUNKS2最多同时 2 个 chunk 在途FLUSH_INTERACTIVE_COALESCE4 ms稀疏输出交互时的合并窗口FLUSH_SUSTAINED_COALESCE16 ms持续输出时对齐显示节拍FLUSH_SUSTAINED_WINDOW40 ms持续输出判定窗口FLUSH_IMMEDIATE_BYTES64 KiB超过此量立即冲刷零延迟READ_BUF16 KiB单次读取缓冲EXIT_ACK_TIMEOUT30 s退出后排空确认的超时上限合并策略见flush_coalesce_delaysession.rs稀疏输出保持 4 ms 低延迟持续输出40 ms 窗口内有输出切换到 16 ms 对齐显示节拍使 IPC 速率无法超过终端呈现的实际意义超过 64 KiB 则立即冲刷。对应的单元测试pty_output_coalescing_preserves_latency_and_bounds_sustained_ipcsession.rs锁定了这三档行为的边界。背压如何工作当 2 MiB 或 2 chunk 任一上限触达时reader 停止从 PTY 排空数据交由操作系统施加背压管道满则子进程阻塞。正常送达与确认重试过程中不会丢弃任何终端字节或残缺转义序列。OutputCreditoutput.rs内部维护sent/acknowledged两个累计计数器与一个 chunk 边界队列。其测试cumulative_acknowledgements_are_idempotent_and_order_independent、invalid_credit_cannot_release_bytes_or_messages验证了确认的幂等性与非法信用无法释放字节/消息两条不变量。pty_ack_output累计确认协议pty_ack_output({ id, bytes })mod.rs携带的是累计已解析字节数而非信用增量语义上等价于到目前为止我已成功解析了 N 字节Rust 侧将计数与已发送 chunk 边界逐一比对OutputCredit::acknowledge在 output.rs 中实现重复确认与过期确认无副作用非法或未来边界被拒绝前端保留未确认的进度对被拒绝的调用以有上限的退避重试未决确认调用至多 2 个卡死的调用或解析器在5 秒后可见前端超时若整个 IPC 连接无响应送达暂停并保留状态而非堆积请求解析器错误永远不返回未消费字节的信用——未解析部分不会被错误释放从而保证零丢失承诺在故障路径下依然成立TERAX.md 亦记载Parser failures stop delivery visibly without acknowledging unconsumed bytes。EOF 与退出码不绕过任何限制几个值得注意的收尾语义均有 session.rs 与 TERAX.md 佐证reader 完成与队列唤醒共享同一把互斥锁OutputQueue的state: Mutexchanged: Condvar杜绝遗漏 EOF 通知子进程退出与 EOF都不会绕过字节上限或消息上限退出码只在最终解析确认之后发出begin_exit先武装 30 秒排空截止再 join reader/flusher注册finished标志检查的是排空完成而非子进程单纯退出避免 reap 掉仍在输出队列存活的会话退出排空期间每次有效确认会续期截止时间见测试progressing_exit_drain_preserves_tail_and_renews_deadlinesession.rs30 秒无确认进展则关闭停滞队列记日志并返回退出码-1Unix 上 shell 退出后 reader 只消费可读输出上限 2 MiB / 30 秒而非无限等待继承的从端描述符关闭。Windows 上waiter 在自己的线程中关闭 ConPTY masterreader 继续读到 EOF包括关闭期间发出的最后一帧显式用户关闭会释放队列等待者并排空多余管道字节直到 EOF。这遵循 ClosePseudoConsole 契约取代了此前 50 ms 尾帧启发式方案。文档明确Windows 运行时测试仍是发布门禁。Shell 引导Unixzsh / bash / fishshell_init::build_commandshell_init.rs根据平台与工作区环境Local 或 WSL构造CommandBuilder。集成脚本全部位于 src-tauri/src/modules/pty/scriptszshenv.zsh、zprofile.zsh、zlogin.zsh、zshrc.zshzsh 四件套bashrc.bashbash 包装init.fishfish安装到~/.config/fish/conf.d/terax.fish。三种 Shell 的注入方式刻意不同Shell注入方式说明zshZDOTDIR指向~/.cache/terax/shell-integration/zsh临时目录先 source Terax 脚本再加载用户真实配置以-l登录 Shell 启动保证 macOS 上/etc/zprofile运行path_helper否则 GUI 启动的应用会拿到缺失 Homebrew 的 PATHshell_init.rsbash--rcfile指向~/.cache/terax/shell-integration/bash/bashrc-ibash 在-l下忽略--rcfile因此在包装脚本内部手动 source/etc/profile等登录初始化来模拟登录态shell_init.rsfishconf.d目录不替换任何用户文件fish -i交互启动脚本写入采用原子替换临时文件 rename保证并行启动的 Shell 永远不会 source 到写了一半的文件write_if_changedshell_init.rs。OSC 7 与 OSC 133不解析提示符即可追踪所有被集成的 Shell 统一发射两类 OSC 序列见 zshrc.zsh 的_terax_precmd/_terax_preexec、bashrc.bash 与 init.fishOSC 7file://host/path当前工作目录。路径按字节做 URL 编码多字节路径也能保持file://URI 合法OSC 133 A/B/C/D提示符边界与退出码。A提示符开始B提示符结束C命令即将执行preexecD命令结束并携带退出码。因此 Terax 无需解析用户提示符即可完成 cwd 追踪与命令边界检测。bash 4.4 用PS0发射 C 标记PS0 只展开不执行比 DEBUG trap 干净得多——后者会覆盖用户自己的 trap 并干扰调试器bash 4.4 无 PS0降级为发射OSC 133;B;terax_blocks0并保留原生提示符见 bashrc.bash。公共环境变量apply_commonshell_init.rs为所有 Shell 注入统一环境变量值用途TERMxterm-256color终端能力声明COLORTERMtruecolor真彩色支持TERAX_TERMINAL1标记 Terax 托管的 Shellfish 脚本据此判断是否注入TERAX_BLOCKS1blocks 模式启用命令块模式TERAX_CONTROL_ADDR/TERAX_CONTROL_TOKEN/TERAX_PANE_ID/TERAX_CLI控制面注入让 Shell 内的terax命令直达 CLI 控制面见 cli-control.mdLANG按平台回退en_US.UTF-8/C.UTF-8未检测到 UTF-8 语言环境时保证脚本字节级编码正确块模式下TERAX_BLOCKS1提示符被整体抑制仅保留 OSC 133 B 标记并预留前导空行首条命令 1 行块头之后每条命令 2 行上一块底部间隙 本块头为冻结的命令块留出垂直呼吸空间——因为网格是 WebGL 渲染空隙必须写在提示符里zshrc.zsh 注释说明。非块模式下则只在 PS1 前注入 B 标记框架p10k、starship重建 PS1 后会重新注入。Shell 引导Windows 与 WSLWindows Shell 优先级Windows 上 Shell 探测优先级固定shell_init.rs 的windows模块pwsh.exePowerShell 7powershell.exeWindows PowerShell 5.1cmd.exe无集成裸启动。PowerShell 通过 profile.ps1 注入启动参数为文档记录的固定形式pwsh -NoLogo -NoExit -ExecutionPolicy Bypass -File profile.ps1profile 在$PROFILE运行完毕后包装用户已有的prompt函数以发射 OSC 7 OSC 133 A/B/D。由于 PowerShell 没有 preexec 钩子OSC 133 C 由PSConsoleHostReadLine包装函数发出在prompt内惰性安装因为 PSReadLine 可能在 profile 之后才加载完且 wrapper 必须以global:作用域定义否则函数返回即丢失。profile 还包含自愈逻辑包装后的 readline 一旦抛错立即恢复原始读取函数绝不会锁死 Shell 输入。ConPTY 对正斜杠的CreateProcessW处理有缺陷因此cwd 在传入 ConPTY 前统一规范化为反斜杠shell_init.rs。Windows 上还额外处理了 Git BashCHERE_INVOKING1防止其/etc/profile把 cwd 弹回$HOME并用正斜杠形式传递--rcfile。WSL跨发行版注入WSL 场景下不再直接 spawn Shell而是构造wsl.exe -d distro --cd cwd --exec ...启动规格build_wsl_launch_specshell_init.rszshenv TERAX_USER_ZDOTDIR探测到的用户 ZDOTDIR ZDOTDIR集成目录 zsh -l先探测发行版内真实 ZDOTDIR 再包装避免 Terax-in-Terax 递归bashbash --rcfile rcfile -ifishenv fish_featuresno-mark-prompt fish -i -C __terax_install_prompt。集成文件写到 WSL 的~/.cache/terax/shell-integration/shell/通过 UNC 路径以 Windows 侧写入再转成 Linux 路径传给 WSL。validate_wsl_distro_name会校验发行版名。对应启动规格的单测见 shell_init.rsbuilds_wsl_zsh_launch_spec_with_env_and_login等。Fish 4.0 特例Fish 4.0 自带 OSC 133 提示符标记mark-prompt特性。为避免双重标记Terax 在 spawn 时设置fish_featuresno-mark-prompt禁用之并在config.fish运行完后再通过-C参数重新断言自己的提示符__terax_install_promptshell_init.rs——-C最后执行因此 starship 等框架提示符在config.fish里覆盖fish_prompt后标记仍能恢复cwd 追踪不会失效。fish 脚本只对TERAX_TERMINAL1的 Shell 生效不会污染用户其他终端init.fish。Windows 并发与进程生命周期CONPTY_LIFECYCLE_LOCK串行化 ConPTY 生命周期openpty spawn_command与对应的 close 由 session.rs 的静态互斥锁串行化#[cfg(windows)] static CONPTY_LIFECYCLE_LOCK: Mutex() Mutex::new(());并发的 ConPTY 生命周期调用会损坏新控制台导致其 Shell 永不泵出输出。因此pty_open与pty_close在分离 drop 线程中都持有该锁。文档将其列为必须守卫的不变量之一。Job Object杜绝孤儿进程每个 ConPTY 子进程被挂到带JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE的 Windows Job ObjectProcessJobsrc-tauri/src/modules/proc/job.rs。当 Job HANDLE 释放时——无论是正常关闭、panic还是 Terax 进程被 SIGKILL——内核会杀死 Shell 的全部后代。没有它TerminateProcess只能杀掉直接子进程pwsh里启动的npm run dev会被孤化。macOS / Linux 上Drop for Sessionsession.rs调用killer.kill()并在#[cfg(unix)]下取消 reader开发期cargo run的 Ctrl-C 仍可能因析构不执行而遗留孤儿文档明确这是开发阶段可接受的并配有针对性的测试drop_kills_child_processsession.rs。输入与转义序列处理DA 过滤器让 PowerShell 不挂起PowerShell / PSReadLine 启动时会发送光标位置查询ESC[6n并阻塞等待应答。DaFilterda_filter.rs在 reader 线程中拦截该查询并直接在 PTY 输入上应答DA1 应答\x1b[?1;2c、DA2 应答\x1b[0;276;0c、启动期 CPR 应答\x1b[1;1R仅在启动期、尚无任何输出时才拦截应答!self.saw_output out.is_empty()避免误吞正常输出流中的 DA/CPR 查询——对应的da_after_output_passes_through、cpr_passes_through_after_output等测试锁定此语义应答只发一次cpr_replied标志HOLD_MAX 256字节上限防止失控的 CSI 序列无限积压runaway_csi_flushes_at_hold_max。Agent 检测只信 OSC不信原始输出reader 线程还跑着AgentDetectoragent_detect.rs。它由两类序列驱动OSC 133;C;cmdshell preexec 发射的完整命令行。检测器将其与已知 Agent 名单匹配DEFAULT_AGENTS [claude, codex, gemini, pi, opencode, grok]按命令行 token 的 basename 匹配claude-enigma这种带横杠后缀的别名也能命中claudexyz不命中命中即武装并发射started自武装的 OSC 777 标记notify;Terax;[agent;]event由 Agent 钩子Claude Code、Codex、Gemini CLI见 agent.rs 的agent_enable_hooks发射驱动working/attention/finished状态PTY 关闭时finish()补发exited避免 UI 残留过期条目。检测只由 OSC 序列驱动绝不看原始输出——因此不断重绘的 TUI 不会在 working/waiting 间抖动。事件经terax:agent-signal事件携带{ id, kind, agent }agent_detect.rs发往前端对应前端状态展示见 AiStatusBarControls.tsx。OSC 9仅在已武装且非任务栏进度9;4时触发 attentionOSC 777 对未知 Agent 名拒绝自武装PTY 输出不可信安全边界在 security-model.md 中展开。回车键\r而非\n终端输入发送的是\rCR而非\nLF——Windows 上的 PowerShell 必须收到 CR 才能正确执行。这是保证跨平台键序一致的最小但关键的约定。不变量Invariants文档明确列出的四条硬性约束改动前必须验证不要移除CONPTY_LIFECYCLE_LOCK除非先在快速开标签页压力下验证首标签稳定性不要在 Windows 上禁用 Job Object除非已有替代的孤儿防护平台专属 Shell 逻辑必须留在 shell_init.rs 对应的#[cfg(unix)]/#[cfg(windows)]分支内传给 ConPTY 的 cwd 必须用反斜杠而 OSC 7 到达前端的 cwd 是正斜杠规范形式。相关文档TERAX.md架构事实来源shell integration、PTY 背压与诊断相关章节two-process-model.mdIPC 边界与完整命令目录cli-control.mdCLI 控制面协议与TERAX_CONTROL_*环境变量的下游消费方security-model.md每个命令必须遵守的安全边界含 shell override 白名单sanitize_shell_override的动机terminal-renderer-pool.md模型归属与呈现池化。进一步阅读源码入口src-tauri/src/modules/pty/mod.rs命令面、src-tauri/src/modules/pty/session.rs三线程与队列、src-tauri/src/modules/pty/output.rs信用协议、src-tauri/src/modules/pty/shell_init.rs跨平台引导、src-tauri/src/modules/pty/scriptsShell 集成脚本本体。【免费下载链接】terax-aiLightweight (7MB) Terminal-first AI-native dev workspace项目地址: https://gitcode.com/GitHub_Trending/te/terax-ai创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考