🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

人设切换 yupaoa/dsh-persona-switcher

DeepSeek Harnessv0.3.4
yupaoa5 天前有提交已提供中文文档扫描:含敏感能力

DeepSeek Harness 人设切换插件:会话中途只换人设与语言风格,不动工具、技能与模型

36.4/100
综合分
5
GitHub 星标
1,217
npm 周下载
—/5
用户评分

数据截至 2026/10/30 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-persona-switcher
npm 源 · dsh-persona-switcher 的发布者账号已校验与本仓库一致

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · ui
装得上吗
本站已在 dsh 0.2.1-rc.2 下真实安装成功
非静态推断
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 5 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过
展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包dsh-persona-switcher @ 0.3.4
✓Node 引擎要求 ^22.19.0 || >=24.0.0 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/30 21:12:49

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件
查看扫描证据(共 29 处,展示前 25 条)
  • 读取本地文件
    lib/index.js:428
    entries = await readdir(resolveMascotDir(), { withFileTypes: true })
  • 读取本地文件
    lib/index.js:440
    const info = await stat(target).catch(() => undefined)
  • 删除本地文件
    lib/index.js:456
    await rm(target, { force: true })
  • 读取本地文件
    lib/index.js:580
    entries = await readdir(dir, { withFileTypes: true })
  • 读取本地文件
    lib/index.js:599
    text = await readFile(file, 'utf8')
  • 读取本地文件
    lib/index.js:792
    text = await readFile(file, 'utf8')
  • 读取本地文件
    lib/index.js:808
    const info = await stat(file).catch(() => undefined)
  • 写入本地文件
    lib/index.js:854
    await mkdir(dirname(file), { recursive: true })
  • 写入本地文件
    lib/index.js:856
    await writeFile(temporary, `${JSON.stringify(mascotMap, null, 2)}\n`, 'utf8')
  • 删除本地文件
    lib/index.js:857
    await rm(file, { force: true })
  • 写入本地文件
    lib/index.js:858
    await rename(temporary, file)
  • 删除本地文件
    lib/index.js:860
    await rm(temporary, { force: true }).catch(() => {})
  • 写入本地文件
    lib/index.js:1058
    await mkdir(join(dir, id), { recursive: true })
  • 写入本地文件
    lib/index.js:1059
    await writeFile(join(dir, id, 'ROLE.md'), serializeRole({ id, name, description, persona }), 'utf8')
  • 删除本地文件
    lib/index.js:1060
    await rm(join(dir, `${id}.md`), { force: true })
  • 删除本地文件
    lib/index.js:1087
    await rm(paths.bundleFile, { force: true })
  • 删除本地文件递归
    lib/index.js:1088
    await rm(paths.bundleDir, { recursive: true, force: true })
  • 写入本地文件
    lib/index.js:1169
    await mkdir(mascotDir, { recursive: true })
  • 写入本地文件
    lib/session-state.js:120
    await mkdir(dirname(file), { recursive: true })
  • 写入本地文件
    lib/session-state.js:123
    await writeFile(temporary, serialize(), 'utf8')
  • 删除本地文件
    lib/session-state.js:124
    await rm(file, { force: true })
  • 写入本地文件
    lib/session-state.js:125
    await rename(temporary, file)
  • 删除本地文件
    lib/session-state.js:127
    await rm(temporary, { force: true }).catch(() => {})
  • 写入本地文件
    lib/session-state.js:137
    await rename(file, `${file}.corrupt`)
  • 读取本地文件
    lib/session-state.js:166
    text = await readFile(file, 'utf8')
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/schemastery@deepseek-ai/dsh-client-locale@deepseek-ai/dsh-client-ui-primitives@deepseek-ai/dsh-client-ui-settings@deepseek-ai/dsh-client-ui-slots@deepseek-ai/cordis@deepseek-ai/dsh-tools
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 会话中途切换人设
  • 调整语言风格
v0.3.42026-10-29
· 中文说明 · 输入栏人设按钮:在权限与表情按钮之后、模型选择器之前新增「人设」入口,可直接搜索并切换当前会话角色;未绑定时显示「人设」,绑定后显示角色名。支持名称 / id / 描述搜索、当前角色勾选、键盘操作、失败重试与空库提示。 · 设置页开关:设置 → 人设切换 → 常规新增「输入框」卡片,可关闭输入栏的人设选择器。这是本机偏好,刷新与跨窗口保持,不影响 /role 命令或已绑定人设。 · /role none 修复:切到 none 后立绘现在会立即消失;重启恢复、切换会话或继承默认角色时也不会残留旧立绘。 · 明确的「当前无人设」段落:/role none 不再只清空人设段,而是明确告诉模型——部署人设才是当前身份,角色目录只是菜单,不能从目录里挑一个角色来扮演。修复切到 none 后模型有时自称阿米娅、有时自称鲸鱼娘的问题。 · 文档与版本:README 整理,补充 0.3.4 变更说明与输入栏开关文档。 · English · Composer persona picker: a new Persona button after permissions / reactions and before the model selector. It searches and switches the current session's role, showing the role name when bound and Persona otherwise, with search by name / id / description, current-role tick, keyboard support, retry and empty-library states. · Settings toggle: Settings → Persona Switcher → General now has a Composer card to hide the picker. This is a per-browser preference that survives refreshes and syncs across windows; /role and an already-bound persona are unaffected. · /role none fixes: the mascot now disappears immediately, including after restart, session switches, or when a default role was inherited. · Explicit no-role section: /role none no longer leaves the persona section empty. It states that the deployment persona is in force and that the role catalogue is a menu, not a list of candidate identities. This fixes the model occasionally answering as Amiya or whale-girl after switching to none. · Docs and version: README cleanup plus 0.3.4 notes and composer-toggle documentation. · 升级说明 / Upgrade 安装或升级后请完全退出并重启 DSH,仅刷新页面不够。 Fully quit and restart DSH after installing or upgrading; a page refresh is not enough.
v0.3.12026-10-21
· 新增 · /role 可以从菜单点选了。 输入框打出 / 后的命令列表里点 role,弹出角色选择器:顶部搜索框(角色名 / id / 描述片段都能搜),当前生效的角色带勾并标「当前」,第一行固定是 none(回到部署人设),点一下就执行 /role ;列表每次打开都重读角色库,设置页里增删角色即时生效。 · 装饰宿主命令,而不是替换它。 /role list、/role 、/role default 、/role none 全部照旧落到宿主 handler;只有裸 /role 改由弹窗回答。点选本身就是一次真实命令执行,所以角色变更提示、sessions.json 记忆写入和转录里的命令节点都保留,浏览器侧没有复制切换逻辑。 · GET /session 新增 liveRoleId。 roleId 仍是记忆里的选择,liveRoleId 是此刻在生效的角色;列表的勾读后者,所以默认角色 / /role 切换 / /role none 三种状态都显示正确。 · 弹窗只在真正执行后才关闭。 宿主「收了命令但没匹配上」(value.matched === false)时不再静默关闭,而是保留列表与搜索词、显示原因,回车即可重试;判断用 === false,旧宿主返回结构没有该字段时不会被误判成失败。 · 升级说明 装到桌面端后需完整重启 DSH(宿主半边新增了 /session 的 liveRoleId 字段),再刷新页面。
v0.3.02026-10-18
· 新增 · 替换角色吉祥物现在会翻牌,不再硬切。以前 /role 切换(或在设置页替换插画)会在客户端察觉的瞬间换掉 src——旧图一帧,新图一帧,中间什么都没有。现在吉祥物把旧图留在屏幕上当卡牌正面,把新图翻进来当背面:卡牌绕自身竖直中线旋转 180°,约 1 秒,缓入缓出,在侧对镜头的 90° 处转得最快,此时轴线看起来就是图片自己的中心。卡牌全程保持平贴——不抬起,不加阴影。检测逻辑没动:/revision 计数器仍能在约四分之一秒内发现切换,翻转随即开始——这是叠在它上面的一层呈现,不是改它。 · 翻转能自愈,不会卡住。新插画在翻转开始前就解码好(背面不会是空框),翻转在卡牌自己的 animationend 上提交,另有一个兜底定时器在最新背面设定后 FLIP_TOTAL_MS + 200ms 提交——隐藏标签页暂停 CSS 动画,也不会再把吉祥物卡在翻转中途、露出两张半透明的脸。解码不了的 URL 跳过翻转直接落定,让落定图片自己的 onError 丢掉真正损坏的角色。翻转中途到来的切换会重定向背面(后来者胜);被取代的预加载永不提交。 · 只有替换才翻。首次出现(无 → 图)和解绑(图 → 无)沿用外层原有的普通透明度淡入淡出——屏幕上没东西时没有卡可翻。在 prefers-reduced-motion 下原地切换,和悬停放大一样:反馈保留,1 秒翻转不保留(Chromium 从 Windows 自身的动画设置推断这一点,所以「减少」并不罕见——见 0.2.1 关于悬停的说明)。新增的设置项可按插件覆盖这一判断,下面说明。 · 按插件的动画开关:跟随系统 / 始终开 / 始终关(System / Always / Never)。上面的 reduced-motion 判断是系统级无障碍偏好,Windows 动画开关关掉的机器(Chromium 此时报告 prefers-reduced-motion: reduce)会失去翻转,除非改全局设置。设置页的吉祥物卡现在带一个三选一,存在 dsh-persona-switcher:mascot-motion 下:始终开即使在系统减少下也翻,始终关即使在系统允许动画处也原地切换,默认的跟随系统与以前一样完全照 OS 走。损坏或手改的值回落到跟随系统——它永远不能朝任一方向强制选择。 · 这些都不破坏共存规则。任何地方都没有 CSS filter(也没有阴影——翻转就是纯旋转),翻转动画的是新的内层 stage/card/face 层,所以外层的 transform: scale() 仍是尺寸倍数,拖拽计算永远看不到它;两个面是 pointer-events: none,永远不吃拖拽;悬停仍像以前一样放大落定的插画——但绝不放大翻转中途的面,那里悬停缩放会和旋转打架。 · 变更 · 吉祥物客户端的检查对切换问了个不同的问题。替换在翻转提交前就已在屏幕上(作为背面),所以切换检测轮询现在问「这张图被任一面画出来了吗」(paintsSrc),而不是「它是第一张吗」——旧问题把检测延迟和动画混为一谈。翻转本身由新增的一节验证(纯 planFlip 状态机、首次显示不渲染卡牌、替换渲染 front=old/back=new 且盒子已按新图尺寸、animationend 提交、兜底定时器提交、reduced motion 原地切换),外加样式表断言钉住轴线、时长和没有 filter。
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。

README

由 DeepSeek 最新模型翻译生成
dsh-persona-switcher 会话中途换「说话的人」 DeepSeek Harness 的人设切换插件:把一个角色的人设与语言风格装进当前会话,随时可换、随时可退;工具集、技能、模型选择全程保持宿主默认不变。 Mid-session persona-only switching for DeepSeek Harness: swap the model's identity and speaking style without touching its tools, skills, or model selection. 环境要求:DSH >= 0.2.0-rc.1(兼容 0.2.1-rc.x)。 版本变化见 CHANGELOG.md。 人设怎么进入提示词:/role → sessions.json → agent/created 绑定 → persona-switcher:persona prompt section 它能做什么 - 📚 角色库:rolesDir 下每个角色一个 ROLE.md(id / name / description + 人设正文)。 - 🔄 会话中途切换:/role <id> 立即生效,下一步就是新的人设。 - 🎭 输入栏人设按钮:权限与表情按钮之后、模型选择器之前,点击 人设 搜索并选择本地角色。
展开完整文档(README 剩余 1 节)
dsh-persona-switcher

会话中途换「说话的人」

DeepSeek Harness 的人设切换插件:把一个角色的人设与语言风格装进当前会话,随时可换、随时可退;工具集、技能、模型选择全程保持宿主默认不变。

Mid-session persona-only switching for DeepSeek Harness: swap the model's identity and
speaking style without touching its tools, skills, or model selection.

环境要求:DSH >= 0.2.0-rc.1(兼容 0.2.1-rc.x)。
版本变化见 CHANGELOG.md。

人设怎么进入提示词:/role → sessions.json → agent/created 绑定 → persona-switcher:persona prompt section

它能做什么

- 📚 角色库:rolesDir 下每个角色一个 ROLE.md(id / name / description + 人设正文)。
- 🔄 会话中途切换:/role <id> 立即生效,下一步就是新的人设。
- 🎭 输入栏人设按钮:权限与表情按钮之后、模型选择器之前,点击 人设 搜索并选择本地角色。
未绑定时显示「人设」,绑定后显示角色名(无名称时显示 id),长名称省略、悬停可看全名;切换对话、重启恢复或 /role 修改后都会同步。列表支持名称 / id / 描述搜索、当前角色勾选、键盘操作、失败重试与空库提示,每次打开重读角色库。添加人设 打开设置页原有新增表单, id 自动建议 char001、char002……并跳过已有 id,未完成的编辑会保留。
- 🖱️ 点选切换:输入框里打出 / 后点 role,直接弹出角色列表——顶部可搜索(名字、id、描述都能搜),当前角色带勾,第一行固定是 none(回到部署人设),点一下就等于执行 /role <id>。列表每次打开都重读角色库,设置页里的增删立刻反映出来;命令本身的 /role list、/role <id>、/role default <id>、/role none 全部照旧可用。
- 🪄 走宿主原生人设机制:把角色正文注册为该 agent 作用域下的独立 prompt section persona-switcher:persona(排在部署人设前缀之后,与内置 dsh-subagent 给子 agent 装人设是同一条路),不是拼接字符串,也不改会话的插件组合。
- 🧬 不动宿主的身份行:部署 persona 前缀 You are a coding agent powered by the {{model}} model. 由宿主自己注册,本插件永不遮蔽它——切角色是加一段人设,不是换掉身份,所以模型始终知道自己跑在哪个模型上。
- ↩️ 可随时退回:/role none 撤销本会话的角色,换成一个明确的「当前无人设」段落——说明部署人设才是当前身份、角色目录只是菜单,模型不会从目录里挑一个角色来扮演;宿主的身份行照旧。
即使配置了 defaultRole,它也不会被重新装回来,重启后依然保持。
- ✅ 默认角色:defaultRole 让新会话自动上身,无需每次手动切。
- 💾 重启后依然在:本会话选过的角色记在 $DSH_HOME/persona-switcher/sessions.json, DSH 完全重启后该会话的第一步就是这个人设(角色在 agent 创建事务里就装好,不必等到第二步);卸载插件或删掉该文件即彻底忘记。
- 🖥️ 设置页:设置 → 人设切换,可视化管理角色库(新建 / 编辑 / 删除)、改新会话的默认角色。
- 🐳 屏幕吉祥物:给角色导入一张立绘,当前会话的角色就作为吉祥物出现在界面上,可拖到任意位置。
只在「有会话界面 + 该会话有角色 + 该角色绑了立绘」三条同时成立时出现;没绑立绘的角色不显示,也不占位。总开关、大小(0.5×–2.5×)与位置重置都在设置页。
- 🃏 角色切换的翻纸动画:替换立绘时不再硬切——旧图作正面、新图从背面翻入,卡片绕立绘自身竖轴转 180°(约 1 秒,不抬起、不加阴影)。「切换动画」有三档:跟随系统(默认,尊重系统的「减少动画」偏好)/ 始终开 / 始终关——所以系统关了动画的机器也能单独让吉祥物翻起来,不需要改系统级开关。
- 🧭 专属导航图标:设置页左栏里它带着自己的面具图标。
- 🔍 role_probe 诊断工具:一条命令看清当前绑定、系统节点、工具清单(可关闭)。
- 🔒 只动该动的:不声明任何角色级工具、技能或模型——切换人设不会顺带放权。

它刻意不做什么

| 不做 | 原因 |
| 不改工具集 / 技能 / 模型 | 「人设是说话的人,能力是手里的工具」——本插件只换前者 |
| 不注册 agent preset,也不 recompose 会话 | 换 preset 会重绑整个 agent 插件组合,工具与命令会一起被换掉(实测 33 个工具掉到 7、10 条命令掉到 7),正是本插件要避免的事 |
| 不遮蔽宿主的 persona 前缀 | 那是部署写的身份行(You are a coding agent powered by the {{model}} model.)——覆盖它模型就不知道自己跑在哪个模型上;角色以独立 section 加在它后面 |
| 不提供自主切换工具(switch_to_role 之类) | 切换只经 /role 命令(含人设按钮调用),避免模型自己改人设 |
| 不读角色级 tools / denyTools / skillsDir / model 字段 | 这些字段即使写了也一律忽略,行为可预测 |

它动你磁盘上的哪些文件(权能边界)

一句话:宿主侧只写下面 6 个位置,浏览器侧只写本机 localStorage;不联网、不上传角色内容、不读任何凭据文件。
除这些以外的一切都只发生在内存里(一条 prompt section + 一条会话消息)。

会写入的 6 个位置

| 位置 | 何时写 | 写什么 | 由谁触发 |
| $DSH_HOME/persona-switcher/sessions.json | 每次切换 / /role none | 每会话一条「角色 id + 时间戳」。原子写(临时文件 + rename),文件损坏时改名成 sessions.json.corrupt 而不是删除;超过保留窗口的旧记录在下次写入时清理 | /role、设置页点选 |
| <rolesDir>/<id>/ROLE.md | 你在设置页新建/编辑角色 | frontmatter(id/name/description)+ 人设正文;顺手清掉同名的旧扁平文件 <rolesDir>/<id>.md | 只有你点保存 |
| <rolesDir>/<id>/(含目录本身) | 你在设置页删除角色 | ROLE.md → 该角色目录 → 旧扁平文件。真删除,不进回收站;若删掉的正是当前默认角色,还会清掉设置里那条 defaultRole | 只有你点删除 |
| $DSH_HOME/persona-switcher/mascot/<roleId>-<stamp>.<ext> | 你在设置页导入立绘 | 立绘原图字节(png / jpeg / webp,解码后 ≤ 1 MiB)。每次上传写一个新文件、旧文件随后清理,每角色最多留 3 份;不写进角色目录,角色正文与立绘互不干扰 | 只有你点导入 |
| $DSH_HOME/persona-switcher/mascot-map.json | 导入 / 清除立绘 | { "<roleId>": { "image": "<文件名>", "box": "WxH" } };文件损坏按「没有任何角色有立绘」处理,不删除你的文件 | 只有你点导入/清除 |
| profile 的 cordis.patch.yml | /role default <id>、设置页改默认、或删除默认角色 | 间接写入:插件只调宿主官方的 settings 表层(settings.update / settings.mutate),真正落盘的是宿主的 ConfigEditor | 只有你显式设/清默认 |

只读的东西

- <rolesDir>:启动时扫描 + 每次 CRUD 后刷新(只读 ROLE.md 的 frontmatter 与正文)。
- $DSH_HOME 环境变量:用来定位上面两个路径。
- 本会话自身的 surface / 事件流:role_probe 用它报告系统节点、上一次请求头里发给模型的工具清单。
- 宿主 agentPresets.composedPreset():只读,仅用于报告「切换没动过会话组合」这一证据。

只在内存或浏览器本地(不写入上面 6 处)

- 一条角色库 prompt section(persona-switcher:catalogue,顺带把可用角色清单列进系统提示词)+ 每会话一条 agent 作用域的人设 section;
- 一条 /role 斜杠命令;exposeTool: true 时一个 role_probe 工具;
- 浏览器侧:一个设置页 section(id 固定 persona-switcher,注册在 settings.section 槽位、order: 15)+ 若干 ps- 前缀样式(锚定在 .dsh-panel.ps-page,不污染宿主 UI);
- 输入栏另有独立的 conversation.input.left 列表条目(id persona-switcher-picker、order 10,在 dsh-meme 的 order 5 后),及仅在打开时出现的菜单 portal。样式限于 ps-composer-;不替换权限、表情或模型槽位,不改输入草稿。新增入口调用宿主设置 shell 的 openSection 动作,若当前宿主不提供该动作,会提示从设置页手动进入,选择人设仍可使用。
- 浏览器侧还有一个独立的 React root(挂在 <body> 下的 #dsh-persona-switcher-mascot-dock)承载屏幕吉祥物,它只读宿主的 sidebarRight.mounted 快照来知道「现在看着哪个会话」,从不写入该服务;总开关、大小与动画偏好存在 localStorage 的 dsh-persona-switcher:mascot-shown / :mascot-size / :mascot-motion,位置存在 :mascot-pos(仅本机、不上传)。
- announceRoleChange: true(默认)时,切换后往该会话注入一条 <system-reminder>Role change: … 用户消息——它是真实会话消息,所以会出现在该会话的记录里,这是刻意的(见「配置项」)。每一次人设变更都会发:角色 → 角色、部署人设 → 角色、角色 → /role none 回到部署人设、以及被记住的角色从库里消失后改由别的角色接手,四种方向各有一条对应的声明;只有「本会话原本就是这个角色」的自动恢复保持安静,因为那不是变更(见「配置项」)。

网络

- 没有出站请求:不联网、不上传、不回传角色内容,也不读 ~/.dsh/.credentials.yaml 之类的凭据文件。
- 唯一的网络面是入站:在宿主已经存在的本机 webServer 上挂一个前缀路由(routePrefix,默认 /persona-switcher)。插件自己不开端口、不建服务器。该路由只有这些端点:

| 方法 | 路径 | 作用 |
| GET | /roles | 列出角色库(每个角色带 mascot: { image, box } \| null) |
| GET | /role?id=<id> | 读一个角色 |
| POST | /role | 新建 / 覆盖一个角色 |
| DELETE | /role?id=<id> | 删除一个角色 |
| GET | /session?session=<id> | 该会话记住的角色(roleId)与此刻在生效的角色(liveRoleId,未绑定均为 null)——/role 列表的勾和屏幕吉祥物读的都是后者 |
| POST | /mascot?role=<id> | 为角色导入立绘(JSON:image 为 data URL、box 为 WxH) |
| GET | /mascot-file/<file> | 读回一张立绘 |
| DELETE | /mascot?role=<id> | 清除该角色的立绘 |

其余一律 404;/mascot 的请求体上限 2 MiB(base64 膨胀后),解码后 ≤ 1 MiB。
- 这条路由自身不做鉴权,它的安全性来自「跑在 127.0.0.1 的宿主 webServer 里」(桌面端默认如此)。如果你的宿主 webServer 被绑到非 loopback 地址,这条路由会跟着一起暴露——请保持 loopback。细节见 SECURITY.md。

一概不碰的东西

- 模型的 provider / 模型选择 / 密钥(llm- 那些行)——本插件一行都不读;
- 会话的工具集、命令表、技能、插件组合:不注册 agent preset、从不调用 recompose()(npm run verify 一旦在产物里发现 recompose 就直接失败);
- 其它插件、其它配置行的任何字段;
- 你的工作区文件与 DSH 会话日志(唯一的例外就是上面那条 role-change 消息,它属于会话记录本身);
- 角色文件里的 tools / denyTools / skillsDir / model 字段:读了也一律忽略。

role_probe 的额外权能(可关)

role_probe 的 command 参数走的是真实命令管线(ctx.commands.execute),也就是它会执行该会话里任何已注册的斜杠命令(例如 /compact),并不限于 /role。这是留给测试与诊断的口子;exposeTool: false 之后,模型侧工具与未装本插件时完全一致。

想收紧权限时

你 profile 的 cordis.patch.yml
- id: persona-switcher
config:
exposeTool: false # 不注册 role_probe announceRoleChange: false # 不注入 role-change 消息 rolesDir: D:\dsh-roles # 只让它读一个专用目录

撤销与清理

- 卸载:dsh plugin remove --profile desktop dsh-persona-switcher——你的角色文件不会被删。
- 想彻底清干净:删 $DSH_HOME/persona-switcher/sessions.json(所有会话遗忘角色)、按需删 <rolesDir>、再把 profile cordis.patch.yml 里可能残留的 defaultRole 一行去掉。除此之外没有别的痕迹。

安装

要求:DeepSeek Harness >= 0.2.0-rc.1,Node.js ^22.19.0 || >=24.0.0(随宿主)。

dsh plugin add 会把参数原样透传给 pnpm add,所以下面三种写法都可用。

1) 从 npm 安装(推荐)
dsh plugin add --profile desktop dsh-persona-switcher

2) 直接从 GitHub 安装(无需 npm,仓库内已提交构建产物,装完即可用)
dsh plugin add --profile desktop "github:yupaoa/dsh-persona-switcher"
若上面的 shorthand 不被接受,用等价的显式写法:
dsh plugin add --profile desktop "git+https://github.com/yupaoa/dsh-persona-switcher.git"

3) 从本地目录安装(开发 / 离线)
dsh plugin add --profile desktop "file:D:\path\to\dsh-persona-switcher"

把 --profile desktop 换成你自己的 profile 名;dsh plugin list --profile desktop 可以查看已装插件。

装完必须重启

插件在宿主启动时装配(entry、设置页 section、斜杠命令都在启动阶段注册)。
安装 / 卸载 / 升级后请完全退出并重启 DSH,仅刷新页面不够。

卸载

dsh plugin remove --profile desktop dsh-persona-switcher

角色文件留在你的磁盘上(见下),不会被删。

如果刚发布的版本装不上

部分 DSH 版本对 profile 启用了新版本冷却期(minimumReleaseAge):刚发布不久的版本会被拒绝安装。等待冷却期结束即可;如果你确认要立刻装,把包名加进 profile 的 pnpm-workspace.yaml:

minimumReleaseAgeExclude:
- dsh-persona-switcher

(该文件位于你的 profile 目录,例如 ~/.dsh/profiles/desktop/pnpm-workspace.yaml。)

快速上手

1. 写一个角色文件

默认角色库在 $DSH_HOME/roles(DSH_HOME 缺省为 ~/.dsh,即 ~/.dsh/roles)。
每个角色一个目录或一个文件:

id: whale-girl
name: 鲸鱼娘 description: 温柔高效的鲸御姐,本鲸自称

你是 DeepSeek 鲸鱼娘,说话温柔、简洁、带一点自嘲的幽默感……
也支持扁平写法:~/.dsh/roles/whale-girl.md。

2. 在会话里切换 text /role 查看本会话当前角色与角色清单(当前角色带 ) /role whale-girl 切换到该角色(当前这一步的提示词已组装完毕,故下一步生效) /role none 撤销本会话角色,回到部署默认人设,并安装「当前无人设」段落(重启后仍是部署人设) /role default whale-girl 把它设为新会话的默认人设

也可以完全不用命令:设置 → 人设切换里点选即可。

切换会立刻写进 $DSH_HOME/persona-switcher/sessions.json(每个会话一条记录,只存角色选择与时间戳):DSH 完全重启后,该会话第一步的提示词里就带着这个角色——恢复发生在 agent 创建事务内,而不是等第一次 pre-step(宿主是先组装提示词、后跑 pre-step 的)。/role none 记下的不是「没有选择」,而是「本会话就要部署人设」,所以配了 defaultRole 也不会被重新装回来;它的提示词里还会有一个「当前无人设」段落,明确告诉模型角色目录只是菜单,不能从里面挑身份。
当前会话的角色仍只存在于内存里,/role 会分别显示「本会话角色」与「已记住」。

角色库格式

路径:<rolesDir>/<role-id>/ROLE.md 或 <rolesDir>/<role-id>.md

| 字段 | 必填 | 说明 |
| id | 建议 | 角色 id;缺省时用目录名 / 文件名。只允许 [a-z0-9][a-z0-9-] |
| name | 否 | 展示名,设置页与 /role 清单里显示 |
| description | 否 | 一句话说明 |
| (正文) | 是 | frontmatter 之后的内容即人设文本 |

规则与边界:

- 只读人设字段。 tools / denyTools / skillsDir / model 等字段一律忽略。
- 纯 frontmatter(无正文)的角色会被跳过并告警——空人设没有意义。
- 角色 id 只是你的文件名,不进任何全局注册表:换角色不改会话的工具、命令、技能与模型。

设置页

设置 → 人设切换(左栏里带面具图标的那一项)分两个标签页:

常规

- 改新会话的默认角色(defaultRole 输入框,留空即回到部署默认;改完保存会写进 profile 配置);
- 开关 role_probe 诊断工具(exposeTool);
- 输入框卡片:开关输入框底部的「人设」选择器(默认开)。这也是本机值(存在 localStorage),关闭后注销对应的 conversation.input.left 条目,/role 命令与已绑定人设不受影响;刷新后保持,跨窗口同步;
- 屏幕吉祥物卡片:总开关(默认开)、大小滑块(0.5×–2.5×)、「切换动画」三档(跟随系统 / 始终开 / 始终关)、「回到默认位置」。这些都是本机值(存在 localStorage),不进 profile 配置;改完当场生效,不需要重启。「切换动画」控制角色替换时立绘的翻纸动画:默认跟随系统的「减少动画」偏好,选「始终开」则系统关闭动画时也照常翻转,选「始终关」则即使系统允许动画也瞬时换图。

角色库

- 角色列表(每条显示名称、id、描述;页面标题旁标着角色数);
- 新建 / 编辑 / 删除角色文件,删除有二次确认;
- 每行可导入 / 换一张 / 清除立绘:导入后该行左侧显示立绘缩略图并标记「有立绘」,当前会话的角色就会以吉祥物形式出现在界面上;
- 当前默认角色的具体值在「常规」页的输入框里看(列表本身不打标记)。

会话中途切人设只有 /role 一条路:浏览器侧只做角色库的增删改,不碰任何会话。

这些操作走宿主本机服务上的 /persona-switcher 路由(与 DSH 桌面端同样只监听 127.0.0.1,不对外暴露);角色内容只存在你自己的磁盘上,插件不联网上传任何东西。

配置项

| 键 | 类型 | 默认 | 说明 |
| rolesDir | string | $DSH_HOME/roles | 角色库目录 |
| defaultRole | string | 空 | 新会话默认角色 id(运行时可在设置页改) |
| exposeTool | boolean | true | 是否注册 role_probe 工具(运行时可在设置页改) |
| announceRoleChange | boolean | true | 切换后是否往该会话注入一条 role-change 声明消息(默认开:替换 system 前缀能改提示词,改不掉模型对自己旧回复的模仿;只在确信 prompt 替换已足够时才关)。覆盖所有人设变更方向,含 /role none 退回部署人设——这个方向尤其需要声明,因为该会话里模型的旧回复正是在模仿刚被撤销的角色。只对「本会话角色没变」的自动恢复静默 |
| routePrefix | string | /persona-switcher | 设置页 CRUD 路由前缀 |

覆盖配置用普通的 id 定向 patch 行(不带 insert,只改写已存在的 entry): yaml

你 profile 的 cordis.patch.yml

- id: persona-switcher
config:
exposeTool: false

诊断:role_probe

role_probe 是唯一诊断出口,报告:本会话绑定的角色、为该会话记住的角色与状态文件、部署默认人设是否仍生效、默认角色、已注册命令、会话的 agent preset 组合(作为「切换没动过组合」的证据)、surface 节点、 system 节点清单(seq + 字符数 + surfaceOp)、上一次请求头发给模型的工具清单;还可用 command 参数让真实命令管线执行一条斜杠命令: json { "command": "/role list" }

exposeTool: false 可完全关闭它(关闭后模型侧工具与未装插件时完全一致)。

工作原理(一句话)角色正文注册成该智能体作用域下的独立提示词 section persona-switcher:persona(order 137,紧跟在部署 persona 前缀 deployment:persona-prefix(order 0)之后),只影响这一个会话;部署那份前缀(You are a coding agent powered by the {{model}} model.)原封不动地留在提示词里。
prompt registry 每一步重新组装系统节点并发出 system-prompt/change,所以下一步就是新的人设、旧人设文本被替换而不是堆叠;/role none 把角色正文换成一个明确的「当前无人设」段落,身份行不受影响。没有这个段落时,模型会把角色目录当成可选身份列表,自己挑一个来扮演(实测挑中过 whale-girl);现在目录被明确标成「菜单,不是当前身份」。
整条路径只碰提示词输入,会话的插件组合、工具、命令、技能、模型一概不动。浏览器侧只做两件事:注入设置页 section,以及给左栏换上自己的图标。

兼容性

- DSH >= 0.2.0-rc.1;
- 依赖注入 commands / systemPrompt / tools(peer 全部 optional);settings 与宿主 agentPresets 只是可选读,后者仅用于 role_probe 的诊断输出。
- 与 dsh-role-switcher 等其它人设/能力插件互不影响:本插件不注册 preset,也不占用角色 id。
- 可与其它屏幕插件同屏共存:本插件用独立的 DOM id、独立的 id 与独立的 localStorage 命名空间(dsh-persona-switcher:*),吉祥物样式里没有任何 CSS filter(避免合成层反复重绘),翻纸动画只动 transform 与 opacity(不触发布局、不加阴影),吉祥物不监听 keydown(Esc 留给别的插件用)、不注册 contextmenu 拦截(右键仍出系统菜单)、也不往 sidebarRight 注册任何东西——只读它的 mounted 快照。

开发 bash
npm install
npm run build # 构建 lib/client.js(esbuild,产物入仓库)
npm run verify # 检查不变量:bundle id、patch 行、发布配置、不许出现 recompose、不许遮蔽部署人设前缀
npm test # 无网络测试:bundle 模拟 + 宿主绑定不变量 + persona 绑定不变量 + 会话状态持久化 + 创建期绑定(重启后第一步即生效)+ role-change 声明 + 吉祥物 host 侧 + 吉祥物 client 侧

开发时用 file: 装插件的话:npm run build 之后必须 npm run install:desktop。
file: 装出来的是拷贝,而且 pnpm 对「目录依赖」记录的 specifier 与 version 都是 file:…
(重建不会让它变),于是它判定"依赖已是最新"、一个文件都不换。实测:pnpm 报 Done、已装目录的 lib/ mtime 也是新的,但里面 package.json 还是旧版本、client.js 还是旧字节。
install-desktop.mjs 因此自己把构建产物同步过去,并逐字节校验版本与 lib/ 关键文件,全部一致才打 PASS。它用 DSH 自带运行时的 node + pnpm(profile 的 PATH 里没有 pnpm),且只对这一条命令放宽 pnpm 的 minimum-release-age 策略。host 侧改完要完全重启 DSH(刷新不够)。

(npm test 实际串起 7 个脚本:simulate-client、verify-host-binding、verify-session-state、 verify-created-bind、verify-role-notice、verify-mascot、verify-mascot-client; prepublishOnly 会把它们全跑一遍。其中 verify-mascot-client 在 Node 里驱动真实组件——自建 hooks 宿主跑组件本身,挂载接线则对着真 React root 验——所以吉祥物的四条件、拖拽、总开关、尺寸与位置重置都不需要开浏览器就能回归。)

改完 src/client/index.jsx 必须重新 npm run build 并提交 lib/client.js: CI 会重建产物并逐字节比对(git diff --exit-code -- lib/client.js),产物过期 CI 会失败。
构建已被固定为与工作目录无关、且 esbuild 版本精确锁定,因此比对是稳定的。

本地试用: powershell
dsh plugin add --profile desktop "file:D:\path\to\dsh-persona-switcher"

完全退出并重启 DSH

给贡献者的三条红线

1. lib/client.js 里的 id 必须等于 package.json 的 name(dsh-persona-switcher),不是 cordis.patch.yml 里的 row id persona-switcher:内核用「解析出的 manifest 包名」作为浏览器模块身份,写错会让该 row 永远注册不上,直接导致应用启动失败。
scripts/build-client.mjs 已改为自动读包名,请勿硬编码。
2. entry 只能有一个来源:插件自带的 cordis.patch.yml。禁止在 profile 的 cordis.patch.yml 里再 - insert: 一条同 id 的 entry(两个 Loader source 解析到同一包名会让组合失败);要改配置只能用上面的 id 定向 patch 行。
3. 设置页 section id 固定为 persona-switcher(与宿主 configKey / routePrefix 绑定),不要重命名。

反馈与贡献
- 🐛 Bug / 功能建议:请开 Issue (模板会提示需要的信息)。
- 🔧 代码贡献:见 CONTRIBUTING.md,PR 前请确保 npm run verify、npm test 通过且产物已重建。
- 🔐 安全问题:见 SECURITY.md。

许可证

MIT © 2026 yupaoa

Mid-session persona switching for DeepSeek Harness. Swap the model's identity and speaking
style without touching its tools, skills, or model selection.
bash

install (then fully restart DSH)

dsh plugin add --profile desktop dsh-persona-switcher

or straight from GitHub (the build artifact is committed, no build step needed)

dsh plugin add --profile desktop "github:yupaoa/dsh-persona-switcher"

Drop one role per file in $DSH_HOME/roles (~/.dsh/roles by default):
markdown

id: whale-girl
name: Whale Girl
description: A gentle, efficient whale persona

You are Whale Girl. Speak warmly and concisely...

Then, inside a session:
text
/role show this session's role and the role list
/role whale-girl switch this session to that persona
/role none drop the role and install the explicit no-role section
/role default whale-girl make it the default for new sessions

There is also a Settings → Persona Switcher page, in two tabs: General (edit the
defaultRole used by new sessions, toggle the role_probe diagnostic tool, and control the on-screen
mascot) and Role library (list / create / edit / delete role files, with a delete confirmation,
plus import / replace / clear artwork per role). Its left-nav entry gets its own mask icon. Roles
are plain Markdown on your disk; the plugin never uploads anything, never touches any session from the
browser side, and never changes which tools the model can call. Switching a live session is /role's
job.

The composer also has a Persona button after permissions / dsh-meme and before the
model selector. Its label is Persona when unbound, otherwise the active role's name (or ID
if unnamed), including restored bindings and subsequent /role changes. Long labels truncate
with a full-name tooltip. It searches local names, IDs and descriptions and dispatches the same
/role command to that composer's session, preserving drafts and restart persistence. Add persona
opens the existing settings library editor with the first unused char001, char002, … ID pre-filled and editable. An unfinished settings edit is preserved. The button occupies its own native list-slot entry, so other plugins keep their controls.

On-screen mascot. Import artwork for a role and the role of the session you are looking at appears
as a draggable mascot — draggable anywhere, and it stays where you put it (no edge snapping). It shows up only when a session screen is on screen, that session has a role (not none), and that role has artwork bound; a role without artwork shows nothing at all, not a placeholder. The General tab carries 总开关(关闭后完全不挂载吉祥物,不产生请求,也不做解码)、0.5×–2.5× 尺寸滑块、三选一的切换动画选项,以及「回到默认位置」按钮。替换画作时用卡片翻转,而不是硬切:旧画作留在正面,新画作作为背面转进来——绕画作自身的垂直中线旋转 180°,约一秒,不抬升,不加阴影。动画选项有三个:System(默认,遵循系统的减少动态效果设置)、Always(即使系统要求减少动态效果也照转——Windows 动画开关关掉的机器,不改系统级偏好也能看到翻转)、Never(即使系统允许动画也原地替换)。开关、尺寸和动画偏好都是本设备的取值,存在 localStorage 里,立即生效,无需刷新。画作写入 $DSH_HOME/persona-switcher/mascot/,映射表 mascot-map.json 放在旁边——绝不写进角色目录,这样角色的文字和图片始终是各自独立的文件。仅支持 png / jpeg / webp,解码后不超过 1 MB,每边不超过 4096 px,每个角色三个版本。

角色以本插件自己的提示词段落形式应用——persona-switcher:persona,紧跟在部署人设前缀之后(顺序 0),注册在该会话自己的作用域里——内置的 dsh-subagent 给子代理设置人设用的也是同一套机制。部署自带的前缀(You are a coding agent powered by the {{model}} model.)永远不会被遮蔽,所以切换是在身份行旁边加上角色文字,而不是替换它;/role none 则把角色文字换成一个显式的无角色段落,把角色目录标记为未启用的菜单。会话的插件组成、工具、命令、技能和模型都不受影响;一旦出现预设重组——或对部署自带段落的作用域内重新注册——npm run verify 就会让构建失败。

切换还会写入 $DSH_HOME/persona-switcher/sessions.json,这样会话在 DSH 完全重启后仍保留自己的人设——它在创建智能体时就被重新应用,所以出现在该会话第一步的提示词里,而不是晚一步。/role none 记录的是「有意使用部署人设」,而不是删掉条目,所以这个选择也能扛过重启,永远不会被配置的 defaultRole 覆盖。删掉这个文件,所有会话就都忘了。

磁盘上会动哪些地方

六个写入位置,仅此而已——没有网络出口,没有上传,不读取凭据:

| 路径 | 写入时机 | 内容 |
| $DSH_HOME/persona-switcher/sessions.json | 每次切换 / /role none | 每个会话一条记录(角色 id + 时间戳);原子写入,先写临时文件再重命名,文件损坏时重命名为 sessions.json.corrupt 而不是删除 |
| <rolesDir>/<id>/ROLE.md | 你在设置页创建或编辑角色 | frontmatter + 人设正文;同时删除遗留的扁平文件 <rolesDir>/<id>.md |
| <rolesDir>/<id>/(目录) | 你在设置页删除角色 | 真删除(不进回收站);如果删掉的是默认角色,设置项也一并清空 |
| $DSH_HOME/persona-switcher/mascot/<roleId>-<stamp>.<ext> | 你在设置页导入立绘 | 上传的原始字节(png / jpeg / webp,解码后 ≤ 1 MB);每次上传生成一个带时间戳的新文件,旧文件清理掉,每个角色最多 3 个 —— 绝不放进角色目录 |
| $DSH_HOME/persona-switcher/mascot-map.json | 导入 / 清除立绘 | { "<roleId>": { "image": "<file>", "box": "WxH" } };文件损坏时按「没有角色有立绘」处理,绝不删除 |
| 你的配置档里的 cordis.patch.yml | /role default <id>、在 UI 里设置默认角色,或删除默认角色 | 间接:插件只调用宿主的设置接口,实际写入由宿主的 ConfigEditor 完成 |

其余都在内存里:一个提示词段落、一个 /role 命令、可选的 role_probe 工具、一个设置页区块、作用域限定在 .dsh-panel.ps-page 的 ps- 前缀样式、挂在 <body> 下的第二个 React 根(#dsh-persona-switcher-mascot-dock)承载立绘 —— 它读取宿主的 sidebarRight.mounted 快照来判断当前是哪个会话,从不写入该服务 —— 以及开启 announceRoleChange: true 时,会话内出现的一条 <system-reminder>Role change: … 用户消息。每次人设变更都会发一条 —— 角色换角色、部署人设换角色、/role none 退回部署人设,以及记住的角色从库里消失后交接给另一个 —— 而自动重绑若没改变会话角色,则保持静默。立绘的开关、尺寸、动画偏好和位置存在 localStorage 里,用本插件自己的命名空间。

唯一的网络面是入站:一条前缀路由挂在宿主已有的回环 webServer 上(routePrefix,默认 /persona-switcher),共八个端点(GET /roles、GET /role?id=、POST /role、DELETE /role?id=、GET /session?session=、POST /mascot?role=、GET /mascot-file/<file>、DELETE /mascot?role=;其他一律 404,请求体上限 1 MiB,base64 立绘上传为 2 MiB)。插件不开端口,也不跑自己的服务器。这条路由自身没有鉴权 —— 它依赖宿主 webServer 绑定在 127.0.0.1,所以请保持回环。见 SECURITY.md。role_probe 的 command 参数会通过真实命令管线执行斜杠命令,因此能跑该会话里注册的任何命令,不只是 /role;exposeTool: false 会彻底移除该工具。

需要 DSH >= 0.2.0-rc.1。MIT © 2026 yupaoa。
预览分享卡片长什么样 ↓yupaoa/dsh-persona-switcher
分享 · DeepSeek Harness Hub
yupaoa/dsh-persona-switcher

DeepSeek Harness 人设切换插件:会话中途只换人设与语言风格,不动工具、技能与模型

这是你的仓库?认领条目

回到顶部