Files
tutti/docs/architecture/agent-gui-refactor-plan.md
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

253 lines
9.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Agent GUI 架构收敛方案(历史记录)
状态:架构重构完成(2026-07-11 复核)。本文只保留这次重构的背景、关键决策、迁移切片和退出标准,不再充当现行架构规范。原始规模快照、逐文件归属表和详细迁移清单可从 Git 历史读取。
现行规范:
- [Agent GUI Node](./agent-gui-node.md):界面、会话引擎、宿主边界
- [Agent Extensions](./agent-extensions.md):扩展目标、运行时安装、setup 生命周期
- [Provider-native Subagents](../specs/2026-07-15-provider-native-subagents.md):child session 实体关系
- `packages/agent/gui/AGENTS.md`:包内编辑路由和硬规则
## 1. 为什么重构
原实现没有单个致命错误,而是多个局部合理选择累积成系统性偏移:
1. 数据模型没有独立 turn,session 同时承载会话、执行和交互状态
2. 工作区级编排依附 React 面板生命周期
3. daemon、桌面桥、多个 store 和组件各持有一份乐观状态
4. provider 差异以名称分支渗入界面
5. Agent GUI 以横向巨石文件组织,修改一个流程会横跨整棵组件树
6. dock、消息中心、通知等消费方各自推导 session 状态
7. 架构约束主要是散文禁令,缺少类型和自动检查
文件过长只是职责、状态所有权和时序边界不清的可见症状。
## 2. 决策
### 2.1 实体边界
活动域分为三个实体:
| 实体 | 所有内容 | 不应包含 |
| ----------- | ------------------------------------------ | --------------------- |
| Session | 目标、工作目录、标题、设置、当前 turn 引用 | turn 结果、待处理审批 |
| Turn | 一次提交的阶段、结果、错误、文件变化 | 后续 turn 的状态 |
| Interaction | 审批、提问、计划确认及处理状态 | session 级展示状态 |
派生值不作为第二份事实保存:
- 展示状态由 turn 和 interaction 推导
- 提交可用性由 selector 推导
- session 只引用 active turn,不复制其 phase/outcome
- interaction 是否待处理由其自身状态表达,不使用三态 `null` 补丁协议
### 2.2 状态所有权
判定问题只有一个:关闭所有 Agent GUI 面板后,该状态是否仍应存在并继续工作?
- 是:属于 daemon 或工作区级会话引擎
- 否:属于组件本地状态
队列推进、乐观提交、运行时事件对账、当前 session/turn 投影属于引擎;滚动位置、输入焦点、临时展开状态属于组件。会话 Rail 的滚动、section 折叠和“显示更多”按 `workspaceId + agentTargetId/all` 记忆;搜索 query 只建立瞬时导航状态,不扩张永久 scope map。query controller 只报告当前请求 scope 是否已精确 resolved,不拥有 DOM 或滚动位置。`activeConversationId` 只表达选中事实;外部打开与新建会话通过显式 reveal intent 请求滚动,Rail 点击与 provider 恢复不发滚动命令。
### 2.3 单向数据流
```mermaid
flowchart LR
UI[Agent GUI / dock / 消息中心] -->|intent| Engine[工作区会话引擎]
Engine -->|command| Transport[桌面传输壳]
Transport -->|HTTP / event| Daemon[tuttid]
Daemon -->|canonical activity| Transport
Transport -->|runtime event| Engine
Engine -->|selectors| UI
```
约束:
- daemon 是业务规则和持久状态权威
- 引擎是客户端时序、乐观状态和对账的唯一所有者
- 桌面层只做传输和宿主集成,不形成第二业务核心
- React 读取快照、派发意图、渲染;不以 effect 编排业务生命周期
- 所有消费方共享 selector,不自行重建状态词汇表
### 2.4 Provider 差异
传输协议不是稳定边界,归一化活动契约才是。
- provider 身份、运行时策略、能力和目标元数据由 descriptor/target 下发
- UI 按 capability 渲染,不按 provider 名称猜行为
- 标准 ACP、专属协议和 SDK 边车都投影为同一活动契约
- 新 provider 不得要求在 Agent GUI 内新增行为分支
### 2.5 类型与协议
- OpenAPI 是跨 daemon 边界传输类型的事实源
- Go/TypeScript 传输类型从契约生成
- 内部域类型允许存在,但只能通过显式投影跨层
- 时间、状态和身份字段使用单一表示
- 未知枚举必须有显式处理路径,不能用宽字符串绕开检查
## 3. 目标模块
```text
packages/agent/
├── activity-core/ # 规范化活动状态、reducer、selectors
└── gui/
├── agent-gui/ # 功能模块装配
├── shared/ # 有明确领域名的复用能力
└── host/ # 最小宿主接口
apps/desktop/
└── workspace-agent/ # 传输适配、桌面集成、工作区实例装配
services/tuttid/
├── service/agent/ # 会话、turn、interaction 业务规则
└── api/ # OpenAPI 投影和路由
```
Agent GUI 纵向拆分为:
- session list:选择、分组、固定、未读
- timeline:活动投影、消息记录、文件变化
- composer:输入、附件、提交能力
- interactions:审批、提问、计划确认
- readiness/setup:目标就绪状态和安装入口
模块可共享域模型和 selector;不得共享“万能 controller”“万能 utils”或扁平大 view model。
## 4. 迁移切片
迁移按可独立验证的切片完成,不做一次性重写。
### 切片 1:建立机械护栏
- 记录文件尺寸、effect/ref、provider 分支和公共 props 基线
- 加入 renderer/provider/activity 边界检查
- 规则使用“只降不升”的过渡预算
退出标准:新增代码不能继续扩大已知偏移。
### 切片 2:生成契约成为唯一跨层类型源
- 先改 OpenAPI,再生成 Go/TypeScript 类型
- 删除手写传输镜像
- 保留内部域类型与生成类型间的显式投影
退出标准:跨边界字段改动由生成检查发现遗漏。
### 切片 3:Session、Turn、Interaction 实体化
- turn 与 interaction 成为持久实体
- session 只保留 active turn 引用
- daemon 启动时将未结束且不可恢复的 turn 收敛为 interrupted
- 取消操作归 turn 且保持幂等
退出标准:重启后不靠 session 字符串猜 turn 状态。
### 切片 4:工作区会话引擎
- 用户 intent 与运行时 event 进入同一 reducer
- 乐观提交声明确认、拒绝和超时条件
- 所有对账逻辑集中到一处
- selectors 服务 Agent GUI、dock、通知、消息中心
退出标准:面板卸载不影响队列、运行状态或对账。
### 切片 5:桌面桥变薄
- 移除桌面层的独立乐观状态和业务判断
- 只保留客户端调用、事件订阅、生命周期装配
退出标准:相同规则不在 desktop 与 engine 各实现一次。
### 切片 6:Agent GUI 纵向拆分
- 按 list/timeline/composer/interactions/readiness 拆模块
- controller 只装配模块
- 组件不持有工作区业务状态
退出标准:业务文件不超过仓库 800 行限制;新增功能只触及所属纵向模块。
### 切片 7:清理过渡代码
- 删除旧 store、双写、兼容 reader 和旧 selector
- 删除已归零的过渡预算
- 将仍有效的规则合并进耐久架构文档
退出标准:生产路径只有一个事实源和一个对账路径。
## 5. 完成快照
2026-07-11 实施快照:
- 协议 P1-P3、provider descriptor、引擎骨架、切片 1 已完成
- activation/submit/queue owner 已进入 engine,旧 overlay 符号扫描为零
- 旧 `AgentHostWorkspaceAgentSession/Message/Timeline` 生产镜像已删除
- generated-file mention provider 已通过 canonical selector 读取
- 原巨石已拆到 800 行以内,会话列表旧 store 已退役
- grouped props/view-model、纵向 view modules、渲染预算与低于 800 行的薄 controller 已落地
- 切片 7 的镜像、过渡导出、旧事件与终值基线清理已完成
- 唯一延后项是按客户端覆盖窗口删除私有持久化/launch migration reader;兼容读取不得重新进入公共写路径
## 6. 关键状态机
### 6.1 Turn
```text
submitted -> running -> waiting -> running -> settling -> settled
\------------------------------/
```
规则:
- `pendingIntents` 是提交乐观消息、接受、确认、结果未知、失败与到期的单一事实源
- controller 不把命令完成还原为 Promise 工作流
- 激活与普通提交共用同一 prompt envelope
- 乐观消息准入同时认文本与可渲染的结构化 `content`
- 合成计划决策走 tuttid 语义 API 与 durable `plan_decision` saga
- provider 原生 exit-plan 继续走 durable `interactive_response`
- session read 契约不再暴露旧 `status/turnLifecycle/submitAvailability/lastError/runtimeContext`
- 进程丢失后的未结算 turn 由 daemon 启动时统一收敛为 `settled/interrupted`
### 6.2 Interaction
```text
pending -> answered
-> dismissed
-> superseded
```
规则:
- 每个 interaction 有独立 ID、类型、状态和所属 turn
- 回答操作幂等,重复回答返回已处理状态
- 新 turn 开始时按规则取代不再有效的 interaction
- UI 只显示 selector 给出的 pending interaction
### 6.3 Readiness / Setup
```text
unavailable -> needs_setup -> setting_up -> ready
\-> failed
```
规则:
- built-in provider 的 managed-environment wizard 只服务其拥有的内建 provider
- Agent Extension setup 属于 Agent Target 生命周期,由 daemon 持久安装/setup 状态驱动
- React 只读取 host API 快照、发起显式用户动作、渲染状态
- provider 名称不得成为 readiness/setup 分支条件
## 7. 静态护栏
保留以下只降不升预算:
- 文件长度
- React effect/ref 数量
- provider 名称分支
- view props 面积
- 组件内 store 创建
预算下降时可更新 baseline;预算上升必须先修源头,不能通过抬 baseline 合并。