Skip to content
dsh.fish
Bundle

dsh-channel-view

Spike: channel-grouped session view for DeepSeek Harness — sidebar tab injection + session-projection data path (RFC dsh-channel-spec)

Source
RGarvel
stars
1 stars
License
MIT
Updated
Updated yesterday

Readme

# dsh-channel-view(spike → v2.5 权威状态路由 + 使用中角标)

[![npm version](https://img.shields.io/npm/v/dsh-channel-view?label=dsh-channel-view)](https://www.npmjs.com/package/dsh-channel-view)

> A **zero-host-modification** plugin for DeepSeek Harness: injects a parallel `Channels` tab beside the sidebar's "工作区" header and groups every session by declared channel — powered entirely by official extension surfaces (bundle client module, `sidebar.footer.action` slot, session projections, react-dom portal). 不改一行宿主的 DSH 渠道会话视图插件:侧栏「工作区」旁注入平行 Channels tab,按渠道分组全部会话,全程只用官方扩展面。

## 效果预览 / Preview

| 工作区常态 | Channels 激活态(分组 + 脉冲点 + 归档折叠组) |
|---|---|
| ![tab-normal](docs/tab-normal.png) | ![channels-active](docs/channels-active.png) |

DSH「渠道会话视图」原型。**spike 三问已全部验证通过(2026-08-27 实测 CHAIN ALIVE ✔)**:

| # | 扩展面 | 结果 |
|---|---|---|
| 1 | 第三方包 `exports["./client"]` 被浏览器启动图装载 | ✔ |
| 2 | 侧边栏插槽注册(footer.action) | ✔ |
| 3 | 会话投影数据链(宿主注册 → 官方推送帧 → 客户端 `projectionValues`) | ✔ latch 语义,6/8 挂值 |

v2 从"面板演示"进入"平行 tab 形态":

## v2 行为

- **Channels tab 注入**:在 WorkspaceBrowser 的「工作区」标题行内注入 `[工作区 | Channels(n)]` 平行 tab(DOM 锚点 + `react-dom` portal,shell 播种的官方 react-dom 18.3.1,零 monkey-patch、不改宿主);切到 Channels 隐藏官方列表分支,显示按渠道分组的会话列表,**点击行 = 打开会话**(`ctx.sessions.open`)。
- **归档可见(非丢弃)**:`workspaces.archivedSessionIds` 同源拆分——归档会话不进主分组,而是收进 Channels 视图底部的「已归档」折叠组(默认收起、整组浅色),可展开、点击尝试打开;`Channels n` 计数只含活跃。跨工作区扁平视图让迁移链(同一 first_prompt 反复续接)产生的同名会话同屏,故配套:
  - **同名去重后缀**:重名行(含归档)追加浅色的 `·<cwd 末级目录>`,末级名也撞车再追 `·<短 id>`;hover 展示完整标题/后缀/`(工作区已移除)`/完整 cwd/完整会话 id;
  - **孤儿标注**:`row.cwd` 不在 `workspaces.items` 注册路径集合中的行(工作区已删但会话尚存)标 `(工作区已移除)`;items 形状异常时空集跳过,宁可不标不误标。
- **运行中指示**:`row.running / isRunning / status==='running'` 任一命中 → 行左侧绿色脉冲圆点;非运行行保留同宽占位保证标题对齐。
- **"absent"如实化(修复②)**:读源确认宿主基线语义——attached 会话走 watermark cache 必出值;**冷会话只读持久化投影行、永不折日志**(`listProjectionsFor` 设计使然,非 bug)。分组显示为「未观测(冷/未声明)」,打开过一次后随快照落盘自动归队。
- **悬停气泡统一(v2.4+)**:会话行/rail 图标的悬停提示复用宿主 `@deepseek-ai/dsh-client-ui-primitives.Tooltip`——与「新建对话」「搜索」按钮**同组件同底色**(`--dsw-alias-tooltip-bg` 深色气泡、500ms 延迟、pre-line 多行;行 hover 为 标题/cwd/id 三行);原语缺席自动回退原生 `title`。
- **不做不可达的兜底**:锚定失败(结构/文案变化)或 `react-dom` 缺失时**不注入任何视图**——早期版本的 footer ▦ 浮层入口在 v2.4 tab 化重构后实际已不可达,遂连同 overlay 代码一并移除,不保留形同虚设的降级;footer 插槽仅剩「sessions hook 缺席」诊断标记。
- **v2.4 折叠(rail)态收束**:宿主侧边栏折叠时——
  - tab 对与 Channels 列表整体退出(不再把渠道子目录留在折叠栏里);
  - 往折叠态仍存在的 `sectionHeader` 首位注入**单个「两 tab 堆叠」图标**(36px 圆底、`currentColor` 描边,与 rail 官方图标同规格):上下两枚圆角矩形代表「工作区 / Channels」两 tab 收拢;点击 = 展开宿主侧栏(转发到宿主 toggle 按钮)并恢复原 tab 选择;
  - **悬停统一**:rail 三枚图标(本图标、宿主「新建对话」「搜索」)hover 观感一致——36px 圆形 + `--dsw-alias-interactive-bg-hover` 淡色底(「新建对话」原生为 12px 圆角方,经 `.dshcv-ind` 统一为圆形),并在底部中央淡入一枚 4px `currentColor` 悬停指示点(`::after`,hover/聚焦出现;「新建对话」「搜索」由看门狗幂等补挂 class,卸载摘除;两 tab 当前态不再常显圆点,改由图标亮度与展开后的 tab 呈现);
  - 看门狗补判 `labelEl.isConnected`:官方标题在折叠/展开间整体重挂载,旧锚点子树可能孤儿存活——强制重装,修「折叠再展开出现双 tab」;
  - tab 按钮加 `whiteSpace:nowrap`,修窄栏下「工作区」逐字竖排。
- **v2.5 权威状态路由 + 「使用中」角标 + live 丢锁存规避**:
  - 宿主入口新增只读 HTTP 路由 `/dsh-channel-view/state`(官方 `ctx.webServer.register` seam,exact 路径,`cache-control: no-store`),返回 `{channels:{sessionId→qqChannel}, bound:[当前绑定会话id], generatedAt}`:channel 值取 `sessionProjectionCache.cachedSnapshot`(冷/持久)∪ 自建 `onChanged` live overlay(本进程新锁存),qqbot 绑定集取 `~/.dsh-qqbot/model-prefs.json` 的 `sessionIds` 覆盖值 ∪ `session-peers.json` 键;
  - **规避的 core 缺陷**:QQ 会话在 web 端被直接对话转为 live 后,客户端 dsh-client-runtime 的行 `projectionValues` 会被 core 以只含内置键的 `projectionValuesOf(log)` 重算——插件单元(`qqChannel`)锁存值不在其中、且 latches 时的推送帧早于浏览器连接、不会再补发 → 该会话从 Channels 的 QQ 组掉进「未观测」(宿主 `session.list` 出口实测无误,纯客户端 live 行装配漏并插件单元)。分组判定改为**权威路由值优先、行投影值兜底**,绕开该路径;路由缺席(旧宿主/重启前)自动退回纯行值,行为不劣于 v2.4;
  - **使用中角标**:`bound` 命中的行右侧渲染绿色「使用中」小徽标(悬停提示「当前 QQ 绑定的会话」),区分「正服务」与「曾服务」——归档组不显示;
  - 客户端每 4s 轮询该路由(仅 Channels 视图挂载期间)。轮询是 spike 级取巧,正式版应由宿主在绑定变更时推帧。
  - ⚠️ 该路由暴露会话 id 与绑定关系,与 `/api` 同等假定本机可信;宿主绑 `0.0.0.0` 时同局域网可读(spike 阶段如实声明,正式版需鉴权)。

数据面主体走官方链路(useSessions 行 + 投影推送帧;v2.5 起 qqChannel 判定加一条宿主权威路由,见上)。**v2.3 起支持真实渠道**:`qqChannel` 投影单元(由 [dsh-qqbot PR #39](https://github.com/tencent-connect/dsh-qqbot/pull/39) 注册——入站消息在 `source.channel` 携带 `qq/c2c`/`qq/group` 声明,单元折叠日志锁存,重放安全)命中最高优先级;`channelSpike`(本库自带的 latch 演示值)退居兜底档,标签如实标注「演示渠道」;`origin` 字段值域只有 `subagent`,不能充当渠道。

## 结构

```
lib/index.js      宿主入口:channelSpike 投影单元 + onChanged live overlay + /dsh-channel-view/state 权威路由(v2.5)
lib/client.js     客户端 bundle(手写,无构建):tab 注入 + 渠道分组视图 + rail 折叠图标
                  分组优先级:qqChannel(真实声明,dsh-qqbot 注册)> channelSpike > subagent > 未观测
cordis.patch.yml  bundle 层 patch:插入 channel-view-spike 行
```

## 安装(profile 为 web 时)

**registry 安装(npm)**:

1. `~/.dsh/profiles/web/package.json`:
   - `dependencies` 加 `"dsh-channel-view": "^0.0.1-spike.6"`;
   - `dsh.profile.bundles` 数组在 `@deepseek-ai/dsh-web-app` 之后加 `"dsh-channel-view"`;
2. `npm install --prefix ~/.dsh/profiles/web`(或 `dsh plugin --profile web add dsh-channel-view`);
3. 重启 `dsh web`。

**开发模式(file: 克隆)**:`dependencies` 用 `"dsh-channel-view": "file:<本地克隆路径>"`,其余同上;纯客户端改动浏览器刷新即生效(宿主入口改动才需重启)。

## 诊断

- **tab 不出现** → react-dom 播种缺失或锚点文案/结构变化(无注入即无视图,不再有浮层可退;排查 DOM 锚点:文本恰为「工作区/Workspaces」的元素);
- **tab 出现但 Channels 里全是"未观测"** → 投影单元未注册/未推送(查宿主启动日志 `[dsh-channel-view]` 行);
- **没有「使用中」角标 / web 对话的 QQ 会话仍掉出分组** → 权威路由未生效(宿主入口改动**不热载**,需重启 dsh web 后再刷新浏览器;`curl http://127.0.0.1:<port>/dsh-channel-view/state` 应返回 JSON);
- 看门狗每 2s 检查注入节点存活性(React 重渲染冲掉时自动重装)。

## 限制(当前阶段声明)

- 真实渠道目前覆盖 QQ(`qq/c2c`/`qq/group`,需 dsh-qqbot 带 PR #39 或同构补丁);其余宿主渠道待声明方插件注册各自的投影单元(值域约定 `<source>/<variant>`);
- tab 注入锚定依赖「工作区/Workspaces」文案与 `regionArea`/`sectionHeader` 类名前缀(css-modules 键名),rail 图标同样锚在 `sectionHeader` 首位并点击转发到 `button[class*="_toggle"]`(有 aria-label「展开侧边栏/Expand sidebar」兜底),宿主 UI 大改时失效——正式版若上游接受 RFC,应改为官方 tab 槽;
- `0.0.x-spike` 版本线:接口与形态可能随 RFC 进展变动,生产采用请锁版本。

## 与 dsh-channel-spec 的关系

[RGarvel/dsh-channel-spec](https://github.com/RGarvel/dsh-channel-spec)(RFC-0001,源自 [deepseek-harness#3897](https://github.com/deepseek-ai/deepseek-harness/discussions/3897))是本功能的**规范载体**,两库分工:

| 库 | 角色 | 回答的问题 |
|---|---|---|
| `dsh-channel-spec` | RFC 规范(纯文档) | 渠道**应该**长什么样:宿主原生 `session.header.channel` 字段 + 官方 GUI 渠道视图 |
| `dsh-channel-view`(本库) | 参考实现(spike,纯第三方插件形态) | **不改宿主**能做到什么程度:插槽 + 会话投影 + portal 注入的链路实证 |

演进契约:RFC 被上游采纳/实现后,本库的 DOM 注入部分应迁往官方槽位、库降级为参考实现存档(spec 的 Related 与本节保持互链);RFC 未决期间本库继续以插件形态演进(~~下一步:dsh-qqbot peer-map → 真渠道投影~~ **已落地:v2.3 消费 qqChannel,声明侧见 dsh-qqbot PR #39**)。

## License

MIT

Install

dsh plugin --profile web add github:RGarvel/dsh-channel-view

Profile: web

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source