# Agent Note: 终端中的实时独立压缩进度

Status: implemented

[English](2026-07-30-compaction-progress-visibility.md) | 中文

## 问题

独立手动压缩（compaction）在轮次之间运行，此时 agent（智能体）保持空闲。因此，在缓慢的摘要操作期间，TUI 的轮次阶段指示器始终显示普通的 `>` 光标；尝试失败时，由于没有替换检查点落地，也不会产生 transcript（文本记录）行。运行状态显示需要复用现有的状态指示器，不能再设置第二处带动画的状态显示。

进程终止后，持久日志中可能保留未匹配的 `compact/start`。该未匹配标记是有用的恢复证据，但无法证明当前进程中有工作正在运行；若将其回放为进度，恢复后的会话便会永久显示虚假的进度指示。

## 决策

TUI 将实时独立的 `compact/start { turn: null }` 与匹配的 `compact/end` 组成的标记对，作为显示进行中压缩状态的真源。模块局部的 `compacting` 状态记录渲染时钟的起始时间，并独占一个动画定时器。提示词上方的固定行根据该时钟渲染 `Context being compacted <elapsed>`，现有的单格运行状态指示器通过与轮次阶段字形相同的明暗渐变和呼吸律动路径渲染 `⊙`，终端进度标志位会保持活跃，直至标记对闭合。

`runningPhaseGlyph` 负责在轮次阶段字形、`⊙` 和空闲光标之间作出选择。轮次阶段字形的优先级更高，因为带编号的压缩标记对处于运行中的轮次内，该轮次的阶段已经激活指示器。该固定行位于 transcript 之外，不带 spinner，也不另设定时器；内容为空时会折叠。压缩状态不会改变空闲编辑器边框、提示或 steering（中途引导）徽标，因此，在独立压缩预留轮次准入期间，界面仍会明确显示提示词已获接纳。

该状态只反映实时事件。挂载和 transcript 回放绝不会扫描历史以查找未匹配的 start；只有已挂载的 TUI 观察到 `session/event` 通知，才能开启它。轮次状态转换会保留该状态，而终端清理会清除其定时器和进度标志位。

收到 `compact/end` 时，TUI 会先清除状态，再启动普通字形的淡出。携带 `error` 的结束事件会以警告形式添加 `Compaction failed: <error>`。成功完成仍由已落地替换项的 transcript 标记呈现；无需再添加已结算的行，也可从匹配且已持久记录的开始与结束时间戳推导持续时间。

本决策仅部分取代[终端 transcript 决策](../bug-fix/2026-07-29-human-transcript-append-origin.md)和[浏览器 transcript 决策](../bug-fix/2026-07-30-web-transcript-log-ordered-projection.md)中与进度相关的延期条款：进度显示不要求标记携带规模信息，也不要求重构替换项渲染。两份记录均保持活动状态，并继续负责基于追加来源的 transcript 投影和已落地检查点标记。[排队式手动压缩决策](2026-07-30-queued-manual-compaction.md)继续负责标记对顺序、锁定机制和陈旧未匹配标记分类。

## 曾考虑的替代方案

**按照 PR（Pull Request）#669 中探索的方案，为 `CommandDefinition` 添加 `progressLabel` 和第二个 TUI 状态控制器。** 不予采用：命令元数据并非压缩生命周期的权威依据，自动压缩并非由人工命令发起，两个状态控制器也可能对同一个指示器给出不一致状态。

**按照 PR #669 中探索的方案，将 `compacting` 添加到 `TurnPhase`。** 不予采用：独立压缩按设计没有轮次，而带编号的压缩已经具有可见的运行轮次阶段。

**添加第五个 `TimingBucket`。** 不予采用：计时分桶用于划分一个开放模型步骤内的时间，并为其 transcript 页脚提供数据。独立压缩没有步骤转换，新分桶会在每个步骤总计中加入一个没有意义的压缩列。

**让运行、淡出和压缩状态共享一个定时器。** 不予采用：淡出过程独占一个会自行终止的定时器，而实时压缩有独立的开启与闭合生命周期。共享定时器会重构已经评审的动画状态机，却不能消除实际存在的并发定时器。

**扫描日志，查找未匹配的 `compact/start`。** 不予采用：来自先前进程生命周期的陈旧未匹配标记是预期的持久历史。只有实时通知才能证明当前进程正在执行工作。

**使用通用的命令运行指示器。** 本行为不采用该方案，因为压缩标记对是更精确的真源，并且还覆盖非命令路径。未来若实现通用命令指示器，应归属于 `command/run` 和 `command/done` 生命周期。

**在 transcript 中添加动画压缩行。** 不予采用：这会为同一个生命周期设置第二处状态动画显示。现有的单格指示器负责显示运行状态，而已落地标记和失败警告负责已结算的 transcript 呈现。

**打印包含持续时间的成功通知。** 不予采用：已落地的替换项已经提供完成标记。标记对的时间戳保留了持续时间，可供未来能够证明新增 transcript 行合理的展示方式使用。

## 后果

手动压缩在 agent 空闲时会在提示词上方显示带名称的已用时间，失败会直接产生警告，恢复会话时的陈旧未匹配标记绝不会显示为活动状态。提示符指示器保持一个终端字符单元宽，状态行和指示器则复用现有的状态动画、语义调色板和终端进度生命周期。

实时状态及其定时器是额外的进程局部状态，在标记对闭合和 TUI 清理这两种情况下都会清除。按设计，这种显示状态不可重建：持久历史提供成功标记与计时事实，只有当前进程的观察才能提供运行中状态。

包（package）级 TUI 测试固定了以下行为：独立开始事件、已用时间刷新、单指示器呈现、排除带编号的开始事件、淡出、失败警告、保留空闲状态、运行轮次优先级、存在未匹配标记时的恢复，以及定时器释放。组装后的 `queued-manual-compact` 终端场景会在真实摘要边界保持开放期间观察到 `Context being compacted 1.0s` 和 `dsh ⊙`。
