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
239 lines
24 KiB
Markdown
239 lines
24 KiB
Markdown
# Agent Runtime / Activity 可靠性改造方案
|
||
|
||
状态:Proposed
|
||
日期:2026-08-21
|
||
范围:`tutti` 单仓库;基于当前 `origin/main` 与 PR #2555 合并后的代码。
|
||
证据来源:飞书 bug 日志、当前代码调用链、已有回归测试;没有证据的结论明确标记为“待确认”。
|
||
|
||
## 1. 背景与目标
|
||
|
||
当前问题集中在三条边界:Provider/ACP 输出归一化、tuttid 持久化后的活动事件传播、桌面端 AgentGUI 投影。目标是让下一次执行得到正确结果或明确失败,而不是把错误隐藏、降级成成功,或只在 UI 层改文案。
|
||
|
||
### 1.1 已确认问题
|
||
|
||
| 问题 | 直接原因 | 系统性原因 | 结论可信度 |
|
||
| ----------------------------------------------------- | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | --------------------------------------- |
|
||
| 高密度 `message_delta` 触发 React #185 / 更新风暴 | 事件到达时同时驱动 canonical reconcile、乐观消息和 UI 通知 | 传输适配、状态投影、通知调度边界曾经耦合,没有统一的 workspace 级批处理入口 | 高 |
|
||
| MCP 普通输出被展示为 Error | `output` 与 `error` 没有在 Provider payload、daemon projection、GUI renderer 全链路保持分离 | 各层兼容了不同的 `isError`/`is_error`/`output` 形状,缺少统一状态与字段不变量 | 高 |
|
||
| ACP `isError` 或 `is_error` 未可靠映射为失败 | 终态主要由事件类型或普通输出推断,显式错误标志未优先参与判定 | Provider 原始 payload 没有先归一化为统一的 `failed` 语义 | 高 |
|
||
| Windows 缺少 cwd 时 ACP 使用了不适合 Windows 的根路径 | 空 cwd 的跨平台 fallback 没有按 OS 选择 | 协议 cwd、进程 cwd、Provider 搜索 cwd 的责任没有被明确拆开 | 高 |
|
||
| Windows Cursor Glob/Grep 绝对路径搜索失败 | 日志证明进入 Provider 边界的绝对路径搜索失败;内置 `rg.exe` 对同类有效路径本身可用 | Provider 内部路径转换/搜索参数形状未被 Tutti 完整观测 | 边界结论高;Provider 内部具体函数待确认 |
|
||
| 会话尚未进入 canonical snapshot 时重命名失败或丢失 | UI action 直接写 session,没有等待 canonical session 出现 | 临时激活态与 canonical lifecycle 的 readiness 边界不明确 | 高 |
|
||
|
||
### 1.2 目标
|
||
|
||
1. canonical store 是会话、turn、message 和 terminal status 的唯一事实来源。
|
||
2. Provider 的 status、input、output、error 在 daemon 边界一次归一化,并保留可诊断信息。
|
||
3. 活动事件只在持久化提交后对外发布;desktop/mobile 只负责传输适配,workspace coordinator 负责顺序、批处理、断点和重连。
|
||
4. Windows 的 process cwd、protocol cwd 和 Provider 搜索路径分别有明确契约;失败时返回真实失败原因。
|
||
5. 每个修复都有跨边界回归测试,能证明“正确结果”或“明确错误”,不以“页面不报错”为验收标准。
|
||
|
||
## 2. 当前架构和完整链路
|
||
|
||
### 2.1 运行时到 UI 的读取/事件链
|
||
|
||
```text
|
||
Provider process / ACP
|
||
-> packages/agent/daemon/runtime
|
||
(standard ACP adapter, tool normalizer, Reporter)
|
||
-> services/tuttid/service/agent/ActivityProjection
|
||
-> packages/agent/store-sqlite
|
||
(canonical durable session/turn/message state)
|
||
-> post-commit publisher / agent.activity.updated
|
||
-> desktop WebSocket adapter 或 mobile DeviceLink adapter
|
||
-> packages/agent/activity-core
|
||
(workspace event coordinator, optimistic overlay, reconcile)
|
||
-> AgentSessionEngine
|
||
-> packages/agent/gui AgentGUIRuntime/selectors/projection
|
||
-> React AgentGUI tool renderers
|
||
```
|
||
|
||
关键事实:
|
||
|
||
- `DurableActivityReporter` 把 runtime event 转成 `ReportActivityInput`;tuttid wiring 将其接到 `ActivityProjection`。
|
||
- `ActivityProjection` 先写 canonical repository,再通过 `ObserveCommitted` 做事件发布、daemon-local view 和 Provider cleanup。事件回调不是持久化成功的前置条件。
|
||
- `agent.activity.updated` 是 server-to-client 的活动主题;desktop 的 `WorkspaceAgentActivityReconcileBridge` 将事件交给 `createAgentActivityWorkspaceEventCoordinator`,而不是直接在组件中改状态。
|
||
- `AgentGUIRuntime` 使用 `AgentSessionEngine` snapshot 与订阅;GUI 组件不应直接读取 WebSocket 或 Provider 原始 payload。
|
||
|
||
### 2.2 GUI action 的写链
|
||
|
||
```text
|
||
AgentGUI semantic action
|
||
-> AgentSessionEngine typed command
|
||
-> apps/desktop WorkspaceAgentActivityService / Desktop adapter
|
||
-> tuttid client / service / Host runtime boundary
|
||
-> ActivityProjection -> canonical store
|
||
-> post-commit event 或 session reconcile
|
||
-> activity-core -> AgentSessionEngine
|
||
-> GUI snapshot / selector
|
||
```
|
||
|
||
重命名的 readiness 修复位于 GUI controller:`waitForCanonicalSession` 等待 session 进入 engine 的 canonical snapshot 后,才调用 `renameSession`。这保持了写操作的 owner 仍为 engine,而不是在 UI 中新增第二份 session 状态。
|
||
|
||
### 2.3 Windows Provider 链
|
||
|
||
```text
|
||
activation / service resolves cwd
|
||
-> standard ACP ProcessSpec.CWD + ProtocolCWD
|
||
-> ACP session/new 或 resume
|
||
-> Provider Glob/Grep 或 native rg / PowerShell
|
||
-> normalized call status/input/output/error
|
||
-> Reporter -> canonical store -> event -> activity-core -> GUI
|
||
```
|
||
|
||
当前代码已经在 `standardACPProtocolCWD` 对空 cwd 做 Windows/POSIX 分支,并在 process start 日志同时记录 `cwd` 与 `protocol_cwd`。Cursor runtime preparation 还会注入 Windows 搜索策略:workspace 内使用相对路径,workspace 外或不确定时使用 native terminal 搜索。
|
||
|
||
### 2.4 直接原因与系统性原因
|
||
|
||
直接原因是一次请求或一次渲染中使用了错误的字段、路径、终态或 readiness;系统性原因是同一个事实在 Provider、daemon、transport、activity-core、engine、renderer 多层重复解释。改造重点是收窄解释边界,而不是增加更多兼容分支。
|
||
|
||
## 3. 目标架构和完整链路
|
||
|
||
### 3.1 目标链路
|
||
|
||
```text
|
||
Provider / ACP raw event
|
||
-> runtime adapter: 归一化 status + input/output/error + cwd
|
||
-> DurableActivityReporter
|
||
-> ActivityProjection: 事务内写 canonical store
|
||
-> committed activity envelope: 只发布已提交事实
|
||
-> transport adapter: WebSocket / DeviceLink,仅处理连接和反序列化
|
||
-> activity-core workspace coordinator:
|
||
顺序校验、批处理通知、乐观 overlay、gap/reconnect reconcile
|
||
-> AgentSessionEngine: 唯一前端生命周期状态
|
||
-> AgentGUIRuntime / selector / projection
|
||
-> renderer: 按 status 与 output/error 分区展示
|
||
```
|
||
|
||
### 3.2 目标不变量
|
||
|
||
- `failed` 只能来自 Provider 显式错误、`call.failed`/等价失败事件、明确的非零退出或归一化后的错误;普通 output 不能单独制造 Error 区域。
|
||
- `output` 是成功或失败调用的结果内容,`error` 是失败原因;失败时两者都可以存在,但不能互换或互相覆盖。
|
||
- canonical 写入先于对外活动事件;事件丢失、乱序、跨 session 或版本不合法时,触发 reconcile,不直接污染本地状态。
|
||
- workspace 级通知由 coordinator 的注入 scheduler 批处理;desktop 不再为同一事实新增第二个 renderer timer。
|
||
- 已提供 cwd 必须原样传递;Windows 空 cwd 使用当前进程的有效工作目录;workspace 内搜索优先相对路径,workspace 外搜索使用 Provider 已验证的 native 搜索入口。
|
||
- 不能确认 Provider 内部路径转换时,必须保留原始错误和诊断字段,并标记为 Provider 边界待确认,不能返回“搜索成功”。
|
||
|
||
### 3.3 Cursor 问题的边界
|
||
|
||
已能确认 Tutti 侧的两个防线:Windows 空 cwd 不再使用错误的 POSIX fallback;Cursor preparation 会给 Provider 明确的相对路径/native terminal 搜索策略。但现有日志没有原始 ACP tool request/response,且真实 Provider 复现曾受 quota 限制,因此“Cursor 内部将 `C:/...` 转换为空路径的具体实现位置”仍待确认。完整修复必须补齐该证据或与 Provider 维护方确认,不能仅凭 Tutti 侧 prompt policy 宣称已解决。
|
||
|
||
## 4. 仓库、服务和模块改造范围
|
||
|
||
当前证据只覆盖 `tutti` 这一仓库;未确认需要修改其他仓库。
|
||
|
||
| 范围 | 改造内容 | 不应承担的职责 |
|
||
| -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
|
||
| `packages/agent/daemon/runtime` | 维护 ACP session cwd/protocol cwd;统一 `isError`/`is_error`、event type、退出状态、input/output/error 归一化;记录可脱敏的 process/tool 诊断 | 不做 GUI 文案、React 状态或桌面连接管理 |
|
||
| `packages/agent/runtimeprep` | 维护 Cursor Windows session-scoped 搜索上下文和相对路径/native terminal fallback;补充可验证的 preparation contract | 不在这里判断 Provider 返回是否成功,不复制 daemon status 逻辑 |
|
||
| `packages/agent/daemon/activity` | 将 normalized call 生成 `WorkspaceAgentMessageUpdate`,保持 status、input、output、error 字段分离 | 不读取 renderer 状态,不为每个 Provider 写 UI 分支 |
|
||
| `services/tuttid/service/agent` | 保持 `ActivityProjection` 作为 durable commit owner;统一 post-commit fanout 和 scope 校验;异常事件只请求 reconcile | 不把事件回调当作持久化成功条件,不创建第二个 canonical store |
|
||
| `services/tuttid/agent_runtime_activity_event_bridge.go` | 校验 runtime event scope、过滤 malformed/cross-scope event、限流 reconcile request,并保留可诊断日志 | 不在 event bridge 中修正业务状态或吞掉 Provider 错误 |
|
||
| `packages/agent/store-sqlite` | 当前不改 schema;继续作为 canonical durable repository | 不保存一份仅供 UI 使用的平行状态 |
|
||
| `packages/agent/activity-core` | 复用 workspace coordinator、optimistic overlay、gap/reconnect reconcile 和注入 scheduler;必要时补充 contract tests | 不依赖 WebSocket、Electron、DeviceLink 或 Provider 类型 |
|
||
| `apps/desktop` | WebSocket 生命周期、诊断、连接状态和 `WorkspaceAgentActivityReconcileBridge` 适配;所有状态变更经 engine/coordinator;保留 canonical readiness 等待 | 不新增桌面侧业务 coordinator、第二套定时器或 Provider 解析器 |
|
||
| `apps/mobile` | 只把 DeviceLink delivery 归一化后交给同一个 activity-core contract;补充移动端连接/重连适配测试 | 不复制 desktop 的事件顺序、overlay、reconcile 算法 |
|
||
| `packages/agent/gui` | 使用统一 status/output/error render data;GUI action 通过 `AgentGUIRuntime`/engine;structured fallback 有深度和长度上限 | 不直接解析 ACP 原始事件,不把异常改成普通 output |
|
||
| `packages/agent/host` | 当前无需改生命周期语义;若后续发现 session/turn lifecycle contract 缺能力,先增加 Host conformance scenario,再由 Host 提供 API | 不在 tuttid 或 desktop adapter 中重新实现 lifecycle owner |
|
||
| `services/tuttid/api/openapi` 与 clients | 当前不计划改公共 HTTP contract;若确需新增 envelope 字段,先改 OpenAPI,再生成/更新 client,并保持字段可选 | 不以未确认的日志字段直接扩展公共 API |
|
||
| `docs/architecture` / `docs/conventions/troubleshooting` | 仅在行为或边界发生新增时更新对应持久文档;本方案本身是实施入口 | 不复制代码注释或堆叠重复排障说明 |
|
||
|
||
## 5. 可复用部分与 Adapter 边界
|
||
|
||
| 可复用核心 | Adapter 输入/输出 | 边界规则 |
|
||
| ------------------------------------------------ | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
|
||
| `store-sqlite` canonical repository | 接收已校验的 projection command,返回 committed result | 只拥有 durable truth,不感知 Provider/UI |
|
||
| `ActivityProjection` + `DurableActivityReporter` | runtime normalized event -> canonical update | 归一化在 runtime;事务提交与 fanout 在 service;不把 provider raw payload 向上泄漏 |
|
||
| `activity-core` workspace coordinator + overlay | transport delivery -> normalized activity event;输出 snapshot/reconcile intent | 同一实现服务 desktop/mobile;只注入 scheduler、snapshot reader 和 dispatch capability |
|
||
| `AgentSessionEngine` + `AgentGUIRuntime` | service/adapter command port -> typed engine state | engine 负责前端 session lifecycle;组件只消费 snapshot/selector |
|
||
| ACP tool/status normalizer | Provider raw event -> stable status/input/output/error | Provider 特殊字段只在此处或其窄 adapter 解释,不能进入 GUI/shared generic model |
|
||
| OS/process path adapter | logical cwd -> process cwd/protocol cwd/native search command | Windows/POSIX 差异留在 runtimeprep/process adapter;产品核心保持 platform-neutral |
|
||
| desktop WebSocket / mobile DeviceLink adapter | transport payload + connection state -> activity-core input | 只负责连接、反序列化、诊断、analytics/presentation invalidation,不拥有业务顺序算法 |
|
||
|
||
禁止为上述边界再创建含义不清的 `shared/common/utils` 包或第二个 event center;只有出现真实的第二个消费者和稳定窄接口时才抽取新 package。
|
||
|
||
## 6. 明确不做的内容
|
||
|
||
- 不重写 AgentGUI、ACP runtime、SQLite 或整个 event stream。
|
||
- 不通过捕获异常、默认成功、清空错误字段或只改 UI 文案来“修复”问题。
|
||
- 不在当前证据不足时声称已经完成 Cursor Provider 内部绝对路径转换修复;真实 Provider request/response 和配额可用性属于待确认项。
|
||
- 不新增数据库 schema、历史数据回填或删除已有 activity;没有证据证明历史记录可安全重算。
|
||
- 不新增公共 API 字段;除非确认跨进程消费者需要,并按 OpenAPI-first 流程走兼容变更。
|
||
- 不扩大到 dormant background-task guard、未关联本调用链的 bug 表项目或其他仓库问题。
|
||
- 不把 desktop/mobile 各自实现成一套活动状态机,也不把 Provider 分支写进 `activity-core` 或 GUI。
|
||
|
||
## 7. 数据迁移和兼容策略
|
||
|
||
### 7.1 当前方案
|
||
|
||
当前改造不改变 SQLite schema、canonical record 语义或 `agent.activity.updated` 的必需字段,因此不需要数据迁移。已有 session 在下一次 start/resume 时使用新的 cwd 规则;历史已经写入的错误 status 不自动回写,因为缺少足够证据判断其真实 Provider 结果。
|
||
|
||
### 7.2 读取兼容
|
||
|
||
- 归一化同时读取 `isError` 与 `is_error`、旧 event type 别名和缺失的 output/error 字段。
|
||
- 旧记录缺少 `error` 时仍可展示已有 output;未知结构只做有上限的文本提取,不改变 terminal status。
|
||
- 新增字段只能采用 optional/additive 方式;旧 desktop/mobile 客户端忽略未知字段仍应能完成基本 reconcile。
|
||
- 若公共事件 envelope 需要版本变化,先更新 OpenAPI/事件 catalog 和 consumer contract,再同时保留旧字段一段兼容窗口;当前没有证据表明需要升版本。
|
||
- Cursor preparation 是 session-scoped;是否能在不重启 live session 的情况下重新注入上下文待确认。若不能,则只对新建或重新准备的 session 生效,并在诊断中明确这一点。
|
||
|
||
## 8. 风险与回滚方案
|
||
|
||
| 风险 | 影响 | 控制/验证 | 回滚 |
|
||
| -------------------------------------- | ----------------------------- | ------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
|
||
| Provider payload 仍有未覆盖形状 | status 或 error 展示不一致 | 保留 raw-safe summary,增加 normalizer fixture 和未知字段日志 | 回退归一化提交;canonical schema 不变 |
|
||
| 严格失败判定暴露更多真实失败 | 用户看到失败数上升 | 对照 Provider 原始事件和退出状态,不以 UI 无红色为指标 | 回退代码提交,不修改历史数据 |
|
||
| coordinator 批处理造成短暂显示延迟 | UI 反馈变慢 | 测量通知次数、reconcile 延迟和最终一致性;使用现有注入 scheduler | 回退 coordinator 变更,transport contract 不变 |
|
||
| Windows cwd 仍在某个 Provider 内被重写 | 搜索继续失败 | 采集脱敏的 `cwd/protocol_cwd/tool input/output`,补真实 Provider ACP 测试 | 暂停 Cursor policy 变更,保留 native terminal fallback;等待 Provider 证据 |
|
||
| event scope/version 不兼容 | 客户端触发 reconcile 或丢更新 | malformed/cross-scope fixture、旧 payload contract test | 回退 event bridge/consumer commit,不清理 canonical 数据 |
|
||
|
||
首选回滚粒度是回退对应代码提交并重新部署,不做 destructive 数据操作;因为本方案不含 schema migration,回滚不需要 down migration。
|
||
|
||
## 9. 测试与验收标准
|
||
|
||
### 9.1 单元与契约测试
|
||
|
||
- `packages/agent/activity-core`:高密度 delta 的批处理、乱序/gap、跨 scope、重连 hydration、optimistic overlay terminal truth;保留现有 `511/511` 基线。
|
||
- `packages/agent/daemon/runtime`:Windows/POSIX 空 cwd、显式 `isError`/`is_error`、`call.failed`、非零退出、output/error 同时存在、未知 payload。
|
||
- `packages/agent/daemon/activity` 与 `services/tuttid/service/agent`:normalized call 到 canonical projection 的 status/input/output/error 映射,以及“提交后才发布”。
|
||
- `packages/agent/runtimeprep`:Cursor Windows prompt/context 使用相对路径和 native fallback;session-scoped cleanup。
|
||
- `packages/agent/gui`:普通 MCP output 不出现 Error 区域;失败调用显示真实 error;失败调用仍可显示 output;tool sequence 保持顺序;structured fallback 受深度/长度限制。
|
||
- `apps/desktop`:canonical session 尚未出现时 rename 等待;WebSocket connection state 与 coordinator 生命周期正确 dispose。
|
||
|
||
### 9.2 集成与 Windows 验收
|
||
|
||
1. 从 runtime event 开始,验证 `runtime -> ActivityProjection -> SQLite -> committed event -> desktop/mobile adapter -> activity-core -> engine -> GUI` 的端到端结果。
|
||
2. 注入连续 `message_delta`,确认通知被 workspace coordinator 合并,最终 canonical message 完整,且不产生 React #185 类重复更新风暴。
|
||
3. 注入 malformed、cross-session、gap event,确认不直接写入错误 session,而是产生受限 reconcile 请求。
|
||
4. Windows 分别验证:已提供 cwd、空 cwd、workspace 内相对搜索、workspace 外绝对路径 native 搜索;结果必须正确,失败必须带可行动错误。
|
||
5. 在真实 Cursor Provider 配额可用后,采集原始 ACP tool request/response,再验证绝对路径失败是否已由 Provider 修复;在此之前该项验收状态为“待确认”,不能标记完成。
|
||
|
||
### 9.3 验收口径
|
||
|
||
- 正确返回:canonical 数据、活动事件、engine snapshot 和 UI 展示的 status/output/error 一致。
|
||
- 正确失败:失败原因可从日志或 UI 定位,不能被转换为成功或静默丢弃。
|
||
- 兼容:旧 payload 和已有 session 可读取;不要求历史错误记录自动重算。
|
||
- 架构:没有新增第二个 canonical owner、第二套 workspace event coordinator 或 Provider 逻辑进入 GUI/activity-core。
|
||
- 当前基线已验证:activity-core 测试通过;ACP/runtimeprep/desktop 相关聚焦测试通过。部分更宽的 Windows symlink 测试受本机权限限制,需在具备权限的 Windows CI/环境补齐,不将权限失败误判为产品修复证据。
|
||
|
||
## 10. 分阶段实施顺序
|
||
|
||
### 阶段 0:证据和契约冻结
|
||
|
||
补齐 Cursor 原始 ACP request/response、真实 Provider 配额复现、live session 是否支持重新 preparation 三项证据;同时冻结 status/output/error、cwd、commit-before-publish 和 reconcile contract。未补齐的项保持“待确认”。
|
||
|
||
### 阶段 1:落地已确认的窄边界修复
|
||
|
||
以 PR #2555 当前代码为基线,完成并保持:runtime status/error 归一化、Windows ProtocolCWD fallback、Cursor Windows preparation policy、activity-core 批处理/overlay、GUI output/error 分区、canonical session readiness。每项都补上对应单元/契约测试。PR #2555 已包含其中一部分,不应重复实现。
|
||
|
||
### 阶段 2:跨层集成和 Windows gate
|
||
|
||
验证 `ActivityProjection -> committed event -> adapter -> coordinator -> engine` 全链路;加入高密度、gap、跨 scope、重连场景;在 Windows CI 或具备权限的环境验证 process cwd、native search 和真实 Provider tool call。
|
||
|
||
### 阶段 3:小范围发布与观察
|
||
|
||
观察失败分类、reconcile 频率、事件延迟、重复通知和 Windows 搜索成功率。若出现回归,按提交粒度回退;不通过修改历史 canonical 数据掩盖问题。只有真实 Provider 证据确认后,才决定是否需要 Provider 侧变更或新增兼容字段。
|
||
|
||
### 阶段 4:文档收敛
|
||
|
||
将已验证的边界写入对应 architecture/troubleshooting 文档,删除重复的临时说明;对仍未确认的 Provider 内部结论保留明确的待确认状态,直到有原始协议证据。
|