# dsh-soul Release Notes `dsh-soul` 为 DeepSeek Harness(DSH)提供「个性化设置」能力:通过 Web 设置页或斜杠命令配置「关于你」(昵称、职业、介绍)、回复风格和语调、特质(标题和列表 / 表情符号 / 表格)、回复长度偏好、输出语言与自定义指令,配置实时编译为 system prompt,**下一次请求即生效**(section 每次装配重算 + 活动会话主动注入,双通道兜底)。 ## 功能 **设置页** - 启用开关、「关于你」(昵称 / 职业 / 介绍)、「特质」(回复风格和语调 / 标题和列表 / 表情符号 / 表格 / 回复长度)、输出语言、自定义指令 - 人设预设分组:7 个内置人设(随插件提供、不可删除)+ 保存当前为预设、一键使用(★ 标记当前匹配项)、删除(二次确认);每行摘要显示风格与「偏离默认值」的维度,便于横向比较 - Agent 工具分组:`set_persona` 确认模式开关 - 输入框光轨:Agent 回复中时输入框边框的流光动效——开关、预设色板 + 取色器 + 十六进制输入、流动速度(慢 / 中 / 快)、光带粗细(细 / 中 / 粗),以及随配置实时联动的效果示例 - 关键字段带 ⓘ 提示图标;保存 / 重置结果以内联提示展示(成功绿字、失败红字,与宿主模型设置页同款形态),失败时页面内另有错误条 - 中英双语文案(跟随界面语言);dirty 检测(无改动禁用保存 + 未保存提示);保存结果区分「已保存 / 无变化」 - 提示词预览折叠区(只读):无未保存编辑时展示「当前生效提示词」;一旦表单有改动,就改为在保存前编译草稿、展示「保存后将生效的提示词」——边改边看(停止输入 400ms 后自动重编译),并在保存 / 重置 / 应用预设后自动刷新;未通过校验的字段会如实列出、且不计入预览,避免「预览悄悄用了旧值」 - **变更记录**:每次配置改动都留痕(时间 · 来源 · 字段级 `原值 → 新值`),可**撤销**、可**恢复被删的预设**、可**清空**。入口在**插件详情页**(宿主的插件管理 → 已安装插件 → dsh-soul),设置页只在「人设预设」展开区给出指引;保留条数在设置页可调(50 / 100 / 200 / 500,默认 200)。详见下方 v0.8.0 段 **Soul 人设总览**(侧栏面板,默认开启) - 侧栏入口「个性化」→「Soul 人设总览」,打开中间列的主面板。三段结构:**当前生效**(全局人设 + 就地切换)·**工作区**(人设覆盖 + 展开后的会话清单 + 就地改绑)·**预设库**(只读清单,指引去设置页增删改) - **会话口径与左侧栏一致**:注册表成员 − 归档 − 子代理会话。任一环节缺服务时如实标注(数字可能偏大),不静默给出错数 - **会话标题**直接读宿主已维护的标题投影(`sessions.list` 快照里的 `projectionValues.title`),**零日志读取**,第一屏就有标题;不走「折叠整条事件流」那条要几秒的路径 - **会话行可点击**跳到该会话(键盘 Enter / Space 亦可);拿不到 `uiWorkspace` 服务时不带可点样式,不画点不动的按钮 - **空会话如实标注并默认隐藏**:从未产生 user/assistant 消息的会话(只有系统事件与插件注入的人设快照)折叠不出标题,界面标为「(空会话 · 未开始对话)」并默认收起,末尾留一行「显示 N 个空会话」的开关 - **切回面板自动重拉**:订阅 `soulController` store 的关键字段指纹(人设库 / 工作区绑定 / 人格字段),别处改了人设或绑定,面板自动跟上;同时监听 `layout.panelInfo` 的 `activePanelId`,切回本面板也重拉一次 - **面板自滚动**:展开多个工作区后内容变长时,沿祖先链找到真正裁剪内容的容器,用它的可见高度作上限,预设库不会被顶出视野 **斜杠命令**(输出语言跟随配置 `language`,中英文案) ```text /soul show 查看当前配置(含确认模式与预设数量) /soul set k=v 修改配置项(如 /soul set style=humorous language=en;特质:tables=more replyLength=concise;光轨字段:trailColor / trailSpeed / trailWidth / trailEnabled) /soul save <名> 保存当前配置为人设预设(内置名不可占用) /soul use <名> 应用人设预设(内置预设同样可用) /soul list 查看人设预设(✔ 标记当前匹配项;内置项带 [内置] 标记;并报告每个预设覆盖的维度数) /soul del <名> 删除人设预设(delete / rm 别名;内置预设不可删) /soul confirm 应用待确认的人设变更(确认模式) /soul reject 拒绝待确认的人设变更 /soul reset 重置默认值(保留人设预设库) /soul enable 启用 /soul disable 禁用 /soul <昵称> 设置昵称(保留原始大小写) ``` **配置与集成** - 持久化:`$DSH_HOME/soul-config.json`(用户预设存于同文件 `personas` 字段;内置人设只在代码 `lib/personas.mjs` 里,不落盘,因此升级时自动更新、也无法被删除或篡改) - 服务:`soulConfig`(`getConfig` / `updateConfig` / `getSystemPrompt` / `resetConfig`) - HTTP API:`/api/soul/config`(GET/POST)、`/api/soul/prompt`(已生效提示词)、`/api/soul/prompt/preview`(POST 草稿,不落盘、不影响生效提示词)、`/api/soul/config/reset`、`/api/soul/status`(GET,诊断两条送达通道与配置状态)、`/api/soul/personas`(GET)、`/api/soul/personas/save|use|delete`(POST) - 输入校验:字段白名单、类型、长度上限与枚举校验,HTTP 保存 / `/soul` 命令 / `set_persona` 工具 / `soulConfig` 服务共用;非法或超限字段整单拒绝 - Agent 工具:`set_persona`(需宿主安装 `@deepseek-ai/dsh-tools`;缺失或不兼容时自动跳过,其余功能不受影响;确认模式下返回 `pending` 提议) - 插件图标:`package.json` 的 `icon` 指向 `assets/icon.svg`(36×36 viewBox,随 npm 包发布) - 设置导航图标:设置面板左侧「个性化」那一项显示与插件图标同一套几何的图标(DSH 的导航图标按 section id 硬编码、插件声明不了,故由客户端做 DOM 替换,见下) ## 已知限制 - 安装 / 升级后需完全重启 DSH 并刷新浏览器,设置栏目才会出现 - 保存配置后在**下一次请求**生效:配置变化同时走 system prompt section(每一步装配时重新求值)与活动会话注入(保存后主动推送快照),不会打断进行中的请求,也不会改写历史消息 - 有效配置变化会在会话里留下一条 `[dsh-soul 个性化配置已更新]` 消息(user 角色)——它是「即时生效」的可见载体;纯外观配置(输入框光轨)只落盘,不产生该消息 - 两条通道**缺一不可**:底座是宿主「每步重新装配 + 不对函数式 section 文本做缓存」,`npm run verify:host` 用宿主的真实实现直接验证它;而本版实测证明**只有底座不够**(会话进行中改配置不生效),因此注入是必需的第二条通道,不要单独删除(见 `DEBUGGING.md` 3.5) - 「改了配置不生效」有三处可查:`/soul show` 末尾的「送达」行、`GET /api/soul/status`、设置页的提示条(`configError` = 配置读不出来;`deliveryWarning` = 配置送不到活动会话) - 配置文件**损坏**时普通保存会被拒绝(以免以默认值为底覆盖你的内容),设置页的**重置**是逃生口:它把损坏文件另存为 `soul-config.json.corrupt` 后再写入默认值 - `npm run verify` 里可能包含**跳过**(无浏览器 / 定位不到 DSH):跳过会显式打印未执行的断言数,但退出码仍为 0;需要「跳过即失败」时用 `npm run verify:strict` - 仅支持 `web` 平台客户端 ## 兼容性 peerDependencies(DSH 在装载插件前校验,**比较对象是 DSH 运行时版本**,即 `@deepseek-ai/dsh-app-boot` 的 version): | 包 | 范围 | DSH 是否校验 | | --- | --- | --- | | `@deepseek-ai/dsh-llm` | `>=0.1.1-rc.2 <0.3.0-0` | 是 | | `@deepseek-ai/dsh-tools` | `>=0.1.0-rc.6 <0.3.0-0` | 是 | | `@deepseek-ai/cordis` | `^4.0.1 \|\| ^4.0.5-alpha.1` | 否(名称不以 `@deepseek-ai/dsh-` 开头,不参与判定) | 即支持 DSH **0.1.x 与 0.2.x**(含 prerelease)。DSH 进入 0.3.x 线后需重新评估再放宽,流程见 `PUBLISHING.md`。 用 `npm run verify:compat` 可在升级 DSH 后一条命令确认声明是否仍然成立(见 `DEBUGGING.md` 第四节);`npm run verify:host` / `npm run verify:e2e` 另可确认「改配置 → 下一轮生效」这条链路在升级后依然成立。 --- ## 版本历史 ### v0.10.0(2026-11-02) 延续 v0.9.0 的开发线,共 31 个提交。主题是**把「谁在生效」这件事搬到一个面板上**,并顺手把整个插件的按钮规格与宿主对齐。 **新增** - **「Soul 人设总览」面板**(侧栏入口 + 中间列 main 面板,默认开启):当前生效 / 工作区 × 会话级联 / 预设库三段。侧栏入口与 main 面板**成对注册**(只注册一侧会得到「点了没反应」的空壳) - **就地操作,少跳页面**: - 「切换 ▾」直接换全局人设(原先要 设置页 → 个性化 → 展开人设预设 → 找到卡 → 点使用,四步) - 失效绑定的「修复」就地展开预设胶囊改绑(原先要跳到插件详情页,改完还得切回来) - 为此给「用这个」与「取消本会话的临时人设」拆了独立类名:两者尺寸需求不同(前者与主按钮配对走 md,后者是列表语义),共用一个类改一个会坏另一个 - **会话行可点击跳到主视图**:键盘 Enter / Space 亦可;id 形式**两种都试**(无前缀 / 注册表原样)——宿主 `resolveTarget` 的校验依赖运行时状态(`summaries` 是否已加载、`sessions` 是否已实例化),押一种会静默失败 - **空会话识别与默认隐藏**:判据是**逐帧解压日志找 user/assistant 消息**,不是文件大小(实测真实空会话 8.6KB~22.6KB,按大小判会误判),也不是只解首帧(实测会把 18 个有对话的会话误判成空) - **自动刷新**:订阅 `soulController` store 的关键字段指纹 + 监听 `layout.panelInfo`。用指纹而非全量响应,是为了避开 `saving`/`loading` 这类高频字段把面板刷成幻灯片 **改进** - **标题读取改走宿主投影**:原先靠 `readTitleSnapshots` 为每个会话折叠整条事件流(34 个约 5 秒),只能「先显示短 id、标题稍后补」。改读 `sessions.list` 快照里的 `projectionValues.title`(侧栏用的同一份),**零日志读取** - **首屏骨架**:数据只差约 100ms,但「加载中…」三个字一闪会让人注意到延迟;改骨架占位,读屏仍能读到加载状态 - **判空结果落盘缓存**(`$DSH_HOME/soul-blank-sessions.json`,键含 mtime):逐帧解压 34 个会话约 400ms,全在点开面板的关键路径上。冷进程首次 408ms → 116ms - **面板自滚动**:不假设宿主容器高度可继承(那些类名 hash 过、CSS 在 web 前端 bundle 里),直接量祖先链里第一个会裁剪的容器 - **五类按钮全部对齐宿主 `Button.module.css`**: | 按钮 | 规格 | | --- | --- | | 总览「切换」/「修复」 | `sm + outline`(28px / 12px / radius-sm / .5px border-l3) | | 工作区预设胶囊 | `sm + outline` | | 人设页「取消本会话的临时人设」 | `md + outline`(36px / 14px / radius-md) | | 人设页「用这个」 | `md + primary`(与「取消」同行,字号行高必须一致) | - **光轨拖尾根治**:真正决定人眼可辨的是**折点间距**(= 拖尾跨度 ÷ 折点数),不是层数。8 层 22px、16 层 9.9px 都能看出分段;改成 48 层 + 8% 跨度 → **1.68px**,低于视觉分辨极限(约 2px),并把线端全改圆头 - **字体阶梯对齐宿主**:16/600 · 14/600 · 13/500 · 12/400 · 11/500,字重不超过 600 **修复** - 会话行标题显示成字面量 `session-`(`slice(0,8)` 恰好切在前缀上) - 会话标题全部退化成 8 位 id(阶段 2 的合并依赖函数式 setState 拿到最新 prev,改为独立 state + 渲染时查表) - 「N 个会话」把归档会话也算进去(某工作区显示 24 而实际只列 10) - 失效绑定的「修复」按钮太隐蔽,用户实际找不到(11px 次要灰 + 透明底) - 会话编号与侧栏口径不一致(曾显示 49 个而侧栏 4 个) - 光轨拖尾露出分段色阶(两次返工,见上) - 会话行跳转静默失败(`catch {}` 吞掉了宿主的 `unknown session` 错误) - 预设库给出一个点不动的跳转按钮(宿主设置弹窗没有公开打开方式),改为如实指引 **验证** - 断言总数 542 → **554 项**(13 个验证脚本) - **光轨性能实测**(Chrome headless + CDP,120 帧):16 / 48 / 96 层均为 **60 FPS**,平均 16.67ms、p95 16.8ms —— `stroke-dashoffset` 的重绘面积始终是同一条线,与层数无关 - **亮/暗对比度实测**(真实宿主 token + alpha 合成,14 个元素):修正了空会话开关的对比度(`label-tertiary` 亮色 3.71 → `label-secondary` 5.80) ### v0.9.0(2026-10-29) **新增** - **三级配置作用域(Global → Workspace → Session)**:人设不再是全局唯一 —— 工作区决定「这个项目用什么人设」,单个会话还能临时覆盖,其余字段逐级继承。合并语义与「应用预设」同一条规则(字段级部分覆盖),用户在设置页点「使用」看到的效果,与作用域里自动拿到的**逐字段一致** - **身份解析**:`sessionId → workspaceId` 索引在装配路径**之外**维护(宿主 `systemPrompt.section.text` 是同步调用,不能 `await` 也不能线性扫描),三个重建时机:首轮、宿主 `domain/changed` 事件、装配未命中时下一拍重扫。索引未命中回退**目录前缀匹配**(复用 0.8.1 的 `lib/bindings.mjs`),再未命中回落全局 —— 失败方向安全:任何一环不可用只能在「更精确」与「更宽松」之间移动,不会变成「错误的人设」 - **`enabled` 任何作用域都不能覆盖**:全局总开关始终说话。工作区与会话的覆盖白名单都不含它,合并函数里还有第二道过滤 —— 否则「关掉插件」就不成立了 - **会话覆盖存储**:`$DSH_HOME/soul-sessions/{sha256(sessionId)[:32]}.json`,与 `soul-config.json` 同级。文件名哈希而非转义(定长、无歧义、天然规避路径穿越),内容里另存原始 `sessionId` 并与文件名互校 —— 改个文件名就能读到别人的覆盖这条口子被堵死 - **配置修订号**:每次配置替换 `configRevision +1`,装配路径**只取一次快照**并把用到的修订号写进诊断状态。修掉一个真实隐患:`configCache` 由写队列整体替换,一次装配里多次读可能拿到两个版本 —— 症状正是「System Prompt Updated 已出现、模型却仍用旧昵称」。现在 `/api/soul/status` 能回答「进模型的是哪一版」 - **会话「人设」tab**(宿主 `conversation.view` 槽位):插件能拿到**当前会话 id** 的唯一正规入口(设置页的 `slotInject` 是空串)。左侧卡牌轮转(与设置页人设卡牌同构:中心大卡 + 两侧露边、位置指示只标中心卡),右侧详情面板与设置页卡背**逐字相同**(六个维度 + 自定义指令全文)。「用这个」即时生效不经保存按钮;提交只带该预设声明过的字段 - **插件详情页「工作区人设」**:平铺卡片,未绑定的排前面(那正是用户来这页要处理的事),点「绑定人设」内联展开人设胶囊。覆盖指向的预设被删后如实标注「人设『X』已不存在」—— 为此覆盖项里带 `__persona` 引用字段(不进合并、不进审计、只剩它时算空覆盖) - **会话覆盖回收**:三阶段保守状态机(发现候选 → 30 天宽限期 → 可回收),判定依据是 `sessionPersistence.list()`(扫磁盘),**绝不使用 `agents.list()`** —— 那是 live registry,会话切走就消失,用它判定会误删正在使用的覆盖。双条件:时间够**且**本轮扫描完整;扫描不完整时状态机对所有条目返回 keep。双入口:`/soul sessions [--apply]` 与详情页按钮,默认 dry-run,打开详情页不自动扫描 - **工作区覆盖放 `soul-config.json`、会话覆盖放 sidecar**:前者是配置(低频、有上限、用户在意变更历史),后者是状态(高频、无上限、随会话生灭)。性质不同,存储边界不同 **修复** - **工作区绑定误报「人设已不存在」**:失效判断只查了 `config.personas`(磁盘上的自建预设),而内置预设只存在于代码里的 `BUILTIN_PERSONAS` ⇒ 绑定指向内置预设(自驱型协作者 / 头脑风暴伙伴)被判成失效、标红。改用 `mergePersonas` 的合并视图 - **侧栏图标顶掉插件图标**:注册 `sidebar.panellist` 时复用了宿主的 id `'plugins'` —— 文档里 "reusing a shipped id puts you in THAT cell" 的含义是**替换该单元**而非并存,结果插件管理器的图标消失。已回滚为自己的 id,并暂时隐藏该入口(等独立 main 面板做好再启用) - **工作区人设区块整块消失(React #130)**:图标名写成 `ui.IconRefreshOutline14` / `ui.IconTrashOutline16`(照抄 npm 包的 `.d.ts`),但宿主 **seed 给插件的模块表**只有 `*Regular` / `*Medium` 后缀 ⇒ `undefined` ⇒ 渲染 undefined 组件 ⇒ 宿主卸载整个 slot entry。**类型声明 ≠ 运行时可用**,判据要以宿主 bundle 的 `TU` 定义为准 - **测试桩掩盖了上面那个 #130**:`verify-client` 的 `ui` 桩原先是 Proxy 无脑返回函数,拼错的图标名静默通过。已改为白名单 + 校验,访问未声明名字直接抛错 - **绑定按钮悬停时整块变黑**:自绘 `