diff --git a/docs/architecture/assistant-thread-information-architecture.md b/docs/architecture/assistant-thread-information-architecture.md
index f86fdf4e..886536d8 100644
--- a/docs/architecture/assistant-thread-information-architecture.md
+++ b/docs/architecture/assistant-thread-information-architecture.md
@@ -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)
diff --git a/docs/architecture/secure-local-persistence-architecture.md b/docs/architecture/secure-local-persistence-architecture.md
index 9ec656a2..68fd7269 100644
--- a/docs/architecture/secure-local-persistence-architecture.md
+++ b/docs/architecture/secure-local-persistence-architecture.md
@@ -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 状态。
-- 清理流程不删除已保存 secrets(Gateway token/password、AI Gateway API key、Vault token 等)。
+- 应用升级 / 系统更新重启不会改写既有持久化目录
+- 用户主动执行“设置 -> 诊断 -> 清理任务线程与本地配置”时,清理的是本地 settings / thread 状态
+- 清理流程不会删除已保存 secrets(Gateway 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 storage;secure 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
FlutterSecureStorage"]
- E --> G["Local State Key
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
settings-snapshot.json
assistant-threads.json"]
- H --> K["assistant-state-backup.json
schemaVersion=2 / sealedState"]
+ F --> H["FlutterSecureStorage"]
+ F --> I["gateway-auth/secure-storage/*
file fallback"]
- E --> L["Secure secret fallback files
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` 写路径
+- 文档中的架构图与存储分层
+- 相关测试断言
diff --git a/docs/architecture/simple-theme-default.md b/docs/architecture/simple-theme-default.md
index 92ceeb52..e29e280d 100644
--- a/docs/architecture/simple-theme-default.md
+++ b/docs/architecture/simple-theme-default.md
@@ -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
diff --git a/docs/architecture/xworkmate-integrations.md b/docs/architecture/xworkmate-integrations.md
index 42a00bd9..7304f9cf 100644
--- a/docs/architecture/xworkmate-integrations.md
+++ b/docs/architecture/xworkmate-integrations.md
@@ -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
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
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 目前主要做 discovery,AI Gateway 仍保持 launch-scoped
+- `OpenCode` 当前有受管 MCP config;AI 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`
diff --git a/docs/architecture/xworkmate-internal-state-architecture.md b/docs/architecture/xworkmate-internal-state-architecture.md
index ee1d595b..944136b6 100644
--- a/docs/architecture/xworkmate-internal-state-architecture.md
+++ b/docs/architecture/xworkmate-internal-state-architecture.md
@@ -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