会话选择器不是列表:Orca 如何守住切换边界

很多终端 Agent 的会话选择器,看起来只是一个列表:上下移动,按 Enter 打开,按 Tab 看几个操作。真正容易出错的地方藏在"打开"之后:当前会话能不能删?Fork 之后屏幕上的历史是谁的?重命名写进磁盘后,运行中的标题会不会又变回去?

我最近重读 Orca v0.3.8 的 TUI,会发现 session picker 更像一个小型状态机。列表只是入口,身份、投影和破坏性操作才是它真正要守的边界。

操作列表先看当前身份

available_session_actions 接收两个值:当前附着的 session id,以及用户选中的 session id。Resume、Fork、Rename 对两者都可用;只有选中的不是当前会话时,Archive 和 Delete 才会加入操作列表;最后再追加 Copy session ID。

这段判断很短,却比在 controller 里收到 Delete 后再拒绝更可靠。用户根本看不到针对当前会话的删除入口,键盘上下移动的索引也只在真实可用的列表里计算。current_session_actions_exclude_archive_and_delete 测试同时检查了两层:动作集合没有 Archive/Delete,连续按 Down 也不会越过最后一个合法动作。

这里有一条值得带走的 UI 原则:破坏性动作的禁用状态要在动作生成处表达,controller 仍然可以保底拒绝,但不能把错误选项先交给用户。

Fork 不是打开旧会话

Resume 和 Fork 都从保存的 session 出发,运行时语义却不同。Resume 让当前 thread 接回原身份;Fork 则创建新身份,并把源会话作为 parent。切换完成后,TUI 必须同时更换运行时 owner 和屏幕上的 projection。

Orca 的 ForkSavedSession 分支现在先调用 switch_saved_hosted_session,再轮换 attached event sender。随后发出 SessionProjectionReset,清掉当前会话的消息、计划和状态;announce_runtime_ready 重新报告新 thread 的身份;emit_typed_history_snapshot 再把 fork 后的历史投影回来。最后才发 SessionForked,让界面更新标题和 session id。

顺序不能随意交换。只发一个 SessionForked 事件,旧 transcript 可能还留在屏幕上;先绘制历史再换 owner,则旧 session 的后台事件可能继续写进新 projection。对应的 picker_fork_replaces_source_transcript 测试明确验证:新会话里能看到 source-only prompt,看不到 current-only prompt,而且 fork id 同时不同于 source id 和原当前会话 id。

标题有两份,写入不是结束

重命名也有类似的双层状态。保存文件里的标题是 durable metadata,TUI 和 runtime surface 里的标题是当前 projection。Orca v0.3.8 的修复把 rename 接到带 revision 的 SessionMetadataPatch::SetTitle:先确认读到的 revision 仍然匹配,再提交持久化变更,成功后发出 SessionRenamed

这解决的是一个很具体的回滚问题:如果只改磁盘,不更新 runtime projection,下一次 announce_runtime_ready 仍可能从旧 snapshot 读标题,把用户刚改的名字重新覆盖掉。hosted_tui_rename_updates_durable_title_and_projection_event 测试把 durable title、revision 和 projection event 放在一次流程里验证;写入失败时,另一个回归测试还要求运行时标题恢复到原值。

把"改名成功"写成一次字符串赋值,会漏掉 revision、失败恢复和下一次 ready event。它其实是一条小型提交协议。

读代码时可以先画三条线

我现在看会话选择器,会先把同一个操作沿三条线展开:

text 复制代码
用户动作 -> 可用动作集合 -> controller dispatch
会话切换 -> 新 runtime owner -> projection reset/history snapshot
元数据写入 -> revision commit -> durable row + typed event

第一条线负责"用户能不能选";第二条线负责"屏幕和会话是不是同一个";第三条线负责"改动能不能在下一次恢复后继续成立"。这三条线都通过,picker 才不只是一个看起来能用的列表。

Orca 的这组修复给我的启发很具体:TUI 里最危险的 bug,通常不是某个按键没有响应,而是动作、身份和投影在不同时间点各自向前走了一步。先把当前 session id 作为动作生成的输入,再用 reset 和 typed snapshot 管住切换,最后用 revision 把标题写回同一个 owner,很多"偶尔显示错了"的问题才有机会变成可测试的不变量。

源码依据:crates/orca-tui/src/session_picker_actions.rs L32-L56、L401-L430;crates/orca-tui/src/app.rs L4102-L4151、L8339-L8381;crates/orca-tui/src/types.rs L448-L459、L2610-L2629;修复提交 c055cd955829b744b8

相关推荐
flash俊杰16 分钟前
多模态视频理解工程:抽帧策略、帧预算与 Prompt 组装
ai编程
李溪白20 分钟前
Function Calling 与 Agent 循环 —— bindTools 不会替你执行工具
agent
云烟成雨TD28 分钟前
LlamaIndex 系列【29】检索增强策略:路由(Routing)机制
ai·agent·rag·llamaindex
初学AI的小高33 分钟前
从LangGraph到DeepAgents:理解AgentHarness
后端·agent
桃西西呀36 分钟前
一句话换个词序意思就全变,大模型是怎么读出门道的?手搓一个 Transformer 看清楚
人工智能·llm·ai编程
flash俊杰43 分钟前
AI 分段引擎与自动剪辑决策:从语义片段到时间线草稿的映射
前端·ai编程
KimLiu43 分钟前
LCODER之AI Agent开发实战一 :问数项目智能体搭建(3)元数据知识库的构建
langchain·llm·agent
AI编码进化论1 小时前
云IDE介绍:环境模板化、Agent上云,云IDE该怎么选
开发语言·ide·人工智能·团队开发·ai编程
flash俊杰1 小时前
LLM 结构化输出的契约化工程:JSON Mode、Schema 约束与重试降级
前端·ai编程