# dsh-plugin-assist **Repository Path**: bbsmrdj/dsh-plugin-assist ## Basic Information - **Project Name**: dsh-plugin-assist - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-24 - **Last Updated**: 2026-10-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # dsh-plugin-companion DeepSeek Harness(DSH)Web 界面里的桌面陪伴者:页面右下角常驻一个可以拖动的 **Live2D 虚拟形象**,会说话、会听你说话,也能感觉到你正在猛敲键盘——像身边坐着一个安静的人。 它不是另一个编程助手。它不会读写你的文件、不会调用工具、不会进 agent loop。它只做两件事:**在旁边待着**,以及**在合适的时候说一句话**。 形象挂在窗口最上层的一个透明浮层里(不占布局、不挡点击),默认停在右下角,气泡浮在它正上方、两个按钮浮在它下方: ``` ┌────────────────────────────────────────────────────┐ │ DSH 主界面(侧栏 / 会话 / 输入框) │ │ │ │ ╭──────────────────────╮ │ │ │ 看你敲了好久,是在 │ │ │ │ 是在调取景吗? │ │ │ ╰──────────────────────╯ │ │ ╭────────────╮ │ │ │ Live2D │ │ │ │ 鲸鱼娘 │ │ │ ╰────────────╯ │ │ [设置] [对话] │ └────────────────────────────────────────────────────┘ ``` 鼠标移到形象上才会浮出这两个按钮;按住形象可以直接拖走,松手就记住新位置。 默认形象是 **DeepSeek 鲸鱼娘**(Live2D Cubism 4):44 个表情、8 个动作、物理摆动、眨眼、视线跟随鼠标,说话时自动对口型。找不到模型时自动降级成内置矢量形象,永远有东西可看。 --- ## 它是什么 | 能力 | 说明 | |---|---| | 虚拟形象 | **Live2D 模型**(44 表情 / 8 动作 / 物理 / 眨眼 / 视线跟随),也可退回内置矢量形象或自己的图片;大小、取景、透明度可调,位置可拖拽并记住 | | 说话 | 气泡逐字显示,同时驱动口型;可选浏览器语音朗读(语速、音高可调) | | 输入 | 文字输入;可选语音输入(Chrome/Edge 的 Web Speech) | | 独立模型 | 走你自己配置的 OpenAI 兼容接口,与 DSH 当前会话的模型完全无关 | | 上下文 | 可选接入当前 DSH 会话最近几轮,给它「你在干什么」的背景 | | 感知 | 键盘节奏、鼠标动静、长时间静默 → 触发主动搭话(有冷却) | ## 它不是什么 - **不修改你的代码或会话**:只读(且默认关闭)当前会话最近几轮文本,没有任何写操作。 - **不接管 DSH 的模型**:`/dsh-companion/chat` 是一条约 100 行的独立转发,配自己的 baseURL / Key / model。 - **不偷按键**:传感器只统计「5 秒内按了多少次键」「鼠标移动了多少像素」,不记录任何键值或内容。 - **不会自己说话**:主动搭话受阈值 + 连续窗口 + 冷却三重限制,且输入框/设置面板打开时完全不触发。 --- ## 安装 插件装进 `web` profile,和你的其它本地插件走同一条路。 ### 1. 链接到 profile ```powershell New-Item -ItemType Junction ` -Path "$env:USERPROFILE\.dsh\profiles\web\node_modules\dsh-plugin-companion" ` -Target "D:\gitee\dsh-plugin-assist" ``` (`mklink /J` 等价。用链接而不是复制,改完源码刷新页面就生效。) ### 2. 在 profile 的 patch 层加一行 编辑 `~/.dsh/profiles/web/cordis.patch.yml`,追加: ```yaml - insert: - id: companion name: dsh-plugin-companion ``` ### 3. 刷新页面 `patchReload: live` 会让 host 半热加载(`curl http://127.0.0.1:3080/dsh-companion/health` 能返回就说明活了)。浏览器半需要**刷新一次页面**才会拉取新的启动图。 形象出现在右下角,就成功了。 --- ## 配置 点形象下方悬停出现的齿轮打开设置面板。配置存在 `~/.dsh/storages/dsh-plugin-companion.json`(纯文本,可手工编辑,改完刷新页面)。 面板宽 332px,跟着形象走——默认出现在形象的同一侧,超出窗口就自动收进视口内。里面从上到下单列排开七段:**形象 / 位置 / 说话 / 输入 / 模型接口(独立于 DSH)/ 上下文 / 感知**,每段一个标题加若干行「标签 + 控件」;控件就是原生下拉框、滑杆、勾选框,没有多余装饰。右上角 ✕ 收起。改哪一项立刻落盘,没有「保存」按钮。 ### 形象 | 项 | 说明 | |---|---| | 渲染方式 | `Live2D 模型`(默认)/ `内置矢量形象` / `自定义图片` | | 模型 | 扫描 `~/.dsh/storages/dsh-plugin-companion-models/` 下所有含 `*.model3.json` 的目录 | | 取景 | 0.4–1.8×,按**头部**取景:1.0 是脸正好填满,越小露出身体越多 | | 视线跟随 | 眼球跟着鼠标转(关掉就回正) | | 常驻表情 / 思考时表情 | 从模型的 expressions 里选 | | 思考动作 | 从模型的 motion 组里选,默认 `aidale` | | 大小 | 96–420px(Live2D 建议 200 以上,太小看不清表情) | | 停靠 | 四个角,或「自由拖动」(也可以直接拖形象,松手自动切到自由并记住坐标) | 模型缺了就在设置面板点「获取 DeepSeek 鲸鱼娘(约 4MB)」,下载到用户目录,插件本体不含美术素材。 装好之后,形象直接出现在右下角,约 220px、半身取景:深蓝色长发,头顶和肩侧翘着鲸鱼鳍与尾巴状的发饰,白色蕾丝女仆头饰,蓝眼睛,深蓝色衣装,手里端着一杯带吸管的饮品。底部有一道渐变淡出,不会硬切在窗口边缘。 ### 说话 | 项 | 说明 | |---|---| | 文字气泡 | 关掉就只剩形象,不说话 | | 语音朗读 | 浏览器 TTS;中文音色取决于系统装了什么语音包 | | 气泡停留 | 6/12/20/40 秒,或不自动消失(点气泡可立即收起) | ### 模型接口 | 项 | 默认 | 说明 | |---|---|---| | API 地址 | `https://api.deepseek.com` | 任何 OpenAI 兼容端点,`/v1` 可省可留 | | Key 来源 | 单独填写 | 或「复用 DEEPSEEK_API_KEY」直接读 dsh 进程的环境变量 | | 模型 | `deepseek-chat` | | | 温度 | 0.85 | 陪伴者话短,温度高一点更自然 | | 名字 | 云樱 | 显示在气泡上方 | | 额外人设 | 空 | 附加在内置人设之后,例如「说话带点懒散」 | **API Key 不会进浏览器**:`GET /dsh-companion/config` 只返回 `sk-1234…abcd` 形式的掩码;短于 20 字符的 Key 只露末 4 位(`…abcd`),避免 16 字符的 Key 被露掉一半。明文只存在 Node 侧配置文件里,浏览器拿不到、也无法通过接口读回。 ### 上下文 开启后,每次请求会把当前 DSH 会话最近 `maxTurns` 轮的文本压成一段摘录,塞进 system prompt。不开就是纯闲聊。 读取走 `ctx.sessions`,拿不到会话时静默降级为无上下文(不会报错,也不会卡住)。 ### 感知 | 项 | 默认 | 说明 | |---|---|---| | 主动搭话 | 开 | 关掉就只在你开口时说话 | | 键盘节奏 | 开 | 5 秒窗口内按键数 ≥ 阈值算「在猛敲」 | | 鼠标动静 | 开 | 5 秒窗口内位移 ≥ 阈值算「在忙」 | | 按键阈值 | 90 / 5s | | | 静默提醒 | 300s | 这么久没动,允许它关心一句;0 关闭 | | 搭话间隔 | 120s | 两次主动搭话的最小间隔 | 触发逻辑:连续 3 个窗口都超阈值 → 说一句;冷却期内不再触发。输入框打开、设置面板打开、正在思考时一律不触发。 --- ## 快捷键 | 键 | 行为 | |---|---| | `Ctrl+Shift+K` | 唤起/收起输入框(可在设置里改,留空关闭) | | `Enter` | 发送 | | `Esc` | 收起输入框 | | 点形象 | 唤起输入框 | | 拖形象 | 移动位置 | --- ## 隐私边界 - 键盘/鼠标:只累计**次数与像素距离**,不采集键值、不采集坐标轨迹。 - 会话上下文:默认**关闭**;开启后只取最近几轮纯文本,截断到 6000 字符,只发给你自己配置的那个 API,不落盘。 - API Key:明文只在 `~/.dsh/storages/dsh-plugin-companion.json`,不出现在任何 HTTP 响应里。 - 同源校验:`/dsh-companion/*` 拒绝带外部 `Origin` 的请求,防止别的网页借你的浏览器打这个代理。 - 对话历史存在 `sessionStorage`,关标签页即清空。 --- ## Live2D ### 用的是什么 | 环节 | 实现 | |---|---| | 渲染 | PIXI 6.5.10 + pixi-live2d-display 0.4.0(`cubism4`)+ Live2D Cubism Core,三份运行时就近 vendor 在 `client/vendor/`,由 host 同源提供,**完全离线** | | 取景 | 加载后把模型缩小、提取像素扫 alpha,量出「整身包围盒」和「顶部 32% 高度带的左右边界」;按后者把头部对齐到容器,所以小尺寸下也能看清脸 | | 口型 | 说话时每帧在 `beforeModelUpdate` 里写 `ParamMouthOpenY`,两个不同频率的正弦叠加,比单频更像在讲话 | | 视线 | 直接驱动 `ParamEyeBallX/Y`。**不用 `model.focus()`**:它的坐标是 Cubism 模型坐标(cm 量级),传窗口像素进去会得到一个恒定偏移的斜视 | | 表情 | 上游 `model3.json` 只注册了动作,44 个表情没被引用;host 扫描 `expressions/` 后在模型目录生成一份 `*.companion.model3.json` 补上 `Expressions`,原文件不动 | | 降级 | 运行时加载失败或一个模型都没有 → 自动回到内置矢量形象,并在头像下方显示一行提示 | 说话时的脸部特写是这样的:嘴从闭合张到小「o」再收回去,下颌跟着动。口型是独立叠在 `beforeModelUpdate` 里的一层,和模型自带的眨眼、待机动作同时生效、互不覆盖,所以说话时该眨的照眨。 ### 模型与授权 默认模型是 **DeepSeek 鲸鱼娘**(`c_0120`),**不随插件分发**——用设置面板的一键获取,或: ```sh node tools/fetch-model.mjs # 下到 ~/.dsh/storages/dsh-plugin-companion-models/ node tools/fetch-model.mjs --out . # 或指定目录 ``` 下载器做了三件必要的事:镜像轮换(jsdelivr 主节点会把 2MB 的 `.moc3` 截断成 240KB)、按 GitHub API 的精确字节数校验、JSON 再解析一遍。 > **美术素材的授权不是 MIT。** 角色形象三重版权链,转载/改编请全部署名,并按 **CC BY-NC-SA 4.0(署名—非商业—相同方式共享)** 使用: > > - **上善无形** — 角色原作「溟月」 > - **ZipZipPipe** — 加入 DeepSeek 元素的「女仆鲸鱼娘」二创 > - **氵六青** — 本模型(绑定、动作、表情) > > 模型作者的《使用须知》允许商用直播与自印物料,**禁止盗用与出售**。插件代码仍是 MIT。 ### 换成你自己的模型 把任意 Cubism 4 模型的文件夹(里面要有 `*.model3.json`)扔进 `~/.dsh/storages/dsh-plugin-companion-models/`,在设置面板点「重新扫描」即可。 不用改代码:动作组、表情名、模型尺寸都是运行时从 `model3.json` 和像素测量里读出来的。取景滑杆是用来适配不同构图的——Q 版模型和写实比例的模型需要的数值不一样。 --- ## 已知限制 - **语音输入依赖浏览器**:Chrome/Edge 有 `SpeechRecognition`,Firefox 没有;首次使用要授权麦克风。 - **语音朗读的中文音色**取决于操作系统装了哪些 TTS voice(Windows 需在「设置 → 时间和语言 → 语音」里装中文语音包)。 - **上下文是文本摘录,不是完整会话**:只取最近 `maxTurns` 轮,工具调用结果会被跳过,长消息截断到 600 字符。 - **自定义图片存成 data URL**:超过 1.5MB 会被拒绝,避免配置文件膨胀。 - **一键下载约需 1–3 分钟**:4MB 走国内可达的 jsdelivr 镜像,速度看当时节点状态;失败会在面板上说明原因,也可以改用脚本。 - **Live2D 是 WebGL**:机房/远程桌面等没有 GPU 的环境会退到软件渲染,帧率会低一些;页面切到后台时 ticker 自动暂停。 - **`shell.overlay` 是 DSH 0.1.x 的预览期 slot**:0.1.5-rc.2 验证通过,后续版本若调整 slot 契约需要跟着改。 --- ## 验证 ```sh # host 半:17 项路由与安全断言,不需要启动 DSH node tests/smoke.mjs # Live2D 独立沙盒:不经过 DSH,验证运行时 + 模型 + 取景 + 口型 + 表情 node tests/live2d-sandbox.mjs .tmp/a.png --sweep # 四档取景对比 node tests/live2d-sandbox.mjs .tmp/a.png --action=mouth # 闭嘴/张嘴各一张 node tests/live2d-sandbox.mjs .tmp/a.png --action=expr:墨镜 # 真实页面里量盒模型 / 截图(需先起一个带调试端口的浏览器) node tests/live2d-check.mjs "http://127.0.0.1:3080/?token=..." .tmp/shot.png node tests/live2d-check.mjs "..." .tmp/shot.png --action=click node tests/live2d-check.mjs "..." .tmp/shot.png --action=gaze-left # 端到端:起 mock LLM,真的发一条消息,看气泡与口型 node tests/chat-e2e.mjs "http://127.0.0.1:3080/?token=..." .tmp/chat.png ``` `tests/smoke.mjs` 覆盖:配置默认值、深合并与未知键丢弃、Key 掩码、空 Key 不清空、显式清空、缺少 Key 的错误、空消息 400、无会话降级、跨站拒绝、未知路由 404、模型清单结构、vendor 白名单、路径穿越拦截。 `tests/chat-e2e.mjs` 会临时把 `llm.baseUrl` 指向本地 mock,跑完自动还原——它验证的是「说话 → 气泡打字机 → speaking 状态 → 口型张合」这一整条链路,不花你的 API 额度。 这三条约束是踩过坑才加上的,别删: - **目标实例里已经存了真 Key 就直接拒绝跑**(`hasApiKey && apiKeySource !== 'env'`)。因为它会把 `apiKey` 换成 mock 值,而 `GET /config` 只回掩码、拿不回明文——一旦覆盖就是不可还原的凭据丢失。要在真实实例上跑,先用临时 `DSH_HOME` 起一个隔离实例。 - **还原时必须带 `clearApiKey: true`**,不能只发 `apiKey: ''`。host 侧「空串 = 保持原值」是给前端防误清设计的,空串在这里反而会把 mock key 留在配置里。 - `patch` 调用放在 `try` 内,CDP 连不上时也会走 `finally` 还原。 --- ## 结构 ``` host/index.js Node 半:配置持久化 + LLM 代理 + 上下文读取 + 模型/静态资源服务 client/index.js 浏览器半:__ModuleLoader__ 包裹的 CJS bundle,挂到 shell.overlay client/vendor/ PIXI + pixi-live2d-display + Cubism Core(离线运行时) tools/fetch-model.mjs 模型下载器(镜像轮换 + 字节校验) tests/smoke.mjs host 路由与安全断言 tests/live2d-sandbox.mjs Live2D 独立沙盒(取景/口型/表情) tests/live2d-check.mjs 真实页面检查(盒模型/点击/视线) tests/chat-e2e.mjs 对话链路端到端(mock LLM) ``` 无构建步骤——两份入口都是手写产物,`node --check` 即为最小编译门禁。改完 `client/index.js`,DSH 的 client HMR 会 stat 到变化并刷新 bundle rev,页面刷新即生效。 ## 许可 插件代码 **MIT**;`client/vendor/` 里 PIXI 与 pixi-live2d-display 为 MIT,Live2D Cubism Core 按 Live2D Inc. 的可再分发条款;**模型美术素材为 CC BY-NC-SA 4.0,不随本仓库分发**。