# DSH Session Groups 架构
本文说明插件如何与 **DSH 侧栏 / 控制器** 以及 **session-groups 本地视图状态** 交互。
核心原则:
> DSH 负责“有什么”;插件本地负责“怎么排”。
> 会话/工作区主数据由 DSH Host 持有;分组、折叠、顺序、颜色是浏览器本地的视图叠加层。
---
## 1. 组件总览
```mermaid
flowchart TB
subgraph HOST["DSH Host / 数据服务"]
H1["会话 Sessions"]
H2["工作区 Workspaces"]
H3["状态 / 归档 / 置顶 / 当前会话"]
TOOL["agent tool:session_groups
Host 审核 + 请求中转"]
end
subgraph WEB["DSH Web 浏览器"]
SHELL["DSH 侧栏 Shell
slots"]
subgraph PLUGIN["插件 client.js"]
APPLY["apply(ctx)"]
MODE["ViewModeStore
grouped / official"]
TOGGLE["sidebar.footer.action
视图切换按钮"]
BROWSER["sidebar.workspaces occupant
SessionGroupsBrowser"]
DERIVE["派生视图模型
sections / treeLayout / group tree"]
UI["React UI
Workspace / Group / SessionRow / Menu"]
ACT["交互层
点击 / 菜单 / HTML5 拖拽"]
BRIDGE["bridge poller
读取快照 / 执行 apply"]
end
STORE[("session-groups 本地视图状态
localStorage: dsh.session-groups.v1")]
end
APPLY -->|"注册"| TOGGLE
APPLY -->|"监听 slot"| SHELL
TOGGLE --> MODE
MODE -->|"grouped:安装 occupant"| BROWSER
MODE -->|"official:卸载 occupant,恢复原版"| SHELL
HOST -->|"snapshot hooks
useSessions / useWorkspaces / useSessionStatus"| BROWSER
BROWSER --> DERIVE --> UI
UI --> ACT
ACT -->|"视图操作
分组 / 嵌套 / 排序 / 折叠 / 换色"| STORE
STORE -->|"loadPrefs / reconcile"| BROWSER
ACT -->|"官方操作
打开 / 重命名 / 归档 / 新建工作区"| HOST
TOOL -->|"审核通过的 read / apply 请求"| BRIDGE
BRIDGE -->|"读 localStorage / 校验后写入"| STORE
HOST -->|"新快照"| BROWSER
```
---
## 2. 启动与 slot 注册
```mermaid
sequenceDiagram
autonumber
participant Host as DSH Host / 数据服务
participant Shell as DSH Shell / Slots
participant Plugin as client.js / apply(ctx)
participant Store as session-groups localStorage
participant Browser as SessionGroupsBrowser
participant UI as React UI
Note over Plugin,Shell: 插件加载
Plugin->>Shell: 注册 sidebar.footer.action(视图切换)
Plugin->>Shell: 监听 sidebar.workspaces
User->>UI: 切换到「分组视图」
Plugin->>Shell: 注册 occupant
session-groups-browser(priority -100)
Note over Browser,Host: 首次渲染
Browser->>Store: loadPrefs()
Browser->>Host: useSessions / useWorkspaces / useSessionStatus
Host-->>Browser: 当前快照
Browser->>Browser: 派生 sections / treeLayout / group tree
Browser->>UI: 渲染侧栏
Note over UI,Store: 普通视图操作只改本地
User->>UI: 拖动会话/组、折叠、换色
UI->>Browser: setPrefs(...)
Browser->>Store: savePrefs()
Browser->>UI: 重渲染
Note over UI,Host: 官方操作走 Host
User->>UI: 打开 / 重命名 / 归档 / 新建工作区
UI->>Host: 官方 API
Host-->>Browser: 新快照
Browser->>Store: reconcile 失效引用
Browser->>UI: 收敛后重渲染
```
---
## 3. 数据职责
| 数据 | 权威所有者 | 插件行为 |
|---|---|---|
| 会话是否存在 | DSH Host | 只读快照;用于裁剪分组引用 |
| 会话属于哪个工作区 | DSH Host | 分组严格按工作区隔离 |
| 会话标题 / 状态 / 归档 / 置顶 | DSH Host | 渲染时直接读取,不写入本地 |
| 当前打开的会话 | DSH Host | 用于选中态 / 自动展开祖先 |
| 分组 / 子组 / 父组关系 | 插件本地 | 写入 localStorage |
| 组名 / 颜色 / RGB 自定义色 | 插件本地 | 写入 localStorage |
| 组和子组折叠状态 | 插件本地 | 写入 localStorage |
| 组内顺序 / 顶层顺序 / 子工作区顺序 | 插件本地 | 写入 localStorage |
| 工作区树 / 单列表 / 隐藏归档 | 插件本地 | 写入 localStorage |
| AI 整理请求 | Host 工具审核 + 浏览器校验 | 审核通过后由浏览器写入同一份本地状态 |
| 不属于任何工作区的会话 | DSH Host 会话列表 | 汇总为末尾「未分组」伪工作区;顺序与折叠写浏览器本地 |
| 子代理运行状态 | DSH Session 状态 + `subagentTiming` | 父会话聚合直接子代理,子行显示自身状态 |
---
## 4. 一次渲染的数据流
```text
DSH 快照
├─ workspaces.items
├─ sessions.byId / ids
└─ sessionStatuses
│
▼
buildWorkspaceParentMap / group tree
│
▼
sections
├─ visible sessions
├─ groups + members + descendants
├─ topItems(组 + 未分组会话)
├─ ungrouped order
└─ child workspace tree
│
▼
renderWorkspace / renderGroup / renderFlatList
│
▼
侧栏 UI
```
`sections`、`treeLayout` 这些派生数据不持久化,每次从“快照 + 本地 prefs”计算。
---
## 5. 本地视图状态
存储键:
```text
dsh.session-groups.v1
```
结构概要:
```json
{
"groups": {
"workspaceId": [
{
"id": "g1",
"name": "分组",
"color": "#4c7dff",
"collapsed": false,
"parentGroupId": null,
"sessionIds": ["sessionId1", "sessionId2"]
}
]
},
"collapsedWorkspaces": {},
"manualOrders": {},
"topOrders": {},
"childOrders": {},
"groupOrders": {},
"hideArchived": true,
"workspaceTree": false,
"flatList": false
}
```
注意:
- `sessionIds` 是 ID 引用,不是会话内容
- `groups` 是扁平数组,通过 `parentGroupId` 形成最多 3 层的树
- 组内“会话 + 子组”的顺序由 `groupOrders` 保存
- 所有 state 都是浏览器本地的,不跨设备同步
---
## 6. 交互写入路径
### 视图操作:只写本地
```text
拖动会话进组 / 拖出
拖动组嵌套 / 换位
折叠 / 展开
重命名组 / 换色 / RGB
切换布局 / 排序
│
▼
setPrefs(previous => next)
│
├─ localStorage 保存
└─ 当前 UI 重渲染
```
不会修改 DSH 的 `workspace.sessionIds`,也不会移动 Host 上的会话。
### 官方操作:通过 DSH API 修改 Host
```text
打开会话
重命名会话
归档 / 取消归档
置顶 / 取消置顶
分叉会话
新建 / 删除工作区
│
▼
ctx.uiWorkspace / ctx.workspaces / ctx.sessions
│
▼
DSH Host / 数据服务
│
▼
新快照回到插件
```
---
## 7. Agent 工具桥
`session_groups` 是 Host 侧的 agent 工具;浏览器仍是分组数据的唯一所有者。
```mermaid
sequenceDiagram
autonumber
participant Model as Agent / 模型
participant Tool as session_groups(Host)
participant Approval as DSH Approval
participant Route as Host bridge mailbox
participant Client as client.js bridge poller
participant Store as session-groups localStorage
Model->>Tool: action=read
Tool->>Route: 入队 snapshot 请求
Client->>Route: 轮询并领取请求
Client->>Store: loadPrefs() + 当前工作区/会话快照
Client-->>Tool: 返回工作区、会话、分组
Tool-->>Model: read 结果(不修改)
Model->>Tool: action=apply + 完整分组方案
Tool->>Tool: 结构校验(id / 父组 / 3 层 / 重复会话)
Tool->>Approval: 请求用户审核(附改动摘要)
Approval-->>Tool: allowed-once / rejected / cancelled / unavailable
alt 未批准
Tool-->>Model: 拒绝,不写任何数据
else 已批准
Tool->>Route: 入队 apply 请求
Client->>Route: 领取请求
Client->>Client: 校验工作区归属、会话恰好覆盖一次、id 冲突
Client->>Store: 替换该工作区分组 + 顺序
Client-->>Tool: 返回写入摘要
Tool-->>Model: apply 结果
end
```
约束:
- `read` 不需要审核;`apply` 必须得到一次性的用户批准。
- 方案必须覆盖指定工作区的每个可见会话且恰好一次。
- 分组数据不迁移到 Host;Host mailbox 只是短生命周期请求队列,插件卸载即清空。
- 浏览器端校验失败时,即使已经审核通过也不会写入。
- bridge 需要分组视图处于挂载状态;原版视图下没有可读的本地分组快照。
---
## 8. 冲突与 reconcile
严格来说多数不是“字段冲突”,而是**本地引用失效**。
优先级:
```text
Host 事实(存在性、归属、标题、状态) > 本地视图排列
本地结构合法性(父引用、3 层限制) > 原样保留
```
reconcile 时机:
- 工作区/会话快照变化后
- 或工作区数据从 pending 进入 ready 后
- 不会在 pending 的空快照上执行,避免误删分组
处理规则:
| 情况 | 处理 |
|---|---|
| 会话不在当前工作区了 | 从所有组移除该 ID |
| 组变空且无子组 | 删除组 |
| 工作区被删除 | 删除该工作区所有本地分组与顺序 |
| 父组引用失效 / 成环 / 超 3 层 | 断开或提升到合法层级 |
| `topOrders` / `childOrders` / `groupOrders` 含无效 key | 清理无效项 |
| 自定义 RGB 颜色是合法 hex | 保留 |
| 本地 JSON 损坏 | 回退默认视图状态 |
结论:
> **Host 决定“有什么”,本地决定“怎么展示”。**
> Host 变化会裁剪本地引用;本地视图变化不会反向修改 Host 的会话结构。
---
## 9. 未分组伪工作区
DSH 原版把不属于任何已注册工作区的会话放在列表末尾的 **Ungrouped** 分组。本插件沿用同一结构:
- 未分组行使用与普通工作区相同的文件夹标题、折叠箭头和展开 / 收起状态
- 会话顺序为:当前空白会话 → 置顶会话 → 其余;其余默认按最近更新,拖动后写入浏览器本地手动顺序
- 折叠状态和手动顺序保存在 `collapsedWorkspaces.__ungrouped__` / `manualOrders.__ungrouped__`
- 没有重命名、删除、新建会话等工作区操作;它只是 Host 会话列表的末尾桶
- 工作区树和单列表模式下仍作为末尾桶处理,不伪造 Host 工作区
---
## 10. 文件地图
| 文件 | 作用 |
|---|---|
| `client.js` | 浏览器运行时:派生渲染、拖拽、本地状态、UI、i18n、agent bridge 客户端 |
| `index.js` | Host 入口:`session_groups` 工具、用户审核、bridge 请求队列与 Web 路由 |
| `locale/*.json` | 插件元数据本地化 |
| `docs/` | 预览图、主题图、架构文档 |
| `README.md` | 使用说明与功能预览 |
---
## 11. 一句话架构
```text
DSH Host(事实)
│ snapshot / action
▼
插件运行时(投影 + 命令分发)
│
├── session-groups 本地视图状态(分组/顺序/颜色/折叠)
│
▼
派生视图模型 → React UI
```
用户拖动、折叠、换色时,主要是在编辑 **session-groups 本地视图状态**;
打开、重命名、归档、建工作区时,才是通过 DSH 官方 API 修改 **Host 事实数据**。
AI 通过 `session_groups` 工具走同一套本地状态:Host 只审核并中转请求,浏览器负责校验与写入。