Bundle
@dsh-focus/focus-plugin
DSH web focus mode plugin: ?focus=conversation.composer shows only the input area, ?focus=conversation.session shows only the message flow, both hiding the DSH main sidebar; auto-resumes the most recent non-blank session when none is current.
- Source
- TheChengXi
- stars
- 1 stars
- License
- MIT
- Updated
- Updated 14 days ago
Readme
# @dsh-focus/focus-plugin
DSH (DeepSeek Harness) web 聚焦模式插件:`?focus=<插槽路径>` 让 DSH 页面只显示指定的插槽——消息流、输入区、会话列表、详情面板或任意注册插槽——其余界面隐藏,主视野留给代码。
> **本仓库也是 intent-flow 工作流的完整展示**:`.intentflow/` 目录记录了每个 feature 从需求 → 设计 → 执行 → 关账的完整 AI 决策轨迹(`requirement.md` / `design.md` / `later-on.md` / `report.md`),`AGENTS.md` 是工作流全局规范,`_packages/` 是模块现状基线。评审工作流请直接看这些文件。
## 功能
| URL(`?focus=`) | 效果 |
|---|---|
| `conversation.session` | 只显示**消息流**(隐藏输入区、会话头部、侧栏、详情) |
| `conversation.composer` | 只显示**输入区**(沉底于视口底部,隐藏消息流等) |
| `conversation.chat.node` | 消息区域全宽(与 `conversation.session` 视觉等价) |
| `conversation` | 对话区整体(含输入区)全宽 |
| `sidebar` / `details` | 会话列表 / 详情面板全宽 |
| `<任意 data-slot 路径>` | 聚焦三槽内任意已注册插槽(含第三方插件注册的插槽) |
| 无参数 / 未知值 | 完全正常,插件 no-op |
> 聚焦模式下若 DSH 没有当前会话,插件**自动恢复最近的非空白会话**(否则消息流视图是空白 hero)。
> 插件加载即把**已注册聚焦规则**的 path(内置 6 条 + 第三方 `registerRule`)作为插槽清单,经 `postMessage` 发给父窗口(dsh-shell),供壳子下拉动态显示(附中文 `label`);壳子接收端做 source/origin/schema 三重校验。清单只含「有规则即能聚焦」的路径,不会混入 `conversation.session.header` 等无法聚焦的内部槽。
## 原理
DSH 的 Web UI 是插槽树(`sidebar | conversation | details` 三栏),渲染器为每个插槽输出 `data-slot="<完整路径>"` 的 wrapper。本插件**不改 DSH 任何代码**,纯 CSS 可见性控制:
1. 读 `location.search` 的 `?focus=`,给 `<html>` 打上 `data-focus="<路径>"`;
2. **顶层规则**(静态注入):按路径首段把三栏 grid 压成单列轨道——隐藏非目标列容器后,目标列成为唯一 grid item,单轨道无自动放置歧义(规避旧版"详情列被推入 center 列"的 bug);
3. **嵌套兄弟规则**(注册式硬编码):目标插槽的祖先链兄弟(如聚焦 `conversation.session` 时隐藏 composer 座位与会话头部)生成隐藏 CSS;所有规则预先注册,零时序问题;
4. **路径专属布局**:`conversation.composer` 聚焦附加沉底规则(输入区固定在视口底部,active 相位生效)。
### v2 架构设计
**核心改进**:
- **注册式硬编码规则**:零时序问题,官方/第三方可扩展
- **持续监听保障**:处理动态变化(切换会话、插槽重挂载)
- **快速失败策略**:未注册路径直接报错,不静默降级
- **插件化扩展**:第三方可通过 `registerRule` API 注册自定义规则
**架构对比**:
| 维度 | 旧架构(动态枚举) | 新架构(注册式) |
|------|------------------|----------------|
| 规则来源 | 运行时枚举 DOM | 预先注册(硬编码) |
| 时序依赖 | ✗ 依赖目标渲染 | ✓ 零时序 |
| 监听策略 | ✗ 首次后 disconnect | ✓ 持续监听 |
| 失败处理 | ✗ 静默兜底 | ✓ 快速失败 |
| 扩展性 | ✗ 需改核心代码 | ✓ API 注册 |
| 稳定性 | 50%-95% | 100% |
## 安装 / 部署
DSH 从 `~/.dsh/profiles/node_modules` 解析插件包。两步:① 把本仓库链接(junction)到该位置;② 把 `focus-mode` 挂进**当前 profile 的持久 patch 层**,裸 `dsh web` 启动即自动挂载。
```powershell
# 1. 部署(Windows junction;修改本仓库代码即生效,无需重建)
mklink /J "$env:USERPROFILE\.dsh\profiles\node_modules\@dsh-focus\focus-plugin" "<本仓库绝对路径>"
# 2. 挂载(推荐:写进 profile 持久 patch,裸 dsh web 启动即生效)
# 追加到 ~/.dsh/profiles/<profile>/cordis.patch.yml 末尾(<profile> 见启动方式,默认 web):
# - insert:
# - id: focus-mode
# name: '@dsh-focus/focus-plugin'
```
> ⚠️ **聚焦「有时生效、有时完整页」的头号根因**:只在 `.dsh/dsh.mcp.patch.yml` 里 insert、靠 `dsh web --patch …` 挂载时,一旦某次忘带 `--patch` 裸启动,focus-plugin 就不进 boot graph → 无聚焦(完整三栏)+ 壳子插槽清单回落预设项。**写进 profile 持久 patch 即根治**(临时 `--patch` 仅用于一次性联调)。
> 挂载行的 `name` 必须**精确等于**包名(`@dsh-focus/focus-plugin`),写错会导致 DSH 启动失败(端口打不开)。
- **首次挂载**:需重启 dsh(patch 进 boot graph);
- **修改插件代码后**:无需重启——DSH 的 client-modules 按内容 hash 伺服插件,浏览器刷新即加载新代码。
## 开发
```powershell
npm install # tsdown + typescript + cordis 类型
npm run build # 构建 lib/client.js
npm run typecheck # 类型检查
```
### 源码结构
- `src/client/index.ts` - 插件入口,注册内置规则,安装健康检查
- `src/client/rules.ts` - 规则注册系统,支持优先级覆盖和第三方扩展
- `src/client/builtin-rules.ts` - 内置规则定义(sidebar/conversation/details)
- `src/client/core.ts` - 核心逻辑:路径解析、CSS 生成、持续监听、健康检查
- `src/client/slot-discovery.ts` - 插槽清单发送:读规则注册表 path + 规则表变更订阅重发 + postMessage 发送壳子
### 构建产物
- `lib/client.js` - DSH 经 `/plugins/<id>/client.js` 伺服
- `lib/index.js` - host 半(空 cordis 插件壳)
### 调试
```bash
# 启用调试模式(URL 参数)
?focus=conversation.session&focus-debug=1
# 健康检查(浏览器控制台)
window.__dshFocusHealth()
# 诊断脚本(浏览器控制台)
# 粘贴 diagnostic.js 内容并运行
```
- 基线对齐 `@deepseek-ai/dsh@0.1.1-rc.2`(聚焦 CSS 选择器与该版本已实测验证有效)
## 🎓 第三方扩展示例
### 注册自定义规则
```typescript
// 在第三方插件中
import { registerRule } from '@dsh-focus/focus-plugin'
registerRule({
path: 'my-plugin.my-slot',
topSlot: 'conversation',
hiddenSelectors: [
'[data-slot="conversation.session"]',
'[data-composer-seat]',
],
layoutCss: `
html[data-focus="my-plugin.my-slot"] .my-element {
position: fixed;
bottom: 0;
}
`,
description: '我的插件聚焦规则',
source: 'my-plugin',
priority: 10, // 高优先级
})
```
### 查询规则
```typescript
import { listRules, getRuleStats } from '@dsh-focus/focus-plugin'
// 列出所有规则
const rules = listRules()
console.log('Available paths:', rules.map(r => r.path))
// 统计信息
const stats = getRuleStats()
console.log('Total rules:', stats.total)
console.log('By source:', stats.bySource)
```
### 规则优先级
- 优先级数字越大越优先(默认 0)
- 高优先级规则覆盖低优先级规则
- 同优先级规则覆盖并警告
### 内置规则清单
| 路径 | 顶层插槽 | 说明 |
|------|---------|------|
| `sidebar` | sidebar | 会话列表全宽显示 |
| `conversation` | conversation | 对话区整体全宽(含输入区和消息流) |
| `conversation.session` | conversation | 只显示消息流(隐藏输入区和会话头部) |
| `conversation.composer` | conversation | 只显示输入区,沉底于视口底部(active 相位) |
| `conversation.chat.node` | conversation | 消息节点区域全宽(与 session 视觉等价) |
| `details` | details | 详情面板全宽显示 |
## License
MIT
Install
dsh plugin --profile web add github:TheChengXi/dsh-focus
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-focus-focus-plugin 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.