DSH / Atlas
2026-08-06implementedfeature

Session completion dot in the sidebar

侧边栏会话完成提醒点

A session the operator delegated work to and then left (switched to another conversation) gives no signal when it finishes. Its running indicator stops, but the row then looks identical to any idle session, so the operator must poll the list or discover the finished work late. The pending-interaction amber dot covers sessions that need input, not sessions whose work is simply done.

English

Problem

A session the operator delegated work to and then left (switched to another conversation) gives no signal when it finishes. Its running indicator stops, but the row then looks identical to any idle session, so the operator must poll the list or discover the finished work late. The pending-interaction amber dot covers sessions that need input, not sessions whose work is simply done.

Decision

SessionManager owns a client-side completion-reminder set, a sibling of the pending-interaction bit: a running→idle edge of a session that is not the selected one arms its reminder; select()/selectSubagent() consume it; starting a new run disarms it and its completion re-arms it; removal prunes it. The bit rides SessionListEntrySessionSummary (optional, absent = no reminder) into the workspace browser, whose session and search rows render the existing StateDot done state — running keeps the ongoing spinner, an idle session without a reminder shows nothing — and whose hover card labels the reminder 已完成 / Completed.

The reminder is in-memory and per browser. It survives connection generations — a transport blip does not invalidate "you have not looked yet" — but not a page reload.

Consequences

The sidebar row states become three disjoint signals: green = finished and unviewed, amber = awaiting the operator's input, blue = running. No wire, on-disk, or configuration format changes: SessionSummary.completed is optional, so existing consumers and test fixtures stay valid, and only the workspace browser reads it. The completion edge is detected eagerly at every list mutation and pull (a snapshot-build-time-only pass would collapse two consecutive status frames into one observation and miss the completion).

Alternatives considered

  • Component-local UI state. Rejected because the sidebar unmounts on collapse and multiple surfaces (grouped tree, flat list, search) need the same bit; the manager already owns the running transitions and the selection, so a manager-owned set is the one source all surfaces can project.
  • Event-driven arming from status frames only. Rejected because a list pull can also carry a running→idle transition (a session finished while the refresh was in flight); the reminder is reconciled against every mutation and pull.
  • Persisting the reminder. Rejected because the reminder means "you have not looked at this session yet" in this browser; reload restores the selection and the user is looking at the list again, so a durable bit would only go stale.

中文

Problem

操作者派发任务后切换到其他会话,原会话完成时没有任何信号。运行指示停止后,该行与普通空闲会话看起来完全一样,操作者只能反复查看列表或很晚才发现工作已完成。等待交互的琥珀点只覆盖需要操作者输入的会话,不覆盖「只是干完了活」的会话。

Decision

SessionManager 持有客户端侧的完成提醒集合,与待交互位并列:非当前会话发生 running→idle 边沿时点亮其提醒;select()/selectSubagent() 消费掉提醒;重新开始一轮运行会熄灭提醒并在再次完成时重新点亮;会话被移除时清理提醒。该位经 SessionListEntrySessionSummary(可选字段,缺省 = 无提醒)进入工作区浏览区,其会话行与搜索结果行渲染现有的 StateDot done 状态——运行中仍显示转圈,无提醒的空闲会话不显示任何点——悬停卡片将该提醒标注为「已完成 / Completed」。

提醒仅存在于内存中且按浏览器实例隔离。它跨连接代存活——传输抖动不会使「你还没回来看」失效——但页面刷新后重置。

Consequences

侧边栏行状态成为三个互斥信号:绿 = 已完成且未查看,琥珀 = 等待操作者输入,蓝 = 运行中。协议格式(wire format)、磁盘格式或配置格式均无变更:SessionSummary.completed 为可选字段,现有消费方与 fixture(测试前置数据) 保持有效,只有工作区浏览区读取它。完成边沿在每次列表变更与拉取时即时检测(仅在建快照时检测会把连续两个状态帧折叠为一次观察,从而漏掉完成事件)。

Alternatives considered

  • 组件本地 UI 状态。 已拒绝:侧边栏折叠时会卸载,且多个界面(分组树、扁平列表、搜索)需要同一状态位;manager 本就持有运行状态迁移与选中状态,manager 持有的集合是所有界面都能投影的唯一真源。
  • 仅从状态帧做事件驱动点亮。 已拒绝:列表拉取本身也可能携带 running→idle 迁移(刷新在途时会话已完成);提醒需对每次变更与拉取做对账。
  • 持久化提醒。 已拒绝:提醒的含义是「此浏览器里你还没查看该会话」;刷新会恢复选中状态且用户正看着列表,持久化位只会变得陈旧。