DSH / Atlas
2026-09-14implementedfeature

Two-hour reclamation of unattended browser terminals

Two-hour reclamation of unattended browser terminals

A browser can disappear without closing its terminal tabs. Keeping every abandoned terminal retains shells, descendant processes and screen buffers indefinitely. Output subscriptions do not identify abandonment: hidden tabs and inactive Sessions legitimately stop following the screen.

English

Problem

A browser can disappear without closing its terminal tabs. Keeping every abandoned terminal retains shells, descendant processes and screen buffers indefinitely. Output subscriptions do not identify abandonment: hidden tabs and inactive Sessions legitimately stop following the screen.

Decision

A browser terminal is reclaimed only after no window holds it and the Host continuously confirms idle activity for two hours. Running work and unknown activity cancel the idle deadline. When work finishes, a fresh full grace period starts; command runtime and output silence never impose a deadline. Explicit tab close and Session or Host disposal retain their authority to terminate work.

SituationBehavior
A connected window holds the tab, including a collapsed sidebar or inactive SessionRetain the terminal.
One of several holding windows disappearsRetain while another window holds it.
The final window disappears while a command is running, stopped, waiting for input, or running in the backgroundPreserve the work, regardless of runtime.
No window holds a positively confirmed idle terminalStart the full grace period and recheck before cleanup.
A window returns before cleanupReattach the same process and cancel reclamation.
The saved process is gonePreserve the tab and show localized unavailability with a New terminal action. Only an explicit click replaces it in place with a fresh identity.

For example, a window disconnecting at 14:00 while a command runs until 20:00 cannot cause reclamation before 22:00. The deadline starts at the first subsequent confirmed idle observation.

The sidebar persists layout per Session and publishes openTabs metadata for saved and adopted Sessions. Startup discovery does not mount dormant content, pin files or activate Agents. Adopted stores are authoritative within their window; another window's storage writes cannot revoke those live holds. Permanent scope removal drops its metadata.

The terminal provider intersects that inventory with its own saved terminal associations and unfinished close requests. Each window uses one retain(sessionId, id, signal) Remote stream per distinct terminal. Its acknowledgement grants a hold without screen output, Agent activation, input control or creation. Restored output waits for an acknowledged current hold. Transport generations cancel independently, and the existing Gateway reconnect and heartbeat mechanisms own transport liveness.

The subprocess seam supplies inspectActivity() with a state and revision. Ordinary non-login Bash 4.4+ and Zsh launches support opt-in lifecycle records, combined with complete process-table observations and original process identities. Zsh distinguishes an empty top-level editor prompt from vared, selection and continuation input. Input invalidates prompt evidence; background and stopped descendants prevent idle. Native Linux also checks the systemd task count so escaped descendants still inside its owned scope remain protected. Custom traps, asynchronous Zsh descriptor handlers, unsupported launches and incomplete observations remain unknown. Lifecycle files are private and disappear after successful cleanup.

The Host terminal controller owns configurable unattendedTimeoutMs (7200000), activityPollIntervalMs (30000) and cleanupRetryMs (60000). Zero disables automatic reclamation only. It uses a monotonic clock; stale observations after gaps longer than twice the polling interval reset the grace period. Input and hold changes invalidate in-flight observations. A successful final check marks the identity closed before asynchronous termination, preventing late creation and admission. Closed identities cannot be reused, and each cleanup remains attached to its original Session owner.

Cleanup awaits provider quiescence and final screen output. Failure retains ownership, rejects new holders and schedules one retry without a new idle grace. Failed allocation cleanup follows the same retry policy. Owner disposal stops timers and streams and joins cleanup and observations before reporting failures, including when cleanup rejects before an observation settles. No Agent terminal tool, model input or Session event changes.

Peer research on Codex thread unloading, OpenCode Location scopes and VS Code PTY grace periods informed separate references and delayed cleanup. The two-hour value is DSH product policy, not an industry default. The browser-terminal decision and layout/provider recovery reference define the resource ownership and persistence separation used here.

Alternatives considered

Count output followers. Switching tabs or Sessions stops output subscriptions without abandoning the corresponding terminal. Window holds track open layout membership instead.

Kill every disconnected terminal after two hours. A hard deadline would kill legitimate long-running commands. Busy and unknown work therefore has no automatic maximum runtime.

Infer idle from silence, low CPU or foreground identity. Silent builds, sleeping jobs, shell builtins and commands waiting for input can satisfy these signals without completing. Positive lifecycle evidence and owned-job observations are both required.

Restore only the visible Session or retain every saved association. The first loses dormant holds after refresh; the second lets obsolete associations keep abandoned processes alive. The layout/provider intersection represents the window's actual tabs.

Consequences

Idle abandoned terminals have bounded retention while connected windows and long-running work survive refreshes and presentation changes. Busy, hung or permanently serving commands may remain indefinitely; so may unsupported shells and uncertain process observations. Explicit close remains available. PowerShell, fish, Windows, custom shell arguments and sandbox-wrapped launches currently report unknown activity.

Root-shell exit does not authorize killing descendants. Failure to enumerate Linux processes is an unavailable observation, not an empty process range; activity stays unknown and cleanup retains ownership. Empty native Linux ranges and complete empty Linux sessions can permit cleanup of exited records. macOS cannot confirm an unobserved range after root exit and may retain its record until explicit close or owner disposal. Existing provider limits on escaped, unobserved descendants remain; the lifecycle record is not a security barrier against hostile same-user processes.

Browser storage failure leaves current memory state usable but cannot guarantee recovery of dormant Sessions after reload. Host restart cannot restore PTYs. Screen history and per-Session terminal quotas retain their existing bounds; no separate unbounded expiry-reason cache is added.

Fake-clock tests cover deadlines, reconnect races, observation invalidation and cleanup retries. Real PTY tests cover silent commands, builtins, background and stopped jobs, Zsh editing modes, startup files and traps. Browser tests cover persistent layouts, multiple windows, collapsed reload, transport loss, retained process identities, explicit replacement of unavailable tabs and reconnection to the original process after transport loss. The keyless recorded Session scenario verifies file-preview layout recovery.

中文

问题

浏览器可能在没有关闭终端标签页的情况下消失。如果始终保留无人使用的终端,shell、后代进程和屏幕缓冲区会无限期占用资源。输出订阅不能代表是否遗弃:隐藏标签和非当前 Session 合理地停止跟随屏幕。

决策

只有没有任何窗口持有终端,并且 Host 持续确认空闲满两小时,才回收浏览器终端。运行中的工作和未知活动都会取消空闲截止时间。工作完成后重新给予完整宽限期;命令执行时长和无输出时间都不是截止条件。明确关闭标签,以及 Session 或 Host 卸载,仍有权终止工作。

情况行为
已连接窗口持有标签,包括侧栏折叠或非当前 Session保留终端。
多个持有窗口中的一个消失只要还有其他窗口持有,就继续保留。
最后一个窗口消失时,命令仍在运行、停止、等待输入或后台执行保留工作,不限制执行时长。
无窗口持有,且明确确认终端空闲开始完整宽限期,清理前再次检查。
窗口在清理前返回重连原进程,取消回收。
保存的进程已经不存在保留标签,显示本地化的不可用提示和“新建终端”。只有明确点击后,才在原位置用全新身份替换。

例如,窗口在 14:00 断开,而命令持续到 20:00,则最早也不能在 22:00 前回收。期限从之后第一次确认空闲的观察开始计算。

侧栏 按 Session 持久化布局,并通过 openTabs 发布已保存和已采用 Session 的标签元数据。启动发现不会挂载非当前内容、pin 文件或激活 Agent。窗口内以已采用的 store 为准;其他窗口对 storage 的写入不能撤销当前窗口的活动持有关系。永久删除 scope 时移除相应元数据。

终端 provider 将该清单与自己保存的终端关联及未完成的关闭请求取交集。每个窗口为每个不同终端使用一条 retain(sessionId, id, signal) Remote 流。确认帧授予持有关系,不发送屏幕输出、不激活 Agent、不接管输入,也不创建进程。恢复输出前等待当前持有关系确认。传输代次独立取消,传输存活由现有 Gateway 重连和心跳机制负责。

subprocess 能力接口提供带状态和 revision 的 inspectActivity()。普通非登录的 Bash 4.4+ 和 Zsh 支持显式启用生命周期记录,并结合完整进程表观察和原始进程身份。Zsh 区分空的顶层编辑提示符与 vared、选择及续行输入。输入使提示符证据失效;后台和停止的后代阻止空闲判断。原生 Linux 还检查 systemd task 数,保护脱离进程树但仍在自有 scope 中的后代。自定义 trap、Zsh 异步文件描述符 handler、不支持的启动方式和不完整观察保持 unknown。生命周期文件为私有文件,在清理成功后删除。

Host 终端控制器 管理可配置的 unattendedTimeoutMs(7200000)、activityPollIntervalMs(30000)和 cleanupRetryMs(60000)。零只禁用自动回收。控制器使用单调时钟;观察间隔超过轮询周期两倍时,旧证据失效并重置宽限期。输入和持有关系变化会使正在进行的观察失效。最终检查通过后,在异步终止前将身份标记为关闭,阻止迟到的创建和连接。已关闭身份不能复用,每个清理任务始终属于原来的 Session owner。

清理等待 provider 范围完全停稳和最终屏幕输出排空。失败时保留所有权、拒绝新持有关系,并安排一次重试,不重新给予空闲宽限期。分配失败后的清理采用相同重试策略。owner 卸载会停止计时器和流,在清理与观察都结束后才报告失败,包括清理早于观察结束而拒绝的情况。Agent 终端工具、模型输入和 Session 事件均不改变。

Codex 线程卸载、OpenCode Location scope 和 VS Code PTY 宽限期 的调研支持了独立引用和延迟清理的设计。两小时是 DSH 的产品策略,不是行业默认值。浏览器终端决策 与布局/provider 恢复参考 定义此处沿用的资源所有权和持久化职责划分。

考虑过的替代方案

统计输出订阅者。 切换标签或 Session 会停止输出订阅,但并未放弃对应终端。窗口持有关系改为追踪布局中打开的标签。

断开满两小时就杀掉所有终端。 固定截止时间会杀掉合理的长命令。因此运行中和未知状态的工作没有自动最长执行时间。

根据静默、低 CPU 或前台身份推断空闲。 静默构建、休眠任务、shell 内建命令及等待输入的命令都可能满足这些条件却尚未完成。必须同时具有正面的生命周期证据和自有任务观察。

只恢复可见 Session,或保留每个保存的关联。 前者在刷新后丢失非当前 Session 的持有关系;后者让过时关联保活无人使用的进程。布局与 provider 的交集才代表该窗口实际打开的标签。

结果与代价

无人使用的空闲终端具有保留期限,而已连接窗口和长任务能跨刷新及呈现变化继续存在。运行中、挂起或长期提供服务的命令可能无限期保留;不支持的 shell 和不确定的进程观察也是如此。仍可明确关闭。PowerShell、fish、Windows、自定义 shell 参数以及经过 sandbox 包装的启动当前返回 unknown。

根 shell 退出不授予终止后代的权限。Linux 进程枚举失败表示观察不可用,而不是进程范围为空;活动保持 unknown,清理保留所有权。明确为空的原生 Linux 范围和完整且为空的 Linux 会话允许清理已退出记录。macOS 无法在根进程退出后确认未观察到的范围,可能保留记录直到明确关闭或 owner 卸载。provider 对已逃逸且未观察到的后代的限制仍然存在;生命周期记录不是用于防御同一用户恶意进程的安全屏障。

浏览器 storage 失败时,当前内存状态仍可使用,但不能保证刷新后恢复非当前 Session。Host 重启不能恢复 PTY。屏幕历史和每个 Session 的终端配额保持原有上限,不新增无限增长的到期原因缓存。

虚拟时钟测试覆盖截止时间、重连竞争、观察失效和清理重试。真实 PTY 测试覆盖静默命令、内建命令、后台和停止任务、Zsh 编辑模式、启动文件及 trap。浏览器测试覆盖持久化布局、多窗口、折叠后刷新、传输中断、保留的进程身份、明确操作后替换失效标签,以及传输中断后重连原进程。无需密钥的已记录 Session 场景验证文件预览布局恢复。