Bundle
@dsh-external/dsh-unread-mark
会话行「标记未读 / 取消标记」:小红点主动未读标记(用户钉住待处理),与系统绿点区分;绿点存在时两项禁用,红点仅手动取消才消失,服务端持久化
- Source
- yangyizhu8
- License
- BSD-3-Clause
- Updated
- Updated 4 days ago
Readme
# dsh-unread-mark — 会话行「标记未读 / 取消标记」 在 dsh Web 会话行的 "..." 菜单追加两项,用**用户主动的红点**钉住「还没处理完」的会话, 与系统绿点(跑完未点开)区分。需求对齐定稿:2026-09-12。 ## 功能 | 会话状态 | 圆点 | 标记未读 | 取消标记 | |---|---|---|---| | 绿点(系统「跑完未点开」) | 绿点 | 置灰 | 置灰 | | 无标记 | — | 可点 | 置灰 | | 红点(用户主动标记) | 红点 | 置灰 | 可点 | - **红点语义**:会话标题前 8px 红色圆点(`#D64545`),与系统绿点 slot 同位异色。 - **红点不随刷新/点开消失**——这是它与绿点的本质区别:只有手动「取消标记」才消失。 - 绿点存在时两项都禁用(用户明确要求);置灰为菜单项半透明 + `aria-disabled` + 原生 `disabled`。 ## 原理(合同依据,dsh 0.1.1-rc.2 实证) - **绿点判定**:`SessionSummary.completed === true` 且非运行中、无待交互 (`ui-workspace/tree.ts:30` 注释原文:"Finished running while not selected and not yet opened (the green 'done' reminder dot)")。从 `ctx.sessions.list.getSnapshot().byId[id]` 读取,无需 DOM 探测。 - **菜单注入**:轮询 `[role=menu]` + 语义锚(归档会话/分叉会话)→ 克隆末项 wrapper 追加两项 (沿用 `dsh-session-handoff` 的 MenuInjector 模式;锚点不匹配即 fail-soft 缺席)。 - **会话身份**:React fiber 精确路径(`fiberSessionIdOf`)→ aria 标题匹配兜底。 - **持久化**:host 半 loopback RPC(channel `/dsh-unread-mark`)+ `~/.dsh/dsh-unread-mark-state.json` (原子写:临时文件 + rename)。端点 `list` / `set` / `clear`。 - **红点渲染**:每 1.2s 巡检会话行(`div[role=treeitem]`)→ 按 host 集合增删红点(幂等)。 拉取失败时本轮不动红点,避免「读失败 = 清空红点」的误删。 ## 安装 / 卸载 ```powershell # 安装(开发期热注入;正式转正走 bundles 需停机窗口,同 dsh-session-handoff 工艺) dsh plugin --profile web add "D:\deepseek-harness\plugins\dsh-unread-mark" # 卸载 dsh plugin --profile web remove @dsh-external/dsh-unread-mark # 状态文件(如需清空标记):删除 ~/.dsh/dsh-unread-mark-state.json ``` 准入自检:`node scripts/check-plugin-ready.mjs plugins/dsh-unread-mark` 全 PASS。 ## 验收证据(2026-09-12,无头 CDP 实测) - 红点:`dotCount=1`,位于被标记会话行,`background=rgb(214,69,69)`、`width=8px`。 - 菜单注入:六项 = 重命名 / 分叉会话 / 归档会话 / **标记未读** / **取消标记** / 移交会话。 - 状态机(未标记会话):标记未读可点、取消标记置灰 → 点「标记未读」→ host 集合新增该 id、 页面红点 1→2 → 再开菜单:标记未读置灰、取消标记可点 → 点「取消标记」→ 集合移除、红点 2→1。 - 状态机(已标记会话):标记未读置灰、取消标记可点。 - 持久化:`~/.dsh/dsh-unread-mark-state.json` 内容随 set/clear 实时更新;`list` 端点回读一致。 - 回归:页面 Console 零报错(client 轮询 RPC 全程无 transport 失败)。 ## 已知边界 - **绿点分支未在本机实测**(验收时无 `completed=true` 的会话):逻辑为 `isGreen()` 读 `SessionSummary.completed`(剔除运行中/待交互),与官方 `tree.ts` 的绿点条件一致; 遇到绿点会话时应表现「两项都置灰」。 - 注入锚点依赖宿主菜单语义(`归档会话`/`分叉会话`)与行结构(`div[role=treeitem]` + fiber `node.id`);宿主升级改结构时 fail-soft 降级为「菜单项缺席 / 红点不渲染」,不抛错、不拖死宿主。 - 红点是浏览器可见态,不写入会话日志;多浏览器/多设备共享同一 host 状态文件(同 `~/.dsh`)。
Install
dsh plugin --profile web add github:yangyizhu8/dsh-unread-mark
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-external-dsh-unread-mark from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.