docs: clarify architecture baselines

This commit is contained in:
Haitao Pan 2026-03-23 08:59:44 +08:00
parent a37fbc4723
commit ae1faa03d0
5 changed files with 378 additions and 377 deletions

View File

@ -1,271 +1,208 @@
# Assistant 任务线程信息架构
笔记名建议
本文分为两部分
- `不是聊天框是任务工作台AI App 里的线程隔离设计`
- 当前已实现的线程信息架构基线
- 尚未落地、只应视为未来扩展方向的 IA 目标
这份文档不再把未来目标写成“当前 UI 已实现”。
## 目标
`XWorkmate` 定义一套“任务即线程”的 Assistant 信息架构,让用户能同时处理多个任务,同时保持以下几类状态互不污染
`XWorkmate` 的 Assistant 已经采用“任务即线程”的基本模型,目标是让以下状态尽量按线程隔离
- 会话历史
- 执行模式
- skills
- 模型
- 附件
- 顶部连接状态
- 草稿输入与结果输出
- 线程标题 / 归档状态
核心原则:
同时需要明确哪些能力当前还没有做成线程级持久化。
1. 一个任务就是一个线程,不是一个全局聊天框里的子状态。
2. 右上角状态只代表当前线程,不代表全局最近一次连接结果。
3. 模式、skills、模型、附件都跟线程走不跟页面走。
## 当前已实现基线
## 页面结构图
### 核心原则
1. 一个任务对应一个 `AssistantThreadRecord`
2. `executionTarget`、`selectedSkillKeys`、`assistantModelId`、`messageViewMode` 跟线程走
3. 右上角 connection chip 只反映当前线程的解析结果,不直接沿用别的线程状态
4. 全局设置只提供默认值,不直接覆盖已有线程
### 当前页面结构
```mermaid
flowchart LR
A["左侧任务线程栏"] --> B["中间会话区"]
C["顶部线程状态栏"] --> B
D["底部输入与执行区"] --> B
E["右侧上下文抽屉"] --> B
C["会话头部"] --> B
D["底部 composer 工具栏"] --> B
A1["线程列表"]
A2["新建任务"]
A3["分组:本地 / 远程 / AI Only"]
A4["线程卡片:标题 / 状态 / 更新时间"]
A1["分组:仅 AI Gateway / 本地 / 远程"]
A2["新对话"]
A3["任务卡片"]
A4["归档动作"]
C1["当前线程名称"]
C2["当前模式"]
C3["当前连接状态"]
C4["已启用 skills 数"]
C5["当前模型 / Agent"]
C1["标题"]
C2["任务状态"]
C3["owner / surface / session key"]
C4["message view mode"]
C5["connection chip"]
B1["消息流"]
B2["任务结果"]
B3["运行中步骤"]
B4["错误与重试"]
D1["输入框"]
D2["模式切换"]
D3["skills 选择"]
D4["附件"]
D5["提交 / 停止 / 重连"]
E1["线程配置"]
E2["已选 skills"]
E3["附件列表"]
E4["运行历史"]
E5["导出 / 归档"]
D1["执行模式切换"]
D2["多 Agent toggle"]
D3["skills"]
D4["permission"]
D5["model"]
D6["thinking"]
D7["附件选择"]
D8["发送 / 重连 / 打开设置"]
```
## 信息架构
当前没有独立落地的右侧“线程上下文抽屉”。
## 当前 UI 真实分布
### 1. 左侧:任务线程栏
用途
当前已实现
- 管理任务线程
- 显示线程分组与归属
- 快速切换当前上下文
- 任务按 `AssistantExecutionTarget` 分组显示
- 支持新建线程
- 支持切换线程
- 支持归档线程
- 任务卡片显示标题、状态、更新时间
建议字段:
当前未实现
- 标题:`任务`
- 主操作:`新建任务`
- 分组:
- `仅 AI Gateway`
- `本地 OpenClaw Gateway`
- `远程 OpenClaw Gateway`
- 单条线程卡片:
- `任务名`
- `模式 · 状态 · 更新时间`
- 线程导出
- 线程模板
- 线程级自动化入口
建议文案:
### 2. 会话头部
- 空态:`还没有任务线程,先新建一个。`
- 分组说明:`任务按当前执行模式分组展示。`
当前头部显示的是:
### 2. 顶部:线程状态栏
- 当前线程标题
- 当前任务状态 pill
- owner
- surface
- session key
- message view mode
- connection chip
用途:
当前没有把以下信息集中放到头部
- 告诉用户当前正在操作哪个线程
- 让模式、状态、skills、模型一眼可见
- 单独的 skills 数
- 单独的模型标签
- 独立的模式标签字段
建议字段:
- 当前线程名称
- 当前模式
- 当前连接状态
- 当前地址或模型
- 当前 skills 数
状态显示规则:
- `仅 AI Gateway` 线程:
- `仅 AI Gateway · gpt-5.4`
- 不显示 `已连接 OpenClaw ...`
- `本地 OpenClaw Gateway` 线程:
- `已连接 · 127.0.0.1:18789`
- 若当前线程未连通,则显示本线程目标地址,不沿用别的线程状态
- `远程 OpenClaw Gateway` 线程:
- `已连接 · gateway.example.com:9443`
- 若当前线程失败,则显示 `错误 · gateway.example.com:9443`
建议文案:
- `这里显示的状态只属于当前任务线程。`
这些能力目前主要在底部 composer 工具栏里呈现;模式语义则通过 connection chip 和执行模式按钮共同体现。
### 3. 中间:会话内容区
用途
当前已实现:
- 承载当前线程完整消息历史
- 承载当前线程的执行结果、错误与流式过程
建议区块:
- 消息流
- 任务结果
- 运行步骤
- 错误与重试
建议文案:
- 区块标题:`当前任务会话`
- 说明:`当前线程的消息、结果和运行记录都独立保存。`
- 运行中:`正在执行当前任务,结果将回到这个线程。`
- 错误:`当前线程连接失败,请重试或调整该线程配置。`
- 渲染当前线程的消息历史
- 渲染本地任务卡片 / tool call / assistant message
- 流式结果回到当前线程
- 切线程后按当前线程重新解析内容来源
### 4. 底部:输入与执行区
用途
当前已实现:
- 所有输入动作默认绑定当前线程
- 防止用户误以为切模式是全局行为
- 执行模式切换
- skills 选择
- 模型选择
- 权限等级
- reasoning 选择
- 附件选择
- 提交 / 停止 / 重连 / 打开设置
建议字段:
也就是说当前“模型”和“skills”不是头部状态栏字段而是 composer toolbar 字段。
- 输入框
- 任务模式
- 本线程 skills
- 附件
- 提交 / 停止 / 重连
### 5. 右侧上下文抽屉
建议文案
当前状态:
- 输入框 placeholder
- `输入需求、补充上下文、继续追问,系统只会沿用当前任务线程上下文。`
- 附件说明:
- `仅附加到当前线程`
- 独立的“线程上下文抽屉”没有落地为已交付能力
- 文档里提到的 `线程配置 / 已选技能 / 附件 / 运行历史 / 导出` 目前不应视为已实现 UI
### 5. 右侧:上下文抽屉
## 当前线程隔离矩阵
用途:
- 汇总当前线程的结构化状态
- 让用户知道哪些配置只影响当前线程
建议分组:
- `线程配置`
- `已选技能`
- `附件`
- `运行历史`
- `导出 / 归档`
建议文案:
- `这些设置只影响当前线程,不会污染其他任务。`
## 线程隔离矩阵
| 维度 | 是否线程隔离 | 说明 |
| 维度 | 当前状态 | 说明 |
| --- | --- | --- |
| 消息历史 | 是 | 每个线程独立保存历史消息 |
| 消息历史 | 是 | 每个线程独立保存 / 解析历史 |
| 执行模式 | 是 | `AI Gateway Only / Local / Remote` 跟线程绑定 |
| Skills | 是 | 本线程已选 skills 不影响其他线程 |
| 模型 | 是 | 当前模型选择跟线程走 |
| 附件 | 是 | 仅附着当前线程 |
| 草稿输入 | 是 | 输入框草稿按线程保留 |
| 顶部状态 | 是 | 只显示当前线程真实状态 |
| 全局设置 | 否 | 仅作为默认值,不直接覆盖已有线程 |
| Skills | 是 | 已导入 / 已选 skills 跟线程绑定 |
| 模型 | 是 | `assistantModelId` 跟线程绑定,没设时回退到默认模型 |
| 顶部连接状态 | 是 | 只显示当前线程解析出的连接状态 |
| message view mode | 是 | 跟线程绑定 |
| 自定义标题 | 是 | 通过 settings 持久化 |
| 归档状态 | 是 | 通过 settings 持久化 |
| 草稿输入 | 否 | 当前只有页面级 `_inputController` |
| 发送前附件草稿 | 否 | 当前只有页面级 `_attachments` |
| 导出 | 否 | 未实现 |
## 交互规则
## 当前交互规则
### 新建线程
- 新线程默认继承“当前线程模式”和“当前视图模式”
当前实现:
- 新线程继承当前线程的 `executionTarget`
- 新线程继承当前线程的 `messageViewMode`
- 不继承上一线程的消息历史
- 可选择继承当前线程已选 skills或默认空白
当前未实现:
- 创建时可选继承当前线程已选 skills
- 线程级输入草稿继承
### 切换线程
- 必须同步切换以下状态:
- 当前模式
- 当前 skills
- 当前模型
- 当前草稿
- 当前顶部状态
- 不允许继续显示上一个线程的连接标签
当前会同步切换:
- 当前模式
- 当前 skills
- 当前模型
- 当前顶部连接状态
- 当前消息内容解析路径
当前不会恢复线程级输入草稿,因为这项能力还没有实现。
### 切模式
当前实现:
- 模式切换默认只影响当前线程
- 若用户需要更改默认新线程模式,应单独在设置中完成
- 切模式后,顶部状态立即切到目标线程语义,再异步刷新真实连接结果
- 同时允许更新 `settings.assistantExecutionTarget` 作为默认新线程模式
- 切换后会按线程目标重连 / 断连 runtime并刷新 skills / connection state
## 推荐的用户可见文案
## 当前实现与未来目标的边界
- `每个任务都是独立线程。`
- `模式只对当前线程生效。`
- `技能只绑定当前线程。`
- `右上角状态只代表当前线程,不代表全局。`
下面这些描述只应视为未来扩展方向,不能再当成“当前 UI 已实现”:
## 讨论补充
### 为什么不能继续用“一个大聊天框”
单一聊天框模型在以下场景会迅速失效:
- 用户并行处理多个任务
- 本地 / 远程 / AI Gateway Only 频繁切换
- skills 与模型依赖任务上下文
- 用户需要回到旧线程继续追问
一旦线程态和全局态混用,用户会立刻遇到:
- 模式看起来切了,但顶部状态没切
- 远程线程显示了本地连接结果
- skills 继承错线程
- 附件或草稿进入错误任务
### 为什么顶部状态必须线程化
用户不会区分“全局 runtime”与“当前任务线程”。
用户只会看见:
- 我当前在哪个任务里
- 这个任务现在通过哪条链路工作
- 这个任务到底连没连上
所以顶部状态必须遵守“当前线程优先”,否则用户会失去信任。
### 产品定位上的收益
把 Assistant 从“聊天框”升级成“任务工作台”后,后续功能才更自然:
- 多 Agent 协作
- 线程归档
- 右侧线程上下文抽屉
- 线程级输入草稿持久化
- 发送前附件的线程级草稿隔离
- 新线程可选继承当前线程已选 skills
- 线程导出
- 线程模板
- 线程级自动化
- 线程级审阅与导出
这也是后续做任务列表、归档、线程模板、任务恢复的前提。
## 为什么仍然坚持线程优先
虽然当前 UI 还没把所有线程信息都集中到一个面板里,但线程优先原则已经成立:
- 当前线程决定执行模式
- 当前线程决定模型
- 当前线程决定 imported / selected skills
- 当前线程决定 connection chip 显示
这也是后续继续扩展任务工作台能力的基础。
## 相关文档
- [模式切换与线程连续追问](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate.svc.plus/docs/cases/thread_mode_switch_followup.md)
- [XWorkmate 集成架构](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate.svc.plus/docs/architecture/xworkmate-integrations.md)
- [模式切换与线程连续追问](/Users/shenlan/workspaces/cloud-neutral-toolkit/XWorkmate.svc.plus/docs/cases/thread_mode_switch_followup.md)
- [XWorkmate 集成架构](/Users/shenlan/workspaces/cloud-neutral-toolkit/XWorkmate.svc.plus/docs/architecture/xworkmate-integrations.md)

View File

@ -2,10 +2,13 @@
## 目标
这次补丁保持现有 UI 不变,只重设计 `XWorkmate` 的本地配置与任务会话持久层,满足两个约束
本文记录 `XWorkmate.svc.plus` 当前桌面端本地持久化实现的真实基线,并明确区分
- 本地配置和任务会话必须能跨重启、跨覆盖安装恢复。
- 持久化以前提 `secure storage` 为本地信任根,避免把可恢复状态明文落盘。
- 当前正在使用的持久化路径
- 仅用于旧版本恢复的 legacy sealed-state 路径
- secret 与 recoverable local state 的边界
如果后续重新引入 sealed local state这份文档必须和 `SettingsStore` 写路径、测试断言一起更新。
## 当前实现基线v0.6.1
@ -15,68 +18,79 @@
- `~/Library/Application Support/plus.svc.xworkmate/xworkmate`
关键文件与目录:
当前活跃文件与目录:
- `config-store.sqlite3``SettingsStore` 主库)
- `settings-snapshot.json`durable mirror
- `assistant-threads.json`durable mirror
- `gateway-auth/secure-storage/*``SecretStore` 文件型安全存储 fallback
- `config-store.sqlite3`
- `SettingsStore` 主库
- `settings-snapshot.json`
- `SettingsSnapshot` 的 durable mirror
- `assistant-threads.json`
- `AssistantThreadRecord` 列表的 durable mirror
- `gateway-auth/secure-storage/*`
- `SecretStore` 的文件型 secure-storage fallback
### 2) 首次安装初始化
- `SettingsStore.initialize()` 会初始化并打开 `config-store.sqlite3`
- `SecretStore.initialize()` 会初始化 `gateway-auth``secure-storage` 目录结构
- 因此 DMG 首次安装后,重启前无需手工“触发一次保存”即可完成持久化目录与主存储文件的准备。
- `SettingsStore.initialize()` 会初始化并打开 `config-store.sqlite3`
- `SecretStore.initialize()` 会初始化 `gateway-auth``secure-storage` 目录结构
- 因此首次安装后,不需要等用户手工保存一次,目录与主存储文件就会被准备好
### 3) 升级与重启行为
- 应用升级 / 系统更新重启不会改写或重置既有路径。
- 只在用户主动执行“设置 -> 诊断 -> 清理任务线程与本地配置”时清理本地 settings/thread 状态
- 清理流程不删除已保存 secretsGateway token/password、AI Gateway API key、Vault token 等)
- 应用升级 / 系统更新重启不会改写既有持久化目录
- 用户主动执行“设置 -> 诊断 -> 清理任务线程与本地配置”时清理的是本地 settings / thread 状态
- 清理流程不删除已保存 secretsGateway token / password、AI Gateway API key、Vault token、device token 等)
### 4) 路径解析失败策略(默认)
- 默认策略为 `fail-fast`:当 `SettingsStore` 无法解析或打开耐久数据库路径时,直接抛错,不再静默降级为内存持久化。
- 这样可以避免“看起来保存成功、重启后全部丢失”的隐性故障。
- 默认策略仍然是 `fail-fast`
- 当 `SettingsStore` 无法解析或打开耐久数据库路径时,直接抛错
- 只有显式开启 `allowInMemoryFallback` 时才允许内存数据库回退
### 5) 内存回退(仅显式开启场景)
### 5) 当前最重要的实现结论
- 仅在显式开启 `allowInMemoryFallback`(主要用于测试/诊断)时允许内存回退。
- 即使发生内存回退,也会在后续写入和销毁阶段尽力回写同步到耐久目录(若路径恢复可用)。
核心结论:
- `FlutterSecureStorage` 仍是长期 secret 的主存储。
- 本地配置和任务会话不直接明文写入 SQLite / JSON而是先用本地状态密钥加密后再落盘。
- 本地状态密钥本身必须优先保存在主 secure storage不再把它当成普通可降级 secret。
- 长期 secret 继续通过 `SecretStore` 持久化,主路径是 `FlutterSecureStorage`
- `SettingsSnapshot``AssistantThreadRecord` 当前写入的是明文 JSON 字符串
- 会写入 `config-store.sqlite3`
- 也会写入 `settings-snapshot.json` / `assistant-threads.json`
- `assistant-state-backup.json`、`sealedState`、`xworkmate.local_state.key` 现在不是当前主写路径
- 它们只保留在旧版本恢复 / 迁移兼容逻辑里
## Trust Boundary
需要明确区分 3 类状态:
当前需要区分 3 类状态:
1. 用户输入的高敏感 secret
- Gateway shared token
- Gateway password
- AI Gateway API key
- Vault token
### 1. 高敏感 secret
2. 可恢复但不应明文落盘的本地状态
- `SettingsSnapshot`
- Assistant 任务线程记录
- 最后活动线程
- 本地恢复 backup
- Gateway shared token
- Gateway password
- AI Gateway API key
- Vault token
- device token / device identity 私钥材料
3. 仅调试或测试环境可接受的替代路径
- 注入式 secure storage client
- 临时文件型 secure storage fallback
### 2. 可恢复的本地应用状态
- `SettingsSnapshot`
- `AssistantThreadRecord` 列表
- assistant custom task titles
- archived task keys
- last session key
- 本地审计 trail
### 3. Legacy sealed-state 恢复输入
- 旧版 `assistant-state-backup.json`
- 旧版 `xworkmate.sealed.local-state.v1` payload
- `local-state-key.txt`
- secure storage 里的 `xworkmate.local_state.key`
边界规则:
- 第 1 类状态优先进入 secure storagesecure storage 超时或异常时,可进入持久化 fallback 文件,但绝不退化成“仅内存”。
- 第 2 类状态不直接进入 `SharedPreferences` 或明文 SQLite必须先 sealed。
- 第 3 类路径只用于 debug / test不进入 release 行为。
- 第 1 类状态`SecretStore`
- 第 2 类状态当前走 `SettingsStore`,属于 recoverable app state不是 secret store
- 第 3 类状态只用于 recovery / migration不是当前版本的常规写入目标
## 架构图
## 当前架构图
```mermaid
flowchart TD
@ -85,164 +99,173 @@ flowchart TD
B --> E["SecureConfigStore"]
D --> E
E --> F["Primary Secure Storage<br/>FlutterSecureStorage"]
E --> G["Local State Key<br/>xworkmate.local_state.key"]
G --> H["AES-GCM Seal / Unseal"]
E --> F["SecretStore"]
E --> G["SettingsStore"]
H --> I["SQLite config-store.sqlite3"]
H --> J["Durable state files<br/>settings-snapshot.json<br/>assistant-threads.json"]
H --> K["assistant-state-backup.json<br/>schemaVersion=2 / sealedState"]
F --> H["FlutterSecureStorage"]
F --> I["gateway-auth/secure-storage/*<br/>file fallback"]
E --> L["Secure secret fallback files<br/>gateway-auth/*"]
G --> J["config-store.sqlite3"]
G --> K["settings-snapshot.json"]
G --> L["assistant-threads.json"]
M["Legacy sealed-state sources"] --> N["legacy recovery / migration"]
N --> G
```
说明:
- 当前活跃写路径是 `SecretStore` + `SettingsStore`
- legacy sealed-state 只参与读旧数据并迁移到当前 store不参与当前常规写入
## 存储分层
### 1. Primary Secure Storage
### 1. 当前 secret 存储
用途:
- 保存 Gateway token / password / AI Gateway API key / Vault token
- 保存本地状态密钥 `xworkmate.local_state.key`
- 保存 Gateway token / password
- 保存 AI Gateway API key
- 保存 Vault token
- 保存 device identity / device token
关键要求:
实现要点
- 主路径仍然`FlutterSecureStorage`
- 本地状态密钥不允许再走“通用 secret fallback”
- 如果主 secure storage 不可用,不允许把本地状态密钥退化成普通文件常态
- 主路径是 `FlutterSecureStorage`
- 当 secure storage 不可用时,`SecretStore` 会尝试提升到文件型 fallback
- 文件型 fallback 位于 `gateway-auth/secure-storage/*`
### 2. Sealed Local State
本地配置和任务会话的持久化结构统一改为:
- `storageFormat = xworkmate.sealed.local-state.v1`
- `nonce`
- `cipherText`
- `mac`
加密方式:
- AES-GCM 256
- 每次写入使用新的随机 nonce
- AAD 绑定存储 key避免跨 key 错读
### 2. 当前本地状态持久化
当前覆盖对象:
- `xworkmate.settings.snapshot`
- `xworkmate.assistant.threads`
- `assistant-state-backup.json`
- `xworkmate.secrets.audit`
### 3. Durable Recovery Files
实现要点:
当 SQLite 不可用时,仍需保证本地状态可以恢复。为此保留两类耐久化文件:
- `SettingsSnapshot` 通过 `toJsonString()` 写入
- `AssistantThreadRecord` 列表通过 `jsonEncode(...)` 写入
- 当前写路径没有 AES-GCM seal / unseal
- durable mirror 文件内容当前也是明文 JSON不是 sealed envelope
### 3. Durable mirror files
当前保留两类 durable mirror
- `settings-snapshot.json`
- `assistant-threads.json`
注意:
语义
- 文件名虽然保持旧风格,但内容已改为 sealed payload不再是明文 JSON。
- 作为 SQLite 的文件镜像 / fallback 来源
- 也是测试里会直接读取和断言的当前持久化内容
### 4. Assistant Backup
### 4. Legacy sealed-state recovery path
`assistant-state-backup.json` 升级到 schema v2
旧版 sealed local state 兼容仍然保留,但仅用于 recovery
- 用 `sealedState` 保存整体恢复快照
- 不再把 settings / threads 明文拼进 backup
- 识别旧版 `xworkmate.sealed.local-state.v1`
- 读取旧版 `assistant-state-backup.json` 里的 `sealedState`
- 通过 legacy local state key 解密旧 payload
- 成功恢复后重写到当前 `SettingsStore`
这样做的目的:
条路径的目标是兼容旧数据,不代表当前版本仍在主动写 sealed local state。
- 避免备份文件成为最容易泄露的明文副本
- 保持“数据库损坏时仍可恢复”的能力
## 写入流程
## 当前写入流程
### SettingsSnapshot
1. `SettingsController` 生成新的 `SettingsSnapshot`
2. `SecureConfigStore.saveSettingsSnapshot()` 进入本地状态写队列
3. 读取或生成 `xworkmate.local_state.key`
4. 先 sealed再写入 SQLite / durable file / backup
1. `SettingsController``AppController` 生成新的 `SettingsSnapshot`
2. `SecureConfigStore.saveSettingsSnapshot()`
3. `SettingsStore.saveSettingsSnapshot()`
4. `snapshot.toJsonString()`
5. 写入 SQLite
6. 同步写入 `settings-snapshot.json`
### Assistant Threads
1. `AppController` 更新线程记录
2. 持久化进入 `_assistantThreadPersistQueue`
3. `SecureConfigStore.saveAssistantThreadRecords()` 串行 sealed 写入
4. 同步刷新 SQLite / durable file / backup
2. 更新被串行排入 `_assistantThreadPersistQueue`
3. `SecureConfigStore.saveAssistantThreadRecords()`
4. `jsonEncode(records.map(...))`
5. 写入 SQLite
6. 同步写入 `assistant-threads.json`
这么做是为了避免异步写晚到,把旧线程快照覆盖新状态。
这么做的目标是避免异步写晚到覆盖较新的线程快照;当前目标不是加密封装
## 读取与恢复流程
## 当前读取与恢复流程
恢复顺序:
1. 优先读 SQLite
2. SQLite 不可用时读 durable state files
3. 若主状态缺失,再读 `assistant-state-backup.json`
4. 若读到的是旧明文格式,则立即迁移为 sealed 格式
1. 初始化 SQLite
2. 优先读取 SQLite entry
3. SQLite 读不到时,再读 durable mirror 文件
4. 如果当前 state 不可读,再尝试 legacy recovery
5. 若发现旧 sealed-state 但缺少 key则产生 locked recovery report
迁移原则
补充说明
- 兼容旧明文快照,避免升级后直接丢历史
- 一旦成功恢复,就把旧格式重写成 sealed 新格式
- legacy `SharedPreferences` 里的本地状态在迁移后会被清理
- `SharedPreferences` 只作为旧数据迁移兼容来源,不是当前桌面端的主状态真值源
- Web 端有独立的 `WebStore`,不适用这里的桌面持久化链路
## Secure Secret Fallback
## Legacy backup / sealedState 的当前语义
Secret fallback 仍然保留,但语义变了
当前代码里
- 用于 Gateway token / password / API key 等长期 secret 的持久化兜底
- 不再因为一次超时就退化成“仅内存”
- 这样即使 secure storage 一时不可用,重启后 secret 仍能恢复
- `assistant-state-backup.json` 只在 legacy recovery 时读取
- `sealedState` 只在旧版 backup 或旧版 durable value 解密时出现
- `xworkmate.local_state.key` 只通过 legacy loader 参与旧数据恢复
约束
因此这三者现在应该被理解为
- `xworkmate.local_state.key` 不在通用 fallback 白名单里
- 对旧版遗留的 `local-state-key.txt`,启动时做一次迁移,成功后删除
- 兼容旧版本
- 避免升级后直接丢历史
- 不属于当前日常写入架构
## Clear 行为
`clearAssistantLocalState()` 清理:
`clearAssistantLocalState()` 当前会清理:
- 本地 settings snapshot
- 本地 assistant thread records
- durable state files
- assistant backup
- `SettingsSnapshot`
- `AssistantThreadRecord` 列表
- `settings-snapshot.json`
- `assistant-threads.json`
- 旧版 `assistant-state-backup.json`(如果存在)
不会误删:
- 已保存的 Gateway token / password
- Gateway token / password
- AI Gateway API key
- Vault token
- 其他 secure refs
- device token / device identity
## Debug / Test 策略
为了让测试稳定运行,新增了可注入的 secure storage 层
为了让测试稳定运行,当前保留可注入的 secure storage client
- `SecureStorageClient`
- `FlutterSecureStorageClient`
- `FileSecureStorageClient`
- `MemorySecureStorageClient`
策略
策略:
- release使用真实 `FlutterSecureStorage`
- debug / test允许走注入式或文件型 secure storage保证单测和回归可跑
- release优先真实 `FlutterSecureStorage`
- debug / test允许注入式或文件型 secure storage
- `allowInMemoryFallback` 只在显式场景下允许内存数据库回退
这不会改变 release 的安全边界。
## 当前文档结论
## 与现有 UI 的关系
当前桌面端本地持久化不是 sealed local state 架构,而是:
这次补丁不改:
- secrets 走 secure storage / file fallback
- recoverable local app state 走 SQLite + plain JSON durable mirrors
- legacy sealed-state 只用于恢复旧数据
- Gateway 设置页结构
- Assistant 任务线程 UI
- 模型、skills、入口按钮布局
如果后续要把本地状态重新升级为 sealed payload必须同步更新
变化只在持久层和恢复链路:
- 重启后不再因为 secure storage 一次超时而丢本地配置
- 覆盖安装后本地配置与任务会话仍可恢复
- 本地 snapshot / backup 不再以明文保存
- `SettingsStore` 写路径
- 文档中的架构图与存储分层
- 相关测试断言

View File

@ -17,15 +17,20 @@ This document records the default `simple` theme token set for `XWorkmate.svc.pl
## Radius
- card radius: `6`
- input radius: `8`
- button radius: `8`
- dialog radius: `5`
- card radius: `16`
- input radius: `14`
- button radius: `12`
- dialog radius: `18`
- chip radius: `12`
- sidebar radius: `20`
## Size
- input height: `36`
- button height: `16`
- textarea height: `36`
- input height: `40`
- button height: `30` desktop / `36` mobile
- toolbar height: `40`
- sidebar item height: `34`
## Source Of Truth

View File

@ -2,39 +2,50 @@
## 概述
XWorkmate 现阶段的集成基线已经从“单一 Codex bridge”升级为“统一发现与分发中心”。App 负责发现、托管和分发三类协作资产:
XWorkmate 现阶段已经不只是“单一 Codex bridge”但当前实现也不是一个单独的 “Discovery / Distribution Catalog” 模块。
1. `skills`
2. `MCP server list`
3. `AI Gateway` 默认注入
当前集成能力分散在几条明确的实现路径里:
运行时上XWorkmate 不再把 CLI 视为孤立工具,而是通过本地 broker 与编排层统一驱动 `OpenClaw / Codex / Claude / Gemini / OpenCode`
1. `GatewayRuntime`
- 负责 OpenClaw Gateway 的实时 RPC、会话、chat、pairing、cron
2. `MultiAgentBrokerServer` + `MultiAgentOrchestrator`
- 负责多 Agent 协作运行
3. `MultiAgentMountManager`
- 负责按 adapter 做 CLI 能力探测、MCP reconcile、挂载状态汇总
4. `CodexConfigBridge` / `OpencodeConfigBridge`
- 负责特定 CLI 的配置文件写入
5. Assistant composer 与 feature flags
- 决定当前哪些集成入口真实对用户可见
也就是说,当前架构更接近“分布式集成面”,不是单一 catalog service。
## 当前架构基线
```mermaid
flowchart LR
X["XWorkmate App"] --> D["Discovery / Distribution Catalog"]
X --> B["MultiAgentBroker<br/>WebSocket JSON-RPC"]
X --> G["OpenClaw Gateway / Host"]
B --> O["MultiAgentOrchestrator"]
O --> C["Codex CLI"]
O --> L["Claude CLI"]
O --> M["Gemini CLI"]
O --> P["OpenCode CLI"]
C --> A["AI Gateway"]
L --> A
M --> A
P --> A
A --> OL["Ollama / Upstream Model Endpoints"]
X["XWorkmate App"] --> GR["GatewayRuntime"]
X --> BM["MultiAgentBrokerServer<br/>WebSocket JSON-RPC"]
X --> MM["MultiAgentMountManager"]
X --> NO["CodeAgentNodeOrchestrator"]
X --> UI["Assistant composer / Settings / Feature flags"]
BM --> O["MultiAgentOrchestrator"]
O --> C["Codex / Claude / Gemini / OpenCode"]
MM --> MA["Codex / Claude / Gemini / OpenCode / OpenClaw adapters"]
MA --> CFG["Managed config writes / mcp list / local file discovery"]
GR --> G["OpenClaw Gateway / Host"]
NO --> G
C --> A["AI Gateway or Ollama endpoint"]
```
关键点:
- `XWorkmate App` 是唯一的 discovery / distribution center。
- `MultiAgentBroker` 是多 CLI 协作的本地运行时入口。
- `OpenClaw` 既是现有 Gateway 集成面,也是可被托管发现的宿主控制面。
- `AI Gateway` 的语义是“XWorkmate 协作运行默认 provider”不是用户全局 provider 替换器。
- `OpenClaw` 既是现有 Gateway 集成面,也是当前 app-mediated code-agent dispatch 的宿主控制面。
- `AI Gateway` 既可以是 direct AI 对话入口,也可以是协作运行的注入式模型入口。
- 当前没有一个单独命名为 `Discovery / Distribution Catalog` 的实现模块。
## 1. OpenClaw Gateway / Host
@ -57,19 +68,19 @@ flowchart LR
- `agent/register`
- `memory/sync`
新的定位:
当前定位:
- 继续作为 Gateway RPC 面存在
- 额外纳入“可挂载目标”集合。
- 发现 `agents / skills / plugins` 状态,但不覆盖用户现有默认 agent。
- 继续作为 Gateway RPC 面存在
- 也是 app-mediated code-agent dispatch 的控制面目标
- 在 mount 视角下OpenClaw 目前更多是“本地发现 + 宿主控制面”,不是一个统一的 skills / plugins catalog service
## 2. AI Gateway
用途:
- 统一模型入口
- 作为 XWorkmate 协作运行的默认模型路由
- 为外部 CLI 提供 launch-scoped 或托管 provider 注入
- direct AI 对话入口
- 协作运行时的模型注入入口
- 对部分 CLI 的配置桥接入口
边界:
@ -79,9 +90,12 @@ flowchart LR
当前策略:
- `Codex` 可以追加 `xworkmate` provider 托管块
- `Claude / Gemini / OpenCode` 优先采用 launch-scoped 注入
- Gateway 不可用时允许回退到 CLI 原有配置
- `CodexConfigBridge` 可以写入受管 provider / MCP block
- `MultiAgentOrchestrator` 在协作运行中会通过环境变量或 `ollama launch` 传递模型入口
- `Claude / Gemini` 的 mount reconcile 目前主要做 discoveryAI Gateway 仍保持 launch-scoped
- `OpenCode` 当前有受管 MCP configAI Gateway 语义仍偏 launch-scoped / runtime injection
换句话说AI Gateway 能力是分散落地的,不是所有 CLI 都通过同一条托管 provider 路径接入。
## 3. Multi-Agent Runtime
@ -107,12 +121,15 @@ flowchart LR
### UI 接线
- Assistant 继续复用现有 composer、附件、当前会话
- Settings 继续复用现有 Multi-Agent 区块
- 桌面端真正对用户可见的协作入口,当前主要是 Assistant composer 上的协作 toggle
- `SettingsPage` 里有 Multi-Agent 配置区块与 detail 页面代码,但桌面端 `settings.agents` 仍被 feature flag 关闭
- 不新增独立任务页面
## 4. 发现与分发
XWorkmate 统一维护两类状态:
当前实现里,`managed / external` 更像一套按 adapter 执行的操作规则,而不是单独的中心化状态目录。
XWorkmate 仍然区分两类对象:
- `managed`
- 由 App 创建与维护的托管项
@ -124,16 +141,17 @@ XWorkmate 统一维护两类状态:
- 只更新 XWorkmate 托管项
- 不删除外部已有项
- 启动时与保存设置后自动 reconcile
- 这套规则当前由 `MultiAgentMountManager` 在各 adapter 上分别执行
## 5. 挂载入口矩阵
| 目标 | Skills 挂载入口 | MCP 挂载入口 | AI Gateway 挂载入口 |
| --- | --- | --- | --- |
| OpenClaw | 发现 `skills / plugins / agents`broker 注入上下文 | 不作为 MCP 主挂载点 | XWorkmate 协作路径默认 route |
| Codex | `AGENTS.md` / skill 上下文 / broker 注入 | `~/.codex/config.toml` 托管块 | `model_providers.xworkmate`,不替换用户默认 |
| Claude | broker 注入 | `claude mcp list/add/remove` 发现与兼容 | 启动参数 / 环境注入 |
| Gemini | broker 注入,后续可扩展 `extensions` | `gemini mcp list/add/remove` 发现与兼容 | 启动参数 / 环境注入 |
| OpenCode | broker 注入,后续可扩展 agent preset | `~/.opencode/config.toml` 托管块 | 启动参数或托管 preset 注入 |
| OpenClaw | 本地文件 / 目录发现 + Gateway 控制面 | 不作为 MCP 主挂载点 | app-mediated dispatch / gateway route |
| Codex | 当前线程 skills 上下文 +协作运行注入 | `~/.codex/config.toml` 受管 MCP block | 受管 provider bridge + runtime injection |
| Claude | 当前线程 skills 上下文 +协作运行注入 | `claude mcp list` 做 discovery | launch-scoped / env / `ollama launch` |
| Gemini | 当前线程 skills 上下文 +协作运行注入 | `gemini mcp list` 做 discovery | launch-scoped / env |
| OpenCode | 当前线程 skills 上下文 +协作运行注入 | `~/.opencode/config.toml` 受管 MCP block | runtime injection |
## 6. 外部 Provider 与执行路径
@ -149,7 +167,7 @@ XWorkmate 统一维护两类状态:
现状:
- `codex` 仍是当前最完整 provider
- 其他 CLI 通过 `CliMountAdapter` 与 broker 接入
- 其他 CLI 当前主要通过 `CliMountAdapter` discovery / reconcile 与 `MultiAgentOrchestrator` 运行时调用接入
- 多 provider 调度 UI 不是当前交付目标
## 7. 安全边界
@ -172,17 +190,21 @@ XWorkmate 统一维护两类状态:
实现约束:
- Gateway 集成页不再重复显示顶层全局 `Save / Apply`,避免与卡片内动作语义冲突。
- `settings.gateway_setup_code``settings.agents` 当前均按 `experimental + enabled: false` 发布策略控制。
- 桌面端 `settings.gateway_setup_code``settings.agents` 当前都被 feature flag 关闭。
- 但桌面端 `assistant.multi_agent` 仍然开启,所以协作入口当前主要暴露在 Assistant composer而不是设置页独立标签。
## 相关代码
- `lib/app/app_controller.dart`
- `lib/app/app_controller_desktop.dart`
- `lib/app/app_controller_web.dart`
- `lib/features/assistant/assistant_page.dart`
- `lib/features/settings/settings_page.dart`
- `lib/runtime/gateway_runtime.dart`
- `lib/runtime/runtime_models.dart`
- `lib/runtime/multi_agent_orchestrator.dart`
- `lib/runtime/multi_agent_broker.dart`
- `lib/runtime/multi_agent_mounts.dart`
- `lib/runtime/codex_config_bridge.dart`
- `lib/runtime/opencode_config_bridge.dart`
- `lib/runtime/code_agent_node_orchestrator.dart`
- `lib/runtime/runtime_coordinator.dart`

View File

@ -198,6 +198,9 @@ Primary fields:
- _pendingAiGatewayApply
- settings.gatewayProfiles
- settings.assistantExecutionTarget
- settings.assistantCustomTaskTitles
- settings.assistantArchivedTaskKeys
- settings.assistantLastSessionKey
Sources:
- settings
@ -216,6 +219,10 @@ Responsibilities:
- Persist secure secrets
- Persist OpenClaw connection source profiles
- Persist the default work mode for newly created threads
- Persist assistant task metadata that is not owned by `AssistantThreadRecord`
- custom task titles
- archived task keys
- last restored session key
- Make the saved configuration take effect only when Apply is executed
Important APIs:
@ -423,11 +430,18 @@ Resolution rule:
Primary source:
- assistantSessions
- _taskSeeds in AssistantPage as a rendering cache
- settings.assistantCustomTaskTitles
- settings.assistantArchivedTaskKeys
Important rule:
Task list is a derived representation of thread/session state.
Task list must not become the owner of mode, model, or skill state.
Important companion rule:
Task titles, archive membership, and last-session recovery are not owned only by
`AssistantThreadRecord`. Part of that metadata is persisted in `SettingsSnapshot`
and must be considered when reconstructing the task list.
Implementation note:
_taskSeeds is still a cache of derived values such as title, preview, status,
owner, surface, and executionTarget. It is not an authoritative source, but it