supergameboy
dsh-new-session-cards
DSH (DeepSeek Harness) plugin: four-step card wizard for new-session configuration — prevents silently using default task mode/model
- Stars
- 0
- Language
- TypeScript
- Created
- Aug 16, 2026
- Updated
- Aug 16, 2026
Introduction
dsh-new-session-cards
中文 | English
把 DSH(DeepSeek Harness)Web GUI 的新会话屏,改造成四步卡片向导的独立插件——防止"用默认配置却忘了配置任务模式、模型"。
对 DSH 本体零修改:独立 bundle 分发,dsh plugin add 一条命令装上,remove 一条命令卸掉。
为什么 DSH 很好玩
DSH 整个产品都是 Cordis 插件树——没有需要"手术"的核心,所有东西都可以从外面替换:
- composer 接管链:新会话屏的输入区是一个 selector 路由链,任何插件都可以"接管"它,把自己变成全屏界面(本插件就是这么干的),用完让位,原输入框自动回归
- 槽系统(Slots):侧边栏、设置页、会话头部、消息行……全有可注入的槽位,插件像乐高一样挂上去
- 主题 token:
--dsw-alias-*语义色板,插件写样式零字面色值,明暗主题自动适配 - bundle 分发:
dsh.bundle+dsh.client两个 manifest,本地目录 / tarball / GitHub 直装都行 - 插件即代码:client 插件是普通 JS/TSX,打包成浏览器 bundle,与内置插件同一套加载契约
你不需要 fork DSH 就能深度改造它的界面。本插件就是一条完整的例子:接管 → 配置 → 让位。
这个插件做什么
新会话屏原本的配置入口(工作区芯片、agent preset 芯片、输入卡角落的小模型选择器)都默认落在部署默认值上,用户极易跳过配置直接发送。本插件把新会话屏替换为:
- Step1 任务模式:agent preset 卡片网格单选,默认预置带「默认」徽标
- Step2 模型:模型卡片网格单选(provider 标签区分),默认模型带「默认」徽标
- Step3 其余配置:工作区(全宽)+ 访问模式 / 计划模式(并排)
- Step4 输入内容:点击「确认配置并开始」后进入——向导内的消息输入区(输入框 + 发送按钮 + "/" 命令菜单,与标准输入框同一套输入机器与命令管道),发送首条消息后向导自然让位,进入对话
确认本身不发送任何消息。任何配置都不会在用户不知情时使用默认值。
也可以随时回到原版 hero:向导底部「使用标准输入框」,或 设置 → 通用设置 → 新会话配置向导 开关(偏好存于浏览器 localStorage)。
安装
# 前置:DSH CLI 已安装并初始化过 profile(如 web)
dsh plugin --profile web add github:supergameboy/dsh-new-session-cards
GitHub 安装拉取的是源码,pnpm ≥10 需要显式允许构建脚本(只允许你信任的源码):
# <profile 目录>/pnpm-workspace.yaml
allowBuilds:
dsh-new-session-cards: true
然后重新执行 add(prepare 脚本自动构建 lib/)。本地目录或 tarball 安装不需要允许构建:
dsh plugin --profile web add ./dsh-new-session-cards # 本地目录(需先 pnpm build)
dsh plugin --profile web add ./dsh-new-session-cards-0.1.0.tgz
验证与启用:
dsh --profile web --dump-config # 应看到 "# == dsh-new-session-cards" 层
dsh --profile web # 打开新会话屏,即见向导
如果插件已装过且改了代码:profile 是 link 安装,浏览器刷新即生效,无需重启服务(服务端每次请求都读最新 bundle 文件)。
卸载:
dsh plugin --profile web remove dsh-new-session-cards
从源码构建
pnpm install
pnpm build # tsdown:lib/index.js(node half)+ lib/client.js(浏览器 bundle)
pnpm typecheck # 需要 DSH 源码树在 ../deepseek-harness(见踩坑 #1)
踩坑记录(写给 DSH 插件作者)
以下是开发本插件时真实踩过的坑,按"坑 → 现象 → 解法"记录。想快速上手 DSH 客户端插件,先读这份。
1. npm 上 @deepseek-ai/* 依赖链不完整
- 坑:npm registry 上的
@deepseek-ai/*包(rc 版)的传递依赖有缺失(如dsh-user-interaction未发布),pnpm install直接 404。 - 解法:
package.json不声明任何@deepseek-ai依赖。运行时不需要——client bundle 的 externals(react、dsh-client-runtime/client等平台模块)由 shell 的模块表提供,其余依赖全部内联;构建也不需要——import type会被擦除。类型检查用 tsconfigpaths指向 DSH 源码树的构建产物(../deepseek-harness/packages/*/*/lib/types)。
2. --dsw-alias-brand-primary 是墨色,不是品牌蓝
- 坑:主题里
brand-primary解析为neutral-bluish-1000(接近黑的墨色)。用它做按钮底 +label-primary(同墨色)做文字 → 深字深底,按钮文字完全看不见。 - 解法:强调/选中用
--dsw-alias-state-business-primary(DeepSeek 品牌蓝,input-bar内部注释原话:"Business blue, not brand-primary: that token resolves to ink");主按钮用--dsw-alias-button-primary-fill底 +--dsw-alias-label-primary-foreground(白)字——优先直接复用ui-primitives的Button组件。
3. 数据加载不会自动触发
- 坑:注入的
load回调只在组件里定义了,没有任何人调用 → 卡片永远渲染骨架屏(无文字)、下一步按钮永远 disabled(依赖status === 'ready')→ 用户卡死在向导里。 - 解法:组件挂载时
useEffect(() => { load() }, [load])(照抄 shippedModelSelect的模式)。
4. composer 接管时,fallback 输入框"隐藏而不卸载"
- 坑:
conversation.composer.bar被接管时 DOM 保留、CSS 隐藏(textarea 一直"在"DOM 里)。依赖它的东西会跟着隐藏:conversation.input.overlay槽(命令菜单 MenuView、popupSelect shell)的锚点在隐藏的 InputBar 内,非 portal。 - 解法:接管界面的 "/" 命令菜单要自绘——但管道可以复用:
sessions.scope(sessionId)拿到 session 作用域,inputTriggers.sessionOf(actx)拿到会话控制器(menu store / track / arbitrate / pick),菜单 UI 读 menu store 自己渲染。
5. chain 重路由需要"变化"来触发
- 坑:chain 的 selector 是纯函数(只收 owner props),确认后状态变了但 owner props 没变 → 不重路由 → 向导退不出去。
- 解法:让 ConversationRoot 重渲染。两个可行触发:(a)
conversation.blocks.set(sessionId, undefined)(root 订阅 block 变化);(b) 发送首条消息后composerPhase从 blank 变 active(本插件最终采用:Step4 发送即自然让位,无需手动翻转)。
6. locale 命名空间要声明合并
- 坑:
ctx.locale.register(NS, ...)的类型签名是N extends keyof LocaleNamespaceMap,自定义 NS 直接编译报错。 - 解法:在插件里
declare module '@deepseek-ai/dsh-client-ui-slots' { interface LocaleNamespaceMap { myNs: MyKeyUnion } }。组件里PropsLocale<typeof NS>(NS 是值,要用typeof)。
7. immer 回调里 RpcResult 判别联合窄化失效
- 坑:
response.result.ok收窄在store.update((draft) => { ... response.result.error.message })回调里失效(TS 不把窄化传播进 mutator 回调)→ "Property 'error' does not exist"。 - 解法:先提取局部变量:
const message = response.result.error.message,回调里用message。
8. client-only 插件无法注册 host settings 命名空间
- 坑:设置持久化走
settingsScope.bind(),但 namespace 是 host 侧注册的(需要 host 插件代码);client-only 插件的 namespace 永远是unavailable。 - 解法:偏好存浏览器 localStorage(
dsh-new-session-cards:useWizard),用createSnapshotStore包一层做响应式镜像;设置行(settings.general.item)读写它——UI 与内置设置行完全一致。
9. 构建配置的细碎坑(tsdown / tsc)
- CSS Modules 虚拟 id 必须基于 importer 解析绝对路径(
resolve(dirname(importer), source)),否则ENOENT。 - node half 要
fixedExtension: false,否则输出index.mjs而 package.json exports 指向.js。 - tsconfig
paths通配每 pattern 只能有一个*(packages/*/*/lib/types不行,要按组展开)。 - 类型检查走 DSH 源码树需要:
allowImportingTsExtensions、lib: ["ES2023"](源码用findLast)、types: ["node"]、CSS Modules 声明文件(src/css-modules.d.ts)。 - 安装
@deepseek-ai/cordis(npm 有 4.x)不是必需的:cordis 类型同样由 paths 从vendor/cordis解析。
10. 分发链路的坑
- GitHub 安装 = 源码安装:必须
prepare脚本(pnpm ≥10 跑在 allowBuilds 之后)自包含构建,不能假设有 monorepo 上下文。 dsh.clientmanifest 的exports["./client"]必须指向真实 bundle 文件(clientModules启动扫描会校验,缺失直接 FAILED)。- profile 的 bundle 顺序 = 配置层顺序:装多个插件时,后装的层覆盖先装的同名行。
工作原理(30 秒版)
flowchart LR
A[新会话屏 hero] -->|conversation.composer 接管链| B[四步向导]
B -->|确认配置| C[Step4 输入内容]
C -->|发送首条消息| D[对话界面]
D -->|新建另一会话| A
- 接管:注册
conversation.composer链 entry,selector 只在"空白会话 + 偏好开启 + 未确认"时命中;发送后 blank → active,链自动让位 - 数据:预置走
agentPresets.list/selectRPC;模型走modelDirectories服务;工作区/会话走标准 store;命令菜单复用inputTriggers管道 - 样式:全部
--dsw-alias-*token,CSS Modules,WCAG 2.1 AA,prefers-reduced-motion降级,zh/en 双语文案 - 偏好:localStorage(
useWizard开关),设置页 General 区一行切换
设计文档
docs/design/ 下有完整的分形 UI 设计文档(L0 决策链 → 页面 → 组件库 → 样式规范,两轮独立验证通过)。
License
MIT