Skip to content

统一 Direct Subagent 与 Workflow child 的会话渲染和 follow-to-end 滚动语义 #105

Description

@tt-a1i

背景

用户进入直接 Subagent takeover 或 Workflow child transcript 时,希望像阅读主 Pi 会话一样阅读一段连续对话:用户输入、Assistant Markdown、thinking、工具活动与结果使用一致的视觉语言;新内容到达时默认自动跟随底部,用户主动上滚后则保留阅读位置。

当前两条 child UI 已经具备数据和实时刷新能力,但使用了两套 renderer 与两套滚动状态,因此行为割裂。

当前事实

基于 main@dbaacf4 与当前依赖 Pi 0.84.1

1. Pi 主会话的参考语义

Pi 主会话分别用 AssistantMessageComponentToolExecutionComponent 处理 Assistant Markdown/thinking 与工具生命周期:

fullscreen 主 transcript 使用 ScrollView({ follow: "end" })

这应当是 child viewport 的交互参考。

2. Direct Subagent:格式较接近,但滚动状态不等价

Direct takeover 已经有较完整的 transcript renderer:Assistant 与 thinking 使用 Markdown,live assistant/tool 单独投影,finalized item 有缓存:

但 takeover 用“距底部行数”保存滚动位置:

因此它能默认追尾,但用户上滚后若新行继续追加,相同的 bottom-relative offset 会让可见内容向后漂移,不能像 Pi ScrollView 一样稳定保持原来的阅读锚点。

3. Workflow child:独立日志样式,且默认从顶部打开

Workflow 将 Pi messages 归一化为持久化 TranscriptEntry[],保留 role、text、toolCallId、timing 等有限事实;它不是完整 Pi Tool Component:

Dashboard 又单独实现了一套角色标签/箭头/文本换行 renderer:

滚动位置使用 top-relative transcriptScroll,进入详情时重置为 0

所以运行中的 Workflow child 打开后不会默认看到最新输出,也没有 follow/pause/resume 状态。

结论

可以做到与主会话一致的视觉语义和滚动语义,但不应直接实例化 Pi 的内部 AssistantMessageComponent / ToolExecutionComponent

  1. 这些类不是稳定的扩展 seam;
  2. ToolExecutionComponent 依赖原始 ToolDefinition、partial/expanded 状态和 live renderer state;
  3. child transcript 已经过归一化和大小裁剪,历史记录不具备重建原生组件所需的全部事实;
  4. 强行重建会把 operator UI 与 Pi 内部实现、持久化版本和工具注册表耦合。

正确边界是:保留 Direct 与 Workflow 各自的 canonical/read-model 数据,用两个薄 adapter 投影到一个共享的、纯 operator-facing transcript document;共享 renderer 与 viewport state 只消费该 document。

建议方案

A. 一个共享 transcript renderer

下沉 Direct 当前较成熟的 Markdown/tool rendering 能力到 extensions/shared/,暴露一个小接口,例如:

renderAgentTranscript(document, width, theme, { now }): string[]

两条 adapter:

  • SubagentSnapshot -> AgentTranscriptDocument
  • Workflow AgentRecord/TranscriptEntry[] -> AgentTranscriptDocument

共享 document 只描述 UI 所需的确定事实:user、assistant text、thinking/redacted、tool call/result、live phase、queued input、cwd。不要让 renderer 读取 Session、activeRuns 或 artifact 文件。

第一版不迁移 Direct snapshot 或 Workflow transcripts.json 的持久化 schema;旧 Workflow artifact 继续由 adapter 读取。

B. 一个共享 follow-to-end viewport state

共享一个无 TUI 副作用的 viewport 状态模块,语义对齐 Pi ScrollView

初始 / followingEnd=true
  新行到达 -> scrollTop 移到新的 maxScrollTop

用户向上滚动
  followingEnd=false
  新行到达 -> 保持绝对 scrollTop,阅读锚点不动

用户滚到底部、按 G/End 或显式 scrollToEnd
  followingEnd=true

Direct takeover 与 Workflow dashboard 可以保留各自 header、输入框和导航层级,只共用 body renderer 与 viewport state。

C. 与 #93 的关系

#93 是本 Issue 中“工具活动行”的子集。本 Issue 不应再造第三套工具 formatter:共享 transcript renderer 应消费 #93 的紧凑 activity projection,或者在同一 PR 中完成两者。

#93 提到的“Pi 原生展开”只有在原始证据仍可达且数据足够时才能提供;本 Issue 不得根据被裁剪的 preview 伪造完整原生 Tool Component。完整证据仍由 child session 文件、Workflow artifact 和已有结果恢复路径负责。

目标体验

  • user:与主会话相同的用户消息层级;
  • assistant:Markdown,而不是 Assistant: 日志标签;
  • thinking:muted/italic,redacted 仍明确显示;
  • tool:使用 统一 Subagent 与 Workflow child 的图标化单行工具活动视图 #93 的紧凑 pending/success/error 活动行;
  • tool result:必要的 bounded preview,错误明确但不淹没最终回答;
  • Direct 与 Workflow 可保留不同 header/badge,conversation body 必须共用视觉规则;
  • 默认显示最新输出并自动跟随;手动上滚后不抢回底部;恢复 follow 有明确按键和状态提示。

验收标准

共享渲染

  • 同一组 user/assistant/thinking/tool/toolResult fixture,经 Direct adapter 和 Workflow adapter 后产生相同的 conversation body;
  • Assistant Markdown、列表、代码块、CJK、ANSI/控制字符清理和窄宽度均有确定性测试;
  • Direct 的 live assistant、live tool、queued steer/follow-up 继续实时可见;
  • Workflow 运行中 transcript 更新继续实时可见,旧 transcripts.json 无需迁移即可渲染;
  • tool activity 使用共享实现并满足 统一 Subagent 与 Workflow child 的图标化单行工具活动视图 #93 的 pending/success/error/长路径验收,不复制 formatter;
  • 不根据 preview 伪造“完整原生输出”或不存在的 Tool renderer state。

滚动

  • Direct takeover 和 Workflow child transcript 打开时都默认位于底部;
  • followingEnd=true 时追加行会继续显示最新尾部;
  • 用户向上滚动后 followingEnd=false,追加多行仍保持同一个 top row/阅读锚点;
  • 向下滚到底部、GEnd 恢复 follow;g / Home 到顶部但不自动恢复;
  • paused 状态显示距底部行数或等价提示,但不改变 viewport 高度;
  • transcript 缩短、宽度变化、viewport 高度变化时安全 clamp,不出现空白越界;
  • running 更新采用有界 repaint;settled transcript 没有无业务变化的时钟刷新。

边界保护

  • 不改变 child model 消息、工具结果、Schema、权限、Agent 调度或 Provider 请求;
  • 不改变 Direct/Workflow canonical lifecycle 和结果投递;
  • 不要求新的模型工具、配置项或持久化迁移;
  • bun run checkbun run test 通过;
  • 真实 TUI smoke 覆盖 Direct 和 Workflow:默认追尾、手动上滚保持锚点、滚到底部恢复追尾。

非目标

实施顺序建议

  1. 先抽出纯 viewport state,并用追加/缩短/resize 的状态测试锁定 follow 语义;
  2. 把 Direct renderer 下沉为共享 renderer,确保 Direct 行为不退化;
  3. 增加 Workflow adapter,替换 transcriptRows()
  4. 接入 统一 Subagent 与 Workflow child 的图标化单行工具活动视图 #93 的共享 tool activity projection;
  5. 做 Direct + Workflow 真实 TUI smoke。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestready-for-agentSpecification is ready for implementation by an agent

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions