Skip to content

Phase 4: 全屏应用(TUI)兼容性与安全 #532

@pionxe

Description

@pionxe

关联 RFC / 架构

  • 提案/架构 issue:终端诊断设计 (V5 终极落地版 - 严格依从现有网关协议)
  • 对应用户场景:Phase 4 继续补齐终端诊断的真实可用性,包括全屏应用防干扰、诊断响应提速、IDM @ai 可用性与权限安全。
  • 当前问题:作为诊断落地的 Phase 4,排查链路与安全防线(截断、脱敏已在 Phase 2 前置)在前期已跑通,但真实开发环境仍有三类体验风险:
    1. 当用户使用 vimlesstophtop 等接管全屏的交互式 TUI 程序时,如果在后台或退出时触发了非零退出(或遇到中断),Auto 模式的强行插入会瞬间毁掉整个屏幕的字符排版。
    2. 手动诊断、自动诊断、IDM 诊断整体响应偏慢。当前主要等待完整模型诊断结果返回,用户在等待期间缺少即时反馈,导致“诊断服务不可用”的体感。
    3. IDM 模式内 @ai 存在卡死风险:流式等待缺少进展兜底,gateway.run 异常返回后可能仍进入事件等待,agent_done 仅依赖 chunk 缓冲渲染,导致部分场景看似无响应。

目标与成功标准

  • 要解决的用户问题:保障终端交互连续性,同时让诊断服务在不缩短超时预算的前提下更快给出可用反馈,并让 IDM @ai 在 plan 模式下安全、可中断、可恢复。
  • 阶段目标
    • 为 Auto 诊断增加“全屏期抑制 + 退出后一次性保护 + 超时软降级”能力。
    • 为手动/自动诊断增加“快速首响 + 结果复用 + 队列治理”能力,优化平均响应时间,但不修改现有超时常量。
    • 为 IDM @ai 接入 Runtime plan 模式,并补齐 ACK 校验、无进展兜底、done payload 兜底与权限事件自动拒绝回归测试。
  • 成功标准
    • vim/less/top/htop 运行期间,Auto 诊断插播次数为 0。
    • 诊断触发后可以快速显示低置信度预判或处理中状态,完整诊断回来后再替换或补充。
    • 同一错误短时间重复触发时,manual 可复用已有 in-flight 或缓存结果,避免重复等待。
    • IDM @ai 不再无限等待:异常 ACK、无进展、权限请求、取消请求都能稳定结束并恢复 IDM> 提示。
    • plan 模式下 IDM 不增加写权限审批风险,Phase 3 的权限自动拒绝兜底继续有效。
  • 非目标
    • 不重构 Runtime / Gateway / Provider。
    • 不改诊断工具对外 RPC schema。
    • 不修改 diagnoseCallTimeoutautoDiagnoseCallTimeoutdefaultDiagnoseToolTimeoutdiagnoseSubAgentTimeout 等超时预算。
    • 不把诊断工具逻辑上移到 runtimetui
    • 不在本阶段引入跨平台(Windows)行为一致性改造。

技术选型依据

  • 工作线 A:全屏保护:在 ptyproxy 输出解析层(与 OSC133Parser 同层)实现全屏状态追踪与 Auto 触发抑制。
  • 工作线 B:诊断响应提速:在 ptyproxy 诊断调度层增加快速预判、in-flight 合并、短 TTL 结果缓存与 manual/auto 优先级治理。
  • 工作线 C:IDM plan 与防卡死:在 IDM gateway.run 调用中显式传递 mode=plan,并在 waitRunStream 及 run ACK 处理处补齐终止条件。
  • 选择理由
    • 边界稳定:继续遵守 TUI -> Gateway -> Runtime -> Tools 主链路。诊断提速发生在触发与展示层,真实诊断仍走 gateway.executeSystemTool(diagnose)
    • 响应更快:快速首响、本地缓存与 in-flight 合并优化的是平均等待体验,不牺牲长尾超时预算。
    • 权限更稳:IDM 接 plan 后,Runtime 只读阶段会减少写工具暴露;即使出现 permission_requested,现有 IDM 自动拒绝机制仍兜底。
    • 回滚清晰:全屏保护、诊断缓存、IDM plan 都可独立开关或回退,不要求一次性大改。

方案对比与取舍

  • 候选 A:上移到 Runtime 层做全屏判定(放弃)
    • 放弃原因:Runtime 不直接消费 PTY 原始字节流,信息不完备;会引入跨层耦合并破坏职责边界。
  • 候选 B:仅缩短诊断超时预算(放弃)
    • 放弃原因:缩短超时只能更快失败,不能提升诊断服务可用性。用户当前真正缺的是首响、复用与调度治理。
  • 候选 C:让 manual/auto 诊断直接走 Runtime plan 模式(暂不采用)
    • 放弃原因:manual/auto 当前走 executeSystemTool(diagnose),不是 runtime.Run。直接迁移会扩大改动面,也会改变诊断工具确定性执行模型。
  • 候选 D:IDM @ai 默认接 build 模式(放弃)
    • 放弃原因:build 模式可能暴露写工具与审批路径,和 IDM 缺少交互式审批 UI 的现实冲突。plan 模式更符合“诊断与分析优先”的职责。
  • 最终采用
    • 全屏保护继续采用 PTY 输出解析同层状态机。
    • 诊断提速采用“快速首响 + 去重复用 + 调度治理”,不改超时预算。
    • IDM @ai 默认以 mode=plan 运行,并保留自动拒绝权限请求的 Phase 3 安全兜底。

实现设计(How)

  • 关键改动点

    1. 全屏 TUI 逃逸检测 (Alternate Screen Buffer):在 internal/ptyproxy/screen_state.go(或复用既有事件检测层)中加入屏幕切换控制序列检测与状态维护。
      • 控制序列覆盖范围:覆盖 CSI ? 1049 h/lCSI ? 47 h/lCSI ? 1047 h/l,并支持分片输入与 tmux passthrough 场景。
      • I/O 安全约束:仅观测并更新状态,不吞掉原始可见输出字节,确保终端输出完整可见。
    2. Auto 屏蔽规则落地:为 Auto 触发链路新增明确抑制语义。
      • isAltScreen=true 期间,禁止派发 auto diagnose。
      • 检测到从备用屏幕退出后,开启一次性保护窗口,仅屏蔽紧随其后的那次 pending auto 触发,随后自动恢复常态。
      • 仅屏蔽自动诊断,不影响手动 neocode diag / neocode diag -i
    3. 诊断快速首响:在发起真实 diagnose RPC 前,基于本地日志片段做低置信度快速预判。
      • 输入来源:exit_codecommand_text、最近错误日志片段、常见错误模式。
      • 输出约束:必须标记为“快速预判/低置信度”,避免与最终模型诊断混淆。
      • 覆盖示例:command not foundpermission deniedno such file or directorycontext deadline exceededmodule not foundport already in use
      • 最终 diagnose 结果回来后,追加或覆盖快速预判,保证用户得到完整结论。
    4. 诊断 in-flight 合并与短 TTL 缓存:对同一错误短时间重复触发进行复用。
      • fingerprint 建议:hash(command_text + exit_code + sanitized_error_log)
      • manual 触发时,如果同 fingerprint 的 auto 正在运行,manual 直接 join 该 in-flight 结果。
      • 成功或降级结果进入短 TTL 缓存,重复触发时秒回最近结果。
      • 缓存只保存已脱敏、已裁剪后的诊断内容,不保存原始敏感日志。
    5. manual/auto 调度治理:降低自动诊断对手动诊断的排队影响。
      • manual 诊断优先级高于 auto。
      • auto 对相同 fingerprint 做短时间去抖,只保留最近一次。
      • auto 队列满时允许丢弃低价值重复任务,不阻塞 PTY 输出主循环。
      • manual 请求必须返回明确结果或明确失败,不允许被 auto 队列长期挤压。
    6. 诊断输入裁剪优化:减少传给模型的噪音上下文。
      • 优先保留最近一次命令窗口,而非整个 ring buffer。
      • 优先保留 fatalerrorpanicpermissiondeniednot foundtrace 等关键行及其邻近上下文。
      • 保留现有最大 payload 限制,不扩大模型输入。
    7. IDM @ai 接入 plan 模式:在 gateway.runRunParams 中传递 Mode: "plan"
      • 协议层已有 mode 字段,无需新增 Gateway RPC schema。
      • IDM 的 terminal-diagnosis skill 继续强调只基于日志分析、不要调用工具、不要输出 plan JSON。
      • 默认策略建议为 plan,保留内部开关方便回退到 build
    8. IDM 防卡死兜底:补齐所有流式运行终止条件。
      • bind_streamgateway.run 都必须校验 ACK / ERROR / 非预期帧。
      • waitRunStream 增加无进展检测:长期没有匹配 run 事件或没有任何输出进展时,取消当前 run 并恢复提示符。
      • agent_done 如 chunk 缓冲为空,应尝试从 done payload 中提取最终文本并渲染。
      • permission_requested 继续调用自动拒绝流程,拒绝后恢复提示符,不进入审批等待死锁。
      • Ctrl+C 取消路径继续调用 gateway.cancel,并确保 mode/currentRunID/streamCancel 被清理。
    9. 只读工具面修正:清理 plan 只读阶段中的可见工具噪音。
      • 当前 todo_write 在只读可见名单里,但权限映射是 write,存在“可见但执行必被拦截”的噪声。
      • 建议在 Phase 4 中修正该可见性,减少 IDM plan 模式下不必要的 tool blocked 错误。
    10. 全链路超时容错与降级:固化现有超时层级,但不调整数值。
      • GatewayRPCClient 基线超时沿用现状。
      • 终端诊断调用保持现状 per-call 覆盖:auto 60s / manual 90s。
      • Runtime diagnose 工具最小超时保持现状。
      • 超时归类为软失败:输出低干扰降级提示,不得导致 PTY 输入输出中断、吞字符或代理强退。
    11. 回滚开关策略(默认决策)
      • 全屏保护:NEOCODE_DIAG_ALTSCREEN_GUARD_DISABLED=1 禁用全屏抑制逻辑。
      • 诊断缓存/合并:提供进程级开关,必要时退回每次独立请求。
      • IDM plan:提供进程级开关,必要时退回 build 或禁用 IDM @ai 的 plan 注入。
      • 后续若进入长期治理,再升级为 config.yaml 配置项并补齐配置迁移与持久化策略。
  • 影响模块

    • ptyproxy:屏幕模式追踪、Auto 触发抑制、诊断快速首响、诊断 in-flight 合并/缓存、manual/auto 调度、IDM run mode 注入与流式防卡死。
    • runtime:仅在必要时补充测试或小范围修正 plan 只读工具可见性,不改变 Runtime 主循环语义。
    • tools:修正只读可见工具列表中的 todo_write 噪音项,诊断工具执行语义不变。
    • gateway client:沿用既有超时与 per-call 覆盖能力,无需新增协议字段。
  • 接口影响说明

    • 公共诊断 RPC schema 无变更。
    • IDM 使用已有 RunParams.Mode 字段,不新增 Gateway RPC 字段。
    • 仅发生 ptyproxy 内部接口/状态扩展,不引入对外 API 破坏性变化。
  • 边界与非目标:本阶段聚焦终端诊断可用性,不涉及安全脱敏重构、核心状态机重写或 Provider 协议扩展。

可观测验收指标

  • 全屏稳定性指标:在 vim/less/top/htop 运行期间,auto diagnose 插播次数 = 0
  • 退出保护指标:退出全屏后仅屏蔽 1 次 pending auto 触发,后续触发恢复常态。
  • 首响指标:manual/auto 诊断触发后可以在完整模型结果前输出快速预判或明确处理中状态。
  • 复用指标:同 fingerprint 重复诊断可以命中 in-flight 或短 TTL 缓存,避免重复 RPC。
  • 队列指标:manual 诊断不被 auto 队列长期阻塞。
  • IDM 稳定性指标@ai run ACK 异常、无事件进展、权限请求、用户取消都能恢复 IDM> 提示。
  • 权限稳定性指标:IDM plan 模式下,permission_requested 不会导致等待死锁;自动拒绝路径继续可用。
  • 超时韧性指标:诊断超时后 shell 不假死,仍可连续输入命令并获得输出。
  • 协议稳定性指标:公共 Gateway RPC 请求/响应 schema 变更数 = 0

任务拆解

  • T1. 备用屏幕状态追踪器开发:监听并解析 CSI ? 1049 h/lCSI ? 47 h/lCSI ? 1047 h/l,维护 isAltScreen 与一次性退出保护窗口状态,覆盖分片输入与 tmux passthrough。
  • T2. Auto 触发抑制接入:在现有 auto trigger 派发前接入状态门禁,确保全屏期间不触发、退出后只屏蔽一次、随后自动恢复。
  • T3. 诊断快速首响:在真实 diagnose RPC 前输出低置信度快速预判或处理中状态,完整结果返回后追加或覆盖。
  • T4. 诊断 fingerprint 与 in-flight 合并:为 manual/auto 诊断增加稳定 fingerprint,同一错误复用正在运行的诊断结果。
  • T5. 短 TTL 诊断结果缓存:缓存已脱敏、已裁剪的结果,支持重复错误快速回放,并提供开关回退。
  • T6. manual/auto 队列治理:manual 优先、auto 去抖、auto 队列满时丢弃低价值重复任务,避免阻塞 PTY 主输出循环。
  • T7. 诊断输入裁剪优化:优先保留最近一次命令窗口和关键错误行,减少噪音上下文。
  • T8. IDM @ai run ACK 校验bind_streamgateway.run 均校验 ACK / ERROR / 非预期帧,异常时立即恢复提示符。
  • T9. IDM @ai 无进展兜底:为 waitRunStream 增加进展检测,长期无匹配事件或无输出进展时取消 run 并恢复提示符。
  • T10. IDM agent_done payload 兜底渲染:chunk 缓冲为空时尝试读取 done payload,避免“完成但无显示”。
  • T11. IDM 接入 plan 模式:在 IDM RunParams 中显式传递 Mode: "plan",并保留回退开关。
  • T12. plan 只读工具面清理:修正只读可见工具列表中的 todo_write 噪音,减少 IDM plan 下无意义的 blocked tool 事件。
  • T13. 超时软失败兜底校验:复用现有 RPC 超时控制机制,验证超时后 PTY 输入输出仍可持续读写,不吞字符、不卡死、不异常退出代理。

测试与验证

  • 文档验收测试(评审必答)
    • 为什么不是只缩短超时?
    • 为什么 manual/auto 不直接接 Runtime plan 模式?
    • IDM 接 plan 后权限审批为什么不会卡死?
    • 失败时如何快速回滚?
    • 如何证明方案有效?
  • 全屏保护测试清单(覆盖率目标 100%)
    • 全屏进入/退出与分片输入场景。
    • tmux passthrough 场景。
    • isAltScreen=true 时禁触发 auto。
    • 退出全屏后一次性保护窗口只生效一次。
  • 诊断提速测试清单(覆盖率目标 100%)
    • 快速首响输出为低置信度且不替代最终结果。
    • 同 fingerprint manual join auto in-flight。
    • 短 TTL 缓存命中时可以快速回放。
    • auto 去抖不影响 manual 触发。
    • 缓存内容不包含原始未脱敏日志。
    • 输入裁剪保留关键错误行和邻近上下文。
  • IDM plan 与防卡死测试清单(覆盖率目标 100%)
    • gateway.run ERROR / 非 ACK 返回时不进入无限等待。
    • waitRunStream 无进展时能取消 run 并恢复提示符。
    • agent_done payload fallback 能渲染最终文本。
    • permission_requested 自动拒绝后恢复提示符。
    • Ctrl+C 取消 streaming 后状态清理完整。
    • IDM RunParams.Mode 正确传递 plan
    • plan 模式下不暴露或不鼓励写工具调用。
  • 回归约束清单
    • 不回归现有 OSC133 自动触发链路。
    • 不回归 diag auto on/off/status 行为。
    • 不回归 shouldTerminateShellOnAutoDiagnoseError 的软/硬失败分类。
    • 不回归 Phase 3 IDM 权限自动拒绝行为。
    • 不修改现有超时预算常量。
  • 人工验证 (集成演示)
    • TUI 逃逸测试:进入 neocode shell,使用 vim 打开文件并以 :cq 异常退出,确认终端排版无污染、无突发诊断插播。
    • 诊断首响测试:制造 command not foundpermission deniedno such file 等错误,确认快速预判先出现,最终模型诊断随后补齐。
    • 重复错误复用测试:连续触发相同失败命令,确认第二次及之后可复用 in-flight 或缓存。
    • IDM plan 测试:进入 neocode diag -i,执行 @ai,确认 run mode 为 plan,回复正常显示,无写权限审批等待。
    • IDM 卡死恢复测试:人为制造 run ERROR、无事件进展、权限请求、用户取消,确认均能恢复 IDM> 提示。
    • 超时降级测试:人为制造诊断 RPC 超时,确认仅出现低干扰降级提示,Shell 可继续输入命令并正常输出。

假设与默认决策

  • 当前优先目标是“把 Phase 4 设计理由写清楚”,实现时仍应按最小 PR 分步落地。
  • 文档主语言保持中文,术语与路径沿用现有风格。
  • 实现范围限定在 Unix-like neocode shellptyproxy 路径,Windows 暂不纳入。
  • 诊断提速优化的是首响、复用与平均等待时间,不把超时失败当作可用性提升。
  • manual/auto 诊断保持 executeSystemTool(diagnose) 确定性执行模型,不在本阶段迁移到 runtime.Run
  • IDM @ai 默认使用 plan,因为该模式更贴合诊断分析场景,也更容易控制写权限风险。
  • 所有新增缓存只允许保存脱敏、裁剪后的内容,不保存原始终端日志。

风险与回滚

  • 风险 1:全屏状态机漏判:可能对极小众或深度定制终端模拟器漏判,导致 Auto 诊断在全屏程序运行中途打印,冲乱屏幕排版。
    • 缓解方案:保留 neocode diag auto off 人工兜底路径,提供 Ctrl+L 刷屏重绘建议,持续覆盖常见 shell/tmux 场景。
  • 风险 2:快速预判误导用户:本地模式匹配可能给出低质量判断。
    • 缓解方案:明确标记“快速预判/低置信度”,并以最终模型诊断为准。
  • 风险 3:缓存复用返回过期结论:同类错误短时间内上下文可能变化。
    • 缓解方案:TTL 保持短周期,fingerprint 纳入日志摘要与 exit code,manual 可提示缓存命中来源。
  • 风险 4:IDM plan 输出规划协议噪音:模型可能误输出 plan_spec / summary_candidate
    • 缓解方案:terminal-diagnosis skill 明确禁止输出 plan JSON;必要时在渲染层做降噪。
  • 风险 5:IDM plan 仍出现权限请求:只读工具面或模型行为仍可能触发 permission_requested
    • 缓解方案:保留 Phase 3 自动拒绝路径,并补充回归测试;清理 todo_write 等只读可见噪音。
  • 回滚方案(工程化)
    • 一级回滚:设置 NEOCODE_DIAG_ALTSCREEN_GUARD_DISABLED=1 快速禁用全屏抑制逻辑。
    • 二级回滚:关闭诊断 in-flight 合并与短 TTL 缓存,退回每次独立诊断。
    • 三级回滚:关闭 IDM mode=plan 注入,退回原有 IDM run 行为。
    • 四级回滚:必要时执行版本回退,恢复到 Phase 3 行为。

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions