From ae1faa03d07c9365702ecf3178097af290f596e2 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Mon, 23 Mar 2026 08:59:44 +0800 Subject: [PATCH] docs: clarify architecture baselines --- ...sistant-thread-information-architecture.md | 333 +++++++----------- .../secure-local-persistence-architecture.md | 287 ++++++++------- docs/architecture/simple-theme-default.md | 17 +- docs/architecture/xworkmate-integrations.md | 104 +++--- .../xworkmate-internal-state-architecture.md | 14 + 5 files changed, 378 insertions(+), 377 deletions(-) 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