DSH Plugins Marketplace

DSH Plugins

Plugins

/

Development & Infrastructure

/

dsh-story-party

m

dsh-story-party

Manifest valid★ 1

DSH 多人联机剧情房间:AI 主持、共享故事线,支持 Galgame / 海龟汤 / 剧本杀,可导入剧情包、子代理与地图。 Multiplayer story-party plugin for DeepSeek Harness — shared AI-driven rooms for galgame, turtle soup and mystery, with story packs, sub-agents and maps.

UI (client)hasBundlePatch

dsh-story-party

作者:B站:喵诺澪染 · 代码许可:MIT · 内置立绘:CC BY-NC-SA 4.0(非商业) · 开源免费,禁止倒卖

如果你是付费购买的,请立即申请退款并举报卖家——本项目从未授权任何人售卖。

联机 Galgame:房间 API + 房间独立 LLM 会话 + 广播 + 可直接打开的网页房间。 阶段 1(服务端)与阶段 2(房间页)已完成;阶段 3(与 galgame 舞台结合)未开始。 v0.2.0 起支持:剧本杀(角色本 + 私聊 + 阶段机)、海龟汤私有汤底、主代理自动点名子代理、舞台场景同步与选项投票、AI 画我猜(单人)。

房间服务(本插件自己的端口,默认 7302)
   ├─ 房间生命周期:建房 / 凭邀请令牌加入 / 职位 / 踢人 / 关闭
   ├─ 共享消息流:用户与 AI 的消息同一条 seq,SSE 广播 + ?since= 增量拉取
   ├─ 私聊:剧本杀里玩家 ↔ GM 的私密消息,第三方与 SSE 都收不到
   ├─ 发言调度:玩家/GM 发言防抖合并成一轮,AI 自己判断接不接话(<silent>)
   ├─ 角色本:每个角色一份 secret,只下发给扮演者与 GM
   ├─ 阶段机:阅读 → 自我介绍 → 搜证 → 讨论 → 投票 → 复盘
   ├─ 房间页:/room/<房号>?t=<邀请令牌>,浏览器直接进
   └─ 房间独立上下文:每个房间一份 system + 消息历史,sessionId = galgame-room:<房号>

截图

剧本杀角色本(玩家只能看到自己那份):

剧本杀角色本

GM 视角:能看全部角色本、指派角色:

GM 角色本

私聊只发给 GM,第三方看不到:

私聊

海龟汤汤底只有 GM 能打开:

汤底

个人本:章节条 + AI 建议选项,输入框仍然可用:

个人本房间

制作页的个人本设置:章节目标、关键选项、结局条件:

个人本制作

舞台:背景、立绘、字幕;两个浏览器不刷新同步:

舞台 GM

舞台玩家

内置「鲸鱼娘」:头像和舞台立绘都来自包内非商业素材(CC BY-NC-SA 4.0):

内置鲸鱼娘

个人资料页:统一换头像和昵称:

个人资料

AI 画我猜(单人):AI 用几何图形画,你来猜:

AI 画我猜

安装

方式一:GitHub(推荐,不用注册 npm)

DSH 桌面版:设置 → 插件 → 添加插件,填仓库地址:

https://github.com/miaonuo-lingran/dsh-story-party

或到仓库 Releases 下载 dsh-story-party-*.tgz,在「添加插件」里选本地文件安装。

方式二:npm(可选,发布后可用)

dsh plugin --profile desktop add dsh-story-party

方式三:本地目录(开发用)

path/to/dsh-story-party

署名与许可

  • 作者:B站:喵诺澪染
  • 代码许可:MIT —— 可以自由使用、修改、分发,但必须保留本署名
  • 内置立绘许可:CC BY-NC-SA 4.0 —— assets/characters/deepseek/portraits/neutral.webp 是第三方衍生素材,不是 MIT;仅限非商业使用,必须保留完整署名链,衍生同许可。 详见 NOTICE 与 LICENSE-ARTWORK。
  • 署名链:上善(鲸鱼娘形象)→ zipzip / ZipZipPipe(女仆二创)→ Small-tailqwq(maid-atelier 皮肤立绘)→ @lanxing/dsh-galgame(本插件直接取用) → 本插件。
  • 内置角色「鲸鱼娘」使用的就是这张立绘;如果不想要非商业素材,可以删除 assets/characters/ 并移除 src/room-server.js 里的 BUILTIN_CARDS,代码仍可正常使用。
  • 本项目开源且免费,从未授权任何人售卖;付费购买请申请退款并举报卖家
  • 署名出现在这些地方:
    • package.json 的 author、LICENSE、README
    • 三个网页的 <meta name="author"> 与页脚 / 房间水印
    • 所有 HTTP 响应头 X-Plugin-Author、/api/health 的 author
    • 剧情包 pack.json 的 generator / brand 字段

目录

src/room-server.js   房间核心(零 DSH 依赖,standalone 与插件共用同一份)
src/draw.js          AI 画我猜游戏核心(出题 / 判分 / 兜底题库)
src/zip.js           零依赖 zip 读取(stored / deflate,带上限与路径校验)
src/index.js         Host 插件:接 ctx.llm + resolveDshHome + cosplay 角色库
src/standalone.js    不启动 DSH 的直跑入口(mock 模型,用于 curl 自检)
public/room.html     房间页(阶段 2)
public/room.css      房间页样式(开房页 / 房间页 / 制作页 / 画我猜共用)
public/room.js       房间页逻辑(SSE + fetch)
public/host.html     开房页(创建 / 加入 + 人设卡 + 剧情包)
public/host.js       开房页逻辑
public/make.html     剧情包制作页
public/make.js       制作页逻辑 + 浏览器端零依赖 zip 打包
public/profile.html  个人资料页(头像 / 昵称)
public/profile.js    个人资料页逻辑(本机 localStorage)
public/draw.html     AI 画我猜页面(单人)
public/draw.js       画我猜页面逻辑(SVG 渲染 + 猜词)
client/client.js     DSH 浏览器半侧:「联机模式」标签页(iframe 嵌房间页)
assets/icon.svg      插件图标(插件管理里显示)
assets/characters/   内置「鲸鱼娘」立绘(CC BY-NC-SA 4.0,非 MIT;见 NOTICE)
LICENSE              代码 MIT 许可
LICENSE-ARTWORK      内置立绘的 CC BY-NC-SA 4.0 许可全文
NOTICE               立绘署名链与使用限制
scripts/acceptance.ps1   一键验收脚本
scripts/smoke-events.mjs 无浏览器的页面契约冒烟测试
scripts/card-test.mjs    cosplay 人设卡集成测试
scripts/pack-test.mjs    剧情包导入 / 建房 / zip-slip 测试
scripts/mystery-test.mjs 剧本杀:角色本 / 私聊 / 阶段机 / 真相不泄露
scripts/solo-test.mjs    个人本:solo 字段 / 主角 / 章节 / 选项剥离
scripts/save-test.mjs    个人本:存档 / 读档 / 回退 / 长期记忆
scripts/scene-test.mjs   舞台:素材路由 / scene 同步 / AI <scene> / 选项投票
scripts/draw-test.mjs    AI 画我猜:出题 / 猜词 / 计分 / 兜底题库
scripts/soup-test.mjs    海龟汤私有汤底:导入 / GM 接口 / 揭晓提示词
scripts/check-els.mjs    前端 els 引用检查
scripts/install-dsh.ps1 装进 profile 的脚本(复制式,备份 package.json)
cordis.patch.yml     插件挂载声明 + 默认配置

直接验收(不重启 DSH)

mock 模型只在「提问 / 叫旁白 / GM 推进」时接话,其余输出 <silent>,用来验证选择性发言。

cd dsh-story-party
$env:GALGAME_ROOM_PORT = '7302'
node src/standalone.js

另开一个窗口跑完整体验:

Invoke-Expression (Get-Content -Raw -Encoding UTF8 'dsh-story-party\scripts\acceptance.ps1')

也可以用 powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\acceptance.ps1。 脚本会把 curl.exe 的响应先写到文件、再按 UTF-8 读回来解析——Windows PowerShell 5.1 在中文系统上默认用 GBK 解码子进程输出,直接 curl | ConvertFrom-Json 会把中文 JSON 拆成乱码并报 ':' or '}' expected。

脚本会依次演示:建房 → 玩家加入 → 观众加入 → 带问句的发言(AI 接话)→ 观众发言(不进上下文) → 闲聊(AI 沉默)→ 拉历史 → GM 推进(强制开口)→ 增量拉取 → 成员列表 → SSE 实时广播。

房间页(阶段 2)

房主先开房:浏览器打开 http://<本机局域网 IP>:7302/。首屏是三个选项:

  • 创建房间:填房间名和昵称(GM),点创建时会先弹联网方式提示(可勾「不再提示」);页内「开放端口」一节可以改本机端口, 点「开放」后服务切到新端口、页面自动跟过去;下面是朋友可用的局域网地址;
  • 加入房间:把房主发来的邀请链接粘进去,直接进对方的房间(跨机器也行);
  • 自我游玩:单人房间,只有你和 AI;可选 cosplay 卡或剧情包(面板里也能直接导入 .zip),不展示邀请链接和参与者,别人 join 会被拒(403)。

建房后会自动以 GM 身份进入房间页;把「复制邀请链接」得到的地址发给朋友即可。

http://<这台电脑的局域网 IP>:7302/room/<房号>?t=<邀请令牌>
  • 进入先填昵称、选「玩家 / 观众」;成员令牌存浏览器 localStorage,刷新不用重进;
  • 消息流:昵称 + 职位徽章 + 时间;AI 的旁白用衬线字和左侧竖线区分;
  • 观众发言带「不进剧情」标记,永远不触发模型;
  • 右侧参与者列表:在线状态、职位;房主可以改职位、踢人;
  • 房主控制条:「推进剧情」跳过防抖强制 AI 开口;「关闭房间」结束这一局;
  • 「复制邀请链接」按当前地址拼链接——想给朋友用,就先用自己的局域网 IP 打开页面, 否则复制到的是 127.0.0.1;开房页会列出本机全部局域网地址。

两个窗口测:同一浏览器开两个窗口,一个用邀请链接以「玩家」进,另一个开无痕窗口 以「观众」进,互相发一句,就能同时看到对方和旁白的消息。

无浏览器冒烟测试(验证页面依赖的接口与 SSE 字段):

node scripts/smoke-events.mjs

接 cosplay 人设卡

开房页的「人设卡」下拉会自动列出 cosplay 插件里的角色库,以及一个内置的「鲸鱼娘」。 选中后:

  • AI 在这个房间里扮演这张卡(消息署名、旁白口吻都跟着卡走)
  • 卡的 description / personality / system_prompt / rules / behavior / mes_example 进 system prompt
  • 卡的 scenario 作为房间的世界观与开场设定(自己填了则以自己填的为准)
  • 卡的 first_mes 作为开场白,建房后直接出现在消息流里

数据来源:$DSH_HOME/profiles/<profile>/cordis.patch.yml 里 cosplay-core 的 config.roles,用 js-yaml 读取(跟随 profile 的依赖),结果缓存 15 秒—— 在 cosplay 里改完卡,稍等十几秒再建房就是新的。

注意:读取角色库是宿主侧代码,装/改完要**重载插件(开关关→开)**才生效, 只刷新页面不够。

无浏览器验证(注入一张假卡,验证建房、开场白、system prompt):

node scripts/card-test.mjs

剧情包(.zip)

发布者把「世界观 + 人物卡 + 背景素材」打成一个 zip,别人导入就能直接开房,类似 MC 整合包。

格式

story-pack.zip
├─ pack.json
├─ scenario.md
├─ characters/1.json
└─ assets/...(可选)

pack.json:

{
  "format": "dsh-galgame-pack",
  "formatVersion": 1,
  "name": "老式公寓的夏天",
  "version": "1.0.0",
  "author": "xxx",
  "description": "一句话简介",
  "mode": "roleplay",
  "scenario": "scenario.md",
  "truth": "汤底 / 完整真相(只有 GM 与 AI 能看到)",
  "solo": {
    "mode": "guided",
    "protagonist": "player",
    "premise": "你和学妹在旧公寓同居的第三个月。",
    "chapters": [
      {
        "id": "ch1",
        "title": "雨夜",
        "goal": "让小柚放下戒心",
        "choices": ["追问她为什么搬来", "先转移话题"]
      }
    ],
    "endings": [{ "id": "good", "title": "雨停了", "condition": "好感度达到 5,且聊过她的过去" }]
  },
  "characters": ["characters/1.json"],
  "assets": ["assets/bg.png"]
}

人物卡兼容酒馆 v2 字段:name / emoji / description / personality / scenario / first_mes / mes_example / system_prompt / rules / behavior。 另外支持:

  • secret:角色本 / 秘密,只发给扮演该角色的玩家与 GM;不会出现在公开消息、房间公开信息或剧情包详情里
  • playerBrief:玩家须知,领取角色后可见
  • playedBy:ai / human / either;剧本杀里人类角色由玩家领取,AI 角色进子代理名册
  • likes / favoritePlaces / relations:喜爱、常去地点、人物关系
  • solo:个人本设置,全部字段可选;mode 是 free / guided / scripted, protagonist 写人物 id(建议该人物标 playedBy: human 或 either), chapters[].choices 是作者写的关键选项(软建议,不锁自由输入)

建房时:包里的 scenario 当世界观,选中人物的卡当人设,first_mes 当开场白。 truth 只存在房主主机的房间 JSON 与系统提示词里;列表 / 详情 / 房间公开接口只返回 hasTruth。

制作

浏览器打开 http://<本机>:7302/make:填包名、世界观、人物卡,可选几张背景图, 然后「下载 .zip」或「装进本机」。打包在浏览器端完成(stored zip、零依赖); 装进本机后回开房页,在「角色」下拉里就能选。

制作页会按包类型只显示需要的字段:

  • 角色扮演 / 其他:显示「世界观 / 大纲」,不显示汤底
  • 海龟汤:显示「汤面 / 谜面」+「汤底(仅 GM 与 AI 可见)」
  • 剧本杀:显示「世界观 / 大纲」+「真相(仅 GM 与 AI 可见)」
  • 个人本设置是独立开关,勾选「这个包支持单人游玩」后才展开章节 / 结局

导入

开房页「剧情包」一栏选 .zip → 点「导入 .zip」,之后「角色」下拉会出现 「剧情包 · xxx(n 人)」分组。

边界

  • 只读数据,不执行任何东西;
  • 上传 ≤ 8MB、解压后 ≤ 24MB、条目 ≤ 200、单条 ≤ 6MB(防 zip 炸弹);
  • 拒绝加密条目、../ 与绝对路径(zip-slip);
  • 素材落到 $DSH_HOME/galgame-room/packs/<id>/assets/,阶段 3 舞台用。
  • 人物头像:制作页每个角色可上传本地图片,自动缩成 128×128 JPEG 存进人物卡 avatar; 导入后 AI 消息的头像用它,房主建房时就能看到。手写包也可以让 avatar 填 assets/xxx.png
  • 包类型:角色扮演 / 海龟汤 / 剧本杀 / 其他;制作页选好写进 pack.json 的 mode,建房后按类型给系统提示词追加规则(海龟汤只答是/不是/无关,剧本杀按阶段推进)
  • 海龟汤揭晓要全员投票:GM 点「发起揭晓投票」,所有人同意后才让 AI 揭底;观众也算一票
  • 地图:制作页「地图」区,地点按东南西北(含四角/中心)摆放,可写描述;地点之间可加关系(如「步行 10 分钟」)。支持层级:双击地点进入下一级(省 → 市 → 住处),面包屑返回;右键节点可「添加子地点 / 把选中的地点挂到这里(设为父级)/ 上移一级 / 移到顶层 / 按方位重置 / 删除」;节点可以拖动摆位,位置随包保存(pos);打包进 map.locations(带 parent)/ map.routes,房间系统提示带【地图】路径
  • 人物喜好:每人可写「喜爱」,并从地图地点里多选「常去地点」;子代理提示里会带「喜爱 / 常去」
  • 人物性别:制作页可选 女 / 男 / 其他,写进人物卡 gender;拼提示词时会写成「扮演『小柚』(女)」,模型不会把性别写反
  • 草稿:制作页自动存进浏览器 localStorage(800ms 防抖),下次打开自动恢复;素材文件不保存、需要重选;顶部有「保存草稿 / 清除草稿」
  • 建议人数:包级 recommendedPlayers {min,max},开房页分组标题会显示(如「建议 2-4 人」);勾「单人本」直接写成 {1,1}
  • 扮演方式:每个人物可标 ai / human / either;人类扮演可填「玩家须知」(playerBrief),入包保存
  • 开场说明:制作页可选填一段固定文案,建房后作为第一条消息,人物开场白和剧情排在它后面
  • 人物关系图:/make 里的可视化编辑——拖动人物摆位置,从人物右侧「+」拉线到另一个人物,在弹出框里写关系(可勾「双向」);打包时按出边 / 双向边写进人物卡 relations,房间的【人物关系】段落会喂给模型

个人本(单人游玩)

开房页的「自我游玩」就是个人本入口:单人房间,只有你和 AI,不生成邀请链接,别人进不来。

  • 如果剧情包写了 solo.protagonist,该角色由你扮演,AI 当旁白并扮演其他角色; 没写就是第一人称「你」
  • AI 每轮可以给 2–4 个建议选项,用 <choices>...</choices> 包起来;服务端会把标记从正文剥掉, 存进 room.story.choices,房间页渲染成选项 chips
  • 选项是软建议:输入框一直可用,玩家可以不选、自己写动作
  • 作者写的章节目标、关键选项、结局条件会进系统提示词,作为软引导; mode 为 free / guided / scripted 时引导强度不同
  • 制作页「个人本设置」可以启用单人模式、选主角、写前提、添加章节(标题/目标/关键选项)和结局
  • 房间公开信息里带 story:章节进度、当前选项、主角;SSE 增加 story 事件

当前进度:选项、章节、主角、结局提示、自动/手动存档、回退一步、长期记忆已经通了; 独立的 /play 视觉小说页、结局收集、多存档导出还在后续版本。

存档面板(自动存档每回合更新,手动最多 5 个):

个人本存档

长期记忆(AI 提议更新,也可以手动改):

个人本记忆

个人本存档 / 回滚 / 长期记忆

  • 自动存档:每轮 AI 开口前留一份快照,包含消息、story、舞台和摘要; 「回退一步」就是回到 AI 开口之前
  • 手动存档:最多 5 个,带名字 / 回合 / 章节 / 消息数 / 时间;读档整份恢复
  • 删除存档:手动存档可删;自动存档只保留最新一份
  • 长期记忆:room.story.memory 分四类——事实、人物关系、好感、物品。 AI 在回复末尾可以输出:
<memory>{"facts":["小柚住在隔壁"],"relations":{"小柚":"开始信任你"},"affection":{"小柚":1},"items":["旧钥匙"]}</memory>

服务端会剥掉标记、合并进记忆(事实/物品去重,好感限制在 -100..100),再从系统提示词喂给下一轮。 也可以在房间右上角「记忆」里手动编辑。

相关接口:

方法路径鉴权说明
GET / POST/api/rooms/:id/saves成员存档列表 / 新建手动存档({name})
POST/api/rooms/:id/saves/:saveId/load成员读档(saveId=auto 读自动存档)
DELETE/api/rooms/:id/saves/:saveId成员删除手动存档
POST/api/rooms/:id/rewind成员回退一步(回到自动存档)
GET / PATCH/api/rooms/:id/memory成员读取 / 覆盖长期记忆

SSE 增加 restore(读档/回退后整份重渲染)与 saves 事件。

舞台与选项投票

房间页顶部可以显示一块 galgame 舞台:

  • scene.background:背景图,只能来自剧情包 assets/
  • scene.sprites:立绘列表,{ characterId, pos: left|center|right, emotion }
  • scene.bgm:BGM,只能来自剧情包 assets/ 里的音频
  • scene.caption:舞台字幕

两种改法:

  1. GM 侧栏「设置舞台」:选背景 / BGM、加立绘、写字幕,保存后 SSE 广播 scene, 所有客户端不刷新就跟过去
  2. AI 在回复里输出:
<scene>{"background":"assets/bg.png","sprites":[{"characterId":"yuzu","pos":"left","emotion":"smile"}],"bgm":"assets/rain.mp3","caption":"雨夜。"}</scene>

服务端会剥掉标记、校验素材与角色,再广播给所有人。素材路由:

GET /pack-assets/<packId>/<filename>

只提供 png/jpeg/webp/gif 与 mp3/ogg/wav/m4a/aac,拒绝 ../、绝对路径和 svg。

选项投票:

  • GM 点「发起选项投票」,每行一个选项(2–6 个),所有人各投一票
  • 所有人投完后按票数继续,平票取第一个
  • 结果会发一条系统消息,并把「玩家选择了 X」写进 AI 下一轮提示词
  • 舞台和选项都走 SSE,两个浏览器不需要刷新

相关接口:

方法路径鉴权说明
GET / POST/api/rooms/:id/scene邀请或成员 / GM读取 / 设置舞台
POST/api/rooms/:id/choice成员 / GM选项投票:start / cast / cancel
GET/pack-assets/:packId/:file无剧情包素材(图片 / 音频)

BGM 受浏览器自动播放限制,第一次需要玩家点一下「BGM 播放」。

AI 画我猜(单人)

开房页的「🎨 AI 画我猜(单人)」或直接打开 /draw:

  • AI 想一个常见中文词,用最多 24 个几何图形画出来,再给一句不直接说出答案的提示
  • 玩家看图猜词;猜错可以继续,猜对按尝试次数得分
  • 五题一局;有「提示字数 / 跳过 / 下一题 / 重新开始」
  • 每轮只调用一次模型;模型返回坏 JSON 时自动用内置题库兜底,游戏不会卡死

图形协议(只允许这些,避免文字泄题和 SVG 注入):

{
  "answer": "雨伞",
  "aliases": ["伞"],
  "hint": "下雨天会用到",
  "shapes": [
    { "type": "polygon", "points": [[50,12],[16,46],[84,46]], "fill": "#e05a5a" },
    { "type": "line", "x1": 50, "y1": 46, "x2": 50, "y2": 88, "stroke": "#6b4f2a", "strokeWidth": 3 }
  ]
}

允许 circle / ellipse / rect / line / polyline / polygon,坐标 0–100;text / image / path 一律丢弃。答案只在猜对或跳过后才下发。

相关接口:

方法路径说明
POST/api/draw/sessions新建一局({rounds},默认 5)
GET/api/draw/sessions/:id当前状态(不含未揭晓答案)
POST/api/draw/sessions/:id/next下一题
POST/api/draw/sessions/:id/guess猜词({text})
POST/api/draw/sessions/:id/skip跳过并揭晓
POST/api/draw/sessions/:id/hint提示字数 / 首字

会话存在内存里,重启房间服务会清空;它是单人小游戏,不做跨设备同步。

房间密码(可选)

建房时可以设一个密码,进房的人必须输对才能进:

  • 密码只存 scrypt 哈希(salt + hash),房间 JSON 里没有明文
  • 公开信息只暴露 hasPassword,不返回任何哈希
  • 密码不进邀请链接,单独告诉朋友即可
  • GM 可以随时改或清空:PATCH /api/rooms/:id {"password":"新密码"},传空串 = 取消密码

个人资料(头像 / 昵称)

开房页顶部「个人资料」进入 /profile:

  • 上传头像(自动缩成 128×128 JPEG)、填写昵称,实时预览
  • 资料只存在这台浏览器的 localStorage,不会上传到房主机器
  • 创建房间 / 自我游玩会预填昵称,并把头像带到 GM 身份
  • 从邀请链接加入时,昵称预填、头像随 join 一起提交;房主那边能看到
  • 房间右上角仍然可以单独换「这一局」的头像,个人资料只是默认值

注意:换浏览器 / 清站点数据 = 个人资料丢失;它不是账号,也不做跨设备同步。

头像与玩家身份

  • 房间右上角「头像」→ 选本地图片;前端缩到 128×128 JPEG 再上传(PNG/JPEG/WebP)
  • 头像存在房间存档的成员资料里,所有人在消息流和参与者列表都能看到
  • 每个成员有 uid(u_ + 10 位十六进制),进房时生成
  • 同时带一个机器码(浏览器指纹哈希,存 localStorage):同一台机器再次进同一间房, 会沿用原来的 uid 和头像;identities.json 记这层绑定
  • 注意:机器码只是「认回身份」的便利,不是安全边界;清站点数据 / 换浏览器就是新身份

记录 AI 思维链(调试,默认关)

开房页的「记录 AI 思维链」勾选框,由房主控制:

  • 开启时:模型不再强制 reasoning=off,思维链留在房间 JSON 的 debug 字段(最近 20 条)
  • 只存在房主本机:不进 messages / 房间公开信息 / SSE,玩家拿不到
  • 会多花 token(reasoning 也计费),只在排查「为什么沉默 / 为什么接话」时开
  • 通过 GET/POST /api/settings {"saveReasoning": true} 控制,选择存 settings.json

子代理(剧情包多角色)

剧情包里除主角外的 AI 角色会自动进房间名册(room.agents),每个角色一个子代理:

  • 名册写进系统提示:【你的子代理】列出每个角色、是否在场,并明确「不要替她写台词」
  • GM 控制条的「让子代理说话」→ POST /api/rooms/:id/speak
  • 调用流程:子代理只读自己的文档 + 摘要 + 上次之后的新消息,输出意图要点 → 主代理把它写成最终消息,署名该子代理
  • 每个子代理还记自己的位置 location 和接下来的打算 plan:子代理输出一行 JSON(line / location / plan),主代理在名册里能看到「她在哪、打算去哪」,用来决定该唤起谁
  • 位置和打算只存在子代理状态里(存档可查),不进公共消息流;主代理是上帝视角,名册里能看到全部代理的位置\n- 子代理的思考与思维链都不进消息流、不共享;人类扮演的角色不进名册

主代理自动点名子代理

剧情包里的 AI 角色除了 GM 手动「让她接话」,主代理也可以自己点名:

  • 系统提示词会告诉主代理:需要子代理开口时,在回复最后单独一行写 <speak 角色名>
  • 服务端把标记从正文里剥掉(玩家看不到),解析到角色名后自动走一轮 「子代理出意图 → 主代理定稿」,消息署名该子代理
  • 每轮最多自动点名一次,避免模型互相点名形成循环
  • 角色名可以写子代理的名字或 id;写错/找不到就只剥掉标记,不会报错
  • GM 手动点名的 /speak 仍然可用

剧本杀:角色本 / 私聊 / 阶段机

剧情包 mode = "mystery" 时,建房会自动进入剧本杀模式:

  • 角色本:人物卡的 secret 只下发给扮演者与 GM;玩家领取角色后点右上角「角色本」查看。 一个成员同时只能持有一个角色;GM 可以在「角色本」弹窗里指派 / 释放,观众不能领取。
  • 私聊:输入框左边的目标下拉框,玩家只能私聊 GM,GM 可以私聊任意成员。 私聊消息只有发送者、接收者与 GM 能看到:历史接口按身份过滤,SSE 只推给相关连接; 私聊不进 AI 上下文,不会被剧情复述。
  • 阶段机:阅读 → 自我介绍 → 搜证 → 讨论 → 投票 → 复盘。房间页顶部显示当前阶段, GM 点「下一阶段」推进;每次切换会发一条系统消息,并让 AI 用一句话交代阶段目标。 复盘阶段会把包里的 truth(完整真相)拼进系统提示词,但公开接口仍然不返回它。
  • 如果建房时选中的人类角色只是玩家位,AI 不会把这个角色变成自己的化身,而是当 GM / 旁白; 选中的是 AI 角色时,AI 仍然扮演它。

相关接口:

方法路径鉴权说明
GET/api/rooms/:id/roles成员角色名单 + 自己的角色本(GM 看全部)
POST/api/rooms/:id/roles成员 / GMclaim 领取 / assign 指派 / release 释放
POST/api/rooms/:id/phaseGMnext 下一阶段 / set 指定阶段(narrate:false 不触发 AI)
GET/api/rooms/:id/truthGM读取汤底 / 真相正文
POST/api/rooms/:id/messages成员带 to: <成员id> 时发私聊

海龟汤私有汤底

制作页的「汤底 / 真相」一栏会写进 pack.json 的 truth(也可以放 truth.md):

  • AI 在系统提示词里能看到汤底,所以「是 / 不是 / 无关」答得准;
  • 但公开列表、详情、房间公开信息都只返回 hasTruth,玩家读不到正文;
  • 房主 / GM 在房间侧栏点「查看汤底 / 真相」才看得到;
  • 全员「揭晓投票」通过后,提示词会带上汤底原文,并要求 AI 按原文完整揭晓;
  • 揭晓之后公开接口仍然不返回汤底,避免玩家翻接口作弊。

长局自动摘要

开房页的「长局自动摘要」默认开启:每攒够 max(20, 读取条数) 条未摘要的消息,就让模型压成一段 300 字以内的摘要,存在房间的 summary 字段里。之后发给模型的是「摘要 + 最近 N 条」,比每次重发全量便宜,也不会丢早期剧情。房主可以关掉它。

继续上次没玩完的局

房间数据一直在房主机器上($DSH_HOME/galgame-room/rooms/),DSH 重启也不丢; AI 的上下文会从最近 contextMessages 条消息重建。

开房页「继续上次没玩完的局」会列出本机开过的房间:

  • 房间名 / 房号 / 消息数 / 人数 / 是否已关闭
  • 点「继续」= 用本机保存的邀请链接回到原房间(GM 身份还在)
  • 已关闭的房间可以「重新开放」(需要在本浏览器开过这间房,GM 令牌存在 localStorage)
  • 每行右侧「编辑」= 改房间名 + 整机的端口 / 公网地址(端口改完页面会跳到新地址);「删除」= 永久删除(房间 JSON 和消息一起清掉,不能恢复)
  • 删除/关闭/踢人用的是页面内确认框(public/dialog.js),不再弹 Electron 原生 confirm

玩家那边不用做任何事:本地副本先显示,再和房主对齐。

连接到房主的房间(版本一致)

「联机模式」标签页默认显示本机房间(自己开房)。要进别人的房间:

  1. 房主在房间里点「复制邀请链接」发给你(弹窗里可以选公网 / 局域网 / 本机)
  2. 你在页面里切到「加入房间」,粘贴链接 → 点「进入房间」
  3. 如果这页是从 DSH 标签页打开的(地址带 ?v=本机版本),加入前会先请求对方 GET /api/version,版本必须与本机一致;不一致直接提示,不会跳转
  4. 加入后你看到的消息、参与者、剧情包全部来自房主的主机, 你这边只是显示;身份存在对方主机那一侧的浏览器存储里

标签页顶部只保留:本机插件版本 + 「回本机」(加入房间不再重复做一份)。

会话绑定与本地副本

  • 从「联机模式」标签页建房时,地址里的 ?sid= 会把当前 DSH 会话 id 记进房间 (room.sessionId,宿主侧可见)
  • 玩家端会在浏览器里留一份本地副本(localStorage,最近 200 条消息 + 会话 id + seq)
  • 打开房间时先用本地副本渲染,再和房主对齐:
    • 会话 id 一致 → 直接补增量
    • 会话 id 不同 → 提示「房主换了会话」,用房主整份覆盖本地副本
  • 房主随时可以「关闭房间」;关闭后玩家再打开会看到「房间已关闭」

版本门禁两种力度:

  • 客户端校验(默认):标签页里连接时才比对;朋友直接用浏览器开链接不受限
  • 服务端强制:cordis.patch.yml 里 strictVersion: true,进房必须携带 clientVersion 且与房主一致,浏览器直连会被拒(409)

这个比对只挡「版本不一致导致的接口差异」,不是安全边界。

走公网(樱花 frp)

  1. 樱花控制台建隧道,本地地址填 127.0.0.1:7302:
    • HTTP(S) 隧道(推荐):拿 https://xxx.sakurafrp.com 域名,自动 HTTPS
    • TCP 隧道:拿 frp-boy.com:56804 这种「域名:端口」,没有 https
  2. 开房页的「公网地址」填进去,点保存(存 $DSH_HOME/galgame-room/publicUrl.json, 立即生效,不用重载插件);也可以写在插件配置 publicUrl 里
  3. 房主正常开房,点「复制邀请链接」就会优先用公网地址
  4. 朋友打开链接即可;TLS 在樱花那一层终止,本地仍是 http

地址稳定性:隧道「停止→启动」通常保留原端口;删除再新建会重新分配(TCP 的 自动端口会变)。地址变了就回到开房页改「公网地址」,旧邀请链接随之作废、重新复制一份。

API

鉴权统一走 Authorization: Bearer <令牌> 或 ?t=<令牌>:

  • 邀请令牌:建房时返回,用来加入房间、读房间与历史;
  • 成员令牌:加入时返回,用来发言、推进、管理,身份与 DSH 账号解耦。
方法路径鉴权说明
GET/无开房页(创建 / 加入 两种模式)
GET/api/health无存活检查
GET/api/version无插件版本 + 协议版本 + 是否强制版本校验
GET / POST/api/settings无房主侧调试开关(saveReasoning)
POST/api/public-url无运行时改公网地址(存 publicUrl.json)
GET/api/network无本机局域网地址(开房页展示用)
GET/api/cards无cosplay 角色库(开房页人设卡下拉)
GET/api/packs无已导入的剧情包列表
POST/api/packs无上传剧情包(body = zip 原始字节)
GET/api/packs/:id无剧情包详情(不含 truth / secret / playerBrief)
DELETE/api/packs/:id无删除剧情包
GET/make无剧情包制作页
POST/api/port无开放 / 切换本机端口({port},失败保持原端口)
GET/room/:id无房间页(页面内再要邀请令牌)
GET/room-assets/room.css | room.js | host.js无页面样式与脚本
POST/api/rooms无建房,返回房号 / 邀请令牌 / GM 令牌
GET/api/rooms无房间列表(不含令牌与消息)
GET/api/rooms/:id邀请或成员房间公开信息
PATCH/api/rooms/:idGM改标题 / 世界观设定
DELETE/api/rooms/:idGM关闭房间
POST/api/rooms/:id/join邀请加入(role = player / spectator)
GET/api/rooms/:id/members邀请或成员成员列表
PATCH/api/rooms/:id/members/:midGM改职位(player / spectator)
DELETE/api/rooms/:id/members/:midGM踢人
POST/api/rooms/:id/messages成员发言;带 to: <成员id> = 私聊(观众只进消息流、不进 AI 上下文)
GET/api/rooms/:id/messages?since=0邀请或成员增量拉取历史(私聊按身份过滤)
POST/api/rooms/:id/advanceGM强制 AI 推进一轮
POST/api/rooms/:id/speakGM点名子代理接话({agentId})
GET/api/rooms/:id/roles成员角色名单 + 自己的角色本(GM 看全部)
POST/api/rooms/:id/roles成员/GMclaim / assign / release
POST/api/rooms/:id/phaseGM剧本杀阶段机:next / set
GET/api/rooms/:id/truthGM汤底 / 真相正文
GET / POST/api/rooms/:id/scene邀请或成员 / GM读取 / 设置舞台(背景、立绘、BGM、字幕)
POST/api/rooms/:id/choice成员 / GM选项投票:start / cast / cancel
GET / POST/api/rooms/:id/saves成员个人本存档列表 / 新建存档
POST/api/rooms/:id/saves/:saveId/load成员个人本读档
DELETE/api/rooms/:id/saves/:saveId成员删除手动存档
POST/api/rooms/:id/rewind成员个人本回退一步
GET / PATCH/api/rooms/:id/memory成员个人本长期记忆
POST/api/draw/sessions无新建一局 AI 画我猜
GET/api/draw/sessions/:id无画我猜当前状态(不含未揭晓答案)
POST/api/draw/sessions/:id/next无下一题
POST/api/draw/sessions/:id/guess无猜词
POST/api/draw/sessions/:id/skip无跳过并揭晓
POST/api/draw/sessions/:id/hint无提示字数 / 首字
GET/pack-assets/:packId/:file无剧情包图片 / 音频素材
POST/api/rooms/:id/avatar成员上传自己的头像(内联图片 data URL)
POST/api/rooms/:id/vote成员/GM投票:start / cast / cancel(全员同意才通过)
POST/api/rooms/:id/purgeGM 或本机永久删除房间(关闭只是打标记)
POST/api/rooms/:id/leave成员主动离开
GET/api/rooms/:id/events?since=0邀请或成员SSE:snapshot / message / members / ai / system / vote / room / phase / roles / story / scene / choice / restore / saves

最小 curl 流程

$base = 'http://127.0.0.1:7302'

# 1. 建房(房主即 GM)
curl.exe -s -X POST "$base/api/rooms" -H 'content-type: application/json' -d '{"title":"天台上的黄昏","gmName":"澪"}'
# → 201 { room, inviteToken, gm:{memberId,token}, joinPath }

# 2. 玩家加入(把上一步的 inviteToken 填进来)
curl.exe -s -X POST "$base/api/rooms/<房号>/join" -H 'content-type: application/json' -d '{"name":"小满","inviteToken":"<邀请令牌>"}'
# → 200 { memberId, token, role, room }

# 3. 发言(用第 2 步拿到的 token)
curl.exe -s -X POST "$base/api/rooms/<房号>/messages" -H "authorization: Bearer <成员令牌>" -H 'content-type: application/json' -d '{"text":"门后面好像有脚步声,要不要进去看看?"}'
# → 202 { message, seq };约 debounceMs 后 AI 会自己判断接不接话

# 4. 拉历史(新客户端进房)
curl.exe -s "$base/api/rooms/<房号>/messages?since=0&t=<邀请令牌>"

# 5. GM 推进(强制 AI 开口)
curl.exe -s -X POST "$base/api/rooms/<房号>/advance" -H "authorization: Bearer <GM令牌>"

# 6. 实时广播(另开一个窗口,边发边看)
curl.exe -N "$base/api/rooms/<房号>/events?t=<邀请令牌>"

选择性发言怎么定的

  1. 玩家 / GM 的发言进队列,观众不触发模型;
  2. debounceMs(默认 1200ms)内的发言合并成一轮,避免一句一回;
  3. 一轮交给模型时,system 里写明「闲聊就只输出 <silent>,剧情需要推进才回应」;
  4. 模型输出 <silent>(或空)→ 不产生消息,但这批发言标记为已读,不会反复重发;
  5. GM 调用 /advance → 跳过防抖、强制要求「立即推进剧情」。

DSH 标签页「联机模式」

浏览器半侧(client/client.js)在会话视图里注册了第四个标签:

  • conversation.view,id: galgame-room,order: 30 → 排在「对话 / 轨迹 / galgame 模式」最右边
  • 内容是一个全高 iframe,地址由宿主路由 /api/galgame-room/config 返回的端口决定
  • 标签内部就是开房页 / 房间页,身份与 SSE 都在房间服务这一侧,不碰 DSH 会话

注意:

  • client 半侧只在 DSH 启动时加载,装完或改完要重启 DSH 才会出现;
  • 换端口后标签页会读新端口(配置路由),但已经渲染的 iframe 要刷新页面才跟过去;
  • 房间服务没监听时标签页是空白,先用 curl http://127.0.0.1:7302/api/health 确认。

装进 DSH

# 会先备份 profile/package.json,再复制插件并登记到 bundles
powershell -NoProfile -ExecutionPolicy Bypass -File 'dsh-story-party\scripts\install-dsh.ps1'

然后重启 DeepSeek Harness。启动日志里应出现:

[galgame-room] 房间服务已监听 0.0.0.0:7302,入口 POST /api/rooms

首次监听如果弹出 Windows 防火墙,请勾选「专用网络」允许,朋友才能连进来。 局域网地址形如 http://192.168.x.x:7302。

打包(.tgz 本地导入包)

cd dsh-story-party
$env:npm_config_cache = 'dsh-story-party\.npm-cache'   # 可选:避免写系统 npm 缓存
npm pack --pack-destination .

产物:.\dsh-story-party-0.2.0.tgz,共 54 个文件 (src/、public/、assets/、scripts/、cordis.patch.yml、README.md、package.json)。

包内已声明:

  • dsh.bundle.patch = ./cordis.patch.yml(插件管理器据此识别为可挂载的 bundle)
  • engines.dsh = ">=0.2.1-rc.2 <0.3.0"(版本兼容以它为准,不再比 peer 范围)
  • dsh.compatibility.dshReleases(市场兼容表)

导入方式二选一:

  1. 插件管理 → 安装插件,在安装框里填本地包路径 .\dsh-story-party-0.2.0.tgz(插件管理器支持 registry / path / git / tarball 四类来源),装完重启 DSH;

  2. 命令行(插件管理器不可用时):

    cd $env:USERPROFILE\.dsh\profiles\desktop
    pnpm add file:F:/超分/.dshplugin/dsh-story-party-0.2.0.tgz
    

    然后把 dsh-story-party 加进 package.json 的 dsh.profile.bundles,重启 DSH。

也可以用不联网的复制式安装:scripts/install-dsh.ps1(直接复制到 profile 的 node_modules 并登记 bundles)。

配置项(cordis.patch.yml 的 config)

键默认说明
port7302房间服务端口(7301 在本机被系统保留,故用 7302);开房页「开放端口」改过的值会存进 $DSH_HOME/galgame-room/port.json,优先级高于这里
hostname0.0.0.0监听地址;只给本机用可改 127.0.0.1
publicUrl空公网地址;开房页里保存的值(publicUrl.json)优先于这里,留空则用页面当前 origin
maxPlayers4GM + 玩家 的座位数(观众另算)
debounceMs1200发言合并窗口
contextMessages30交给模型的历史消息条数(滑动窗口);0 = 全部历史(每轮重发,费 token);建房时可单独覆盖
aiName旁白AI 在消息流里的名字
strictVersionfalsetrue = 进房必须带 clientVersion 且与房主插件版本一致(浏览器直连会被拒)
provider / model空留空跟随当前对话模型;填了优先用

测试清单(不需要浏览器)

cd dsh-story-party

# 纯前端 / 契约
node scripts/check-els.mjs
node scripts/client-sim.mjs

# 服务端(各自起临时端口,跑完自动清理)
$env:CARD_TEST_PORT=7341;      node scripts/card-test.mjs
$env:PACK_TEST_PORT=7342;      node scripts/pack-test.mjs
$env:VERSION_TEST_PORT=7343;   node scripts/version-test.mjs
$env:PASSWORD_TEST_PORT=7344;  node scripts/password-test.mjs
$env:AVATAR_TEST_PORT=7345;    node scripts/avatar-test.mjs
$env:VOTE_TEST_PORT=7390;      node scripts/vote-test.mjs
$env:AGENTS_TEST_PORT=7348;    node scripts/agents-test.mjs
$env:CONTEXT_TEST_PORT=7349;   node scripts/context-test.mjs
$env:SUMMARY_TEST_PORT=7350;   node scripts/summary-test.mjs
$env:REASONING_TEST_PORT=7351; node scripts/reasoning-test.mjs
$env:SOLO_TEST_PORT=7378;      node scripts/solo-test.mjs
$env:SAVE_TEST_PORT=7386;      node scripts/save-test.mjs
$env:SCENE_TEST_PORT=7382;     node scripts/scene-test.mjs
$env:DRAW_TEST_PORT=7396;      node scripts/draw-test.mjs
$env:MYSTERY_TEST_PORT=7372;   node scripts/mystery-test.mjs
$env:SOUP_TEST_PORT=7374;      node scripts/soup-test.mjs
$env:HOSTSIM_PORT=7360;        node scripts/host-sim.mjs

smoke-events.mjs 需要先起 standalone:

$env:GALGAME_ROOM_PORT=7340
$env:GALGAME_ROOM_DATA='.smoketest\rooms'
node src/standalone.js
# 另开一个窗口:
$env:GALGAME_ROOM_BASE='http://127.0.0.1:7340'; node scripts/smoke-events.mjs

真模型链路(对着已装进 DSH 的插件,默认 7302):

$env:GALGAME_ROOM_BASE='http://127.0.0.1:7302'; node scripts/live-check.mjs

已知限制

  • standalone 的模型是 mock,只用于 API 自检;真实模型要装进 DSH 后走 ctx.llm;
  • 房间在内存里,落盘是每房一个 JSON;进程重启后房间会从文件恢复,SSE 客户端需重连;
  • 没有 TLS、没有限流之外的防护,当前只适合局域网;公网方案(frp / 反代)在阶段 3;
  • 观众能看到全部消息,但发言不进 AI 上下文——这是「观众位永不触发 LLM」的落点;
  • 消息限流按 IP:默认 10 条/秒,超出返回 429;
  • 房间页一次渲染全部历史,几百条以内没问题,再长要做虚拟滚动;
  • 同一局域网里谁都能打开 / 自己建房;公网上线前要先加访问控制(阶段 3);
  • 私聊的 UI 只在剧本杀模式出现;服务端本身支持任意房间发 to,但其他模式默认不显示目标下拉框;
  • 角色本、汤底 / 真相在房主主机的房间 JSON 里是明文(只存在本机),不是端到端加密;
  • 剧本杀阶段机目前是 GM 手动推进,没有计时器、自动搜证或玩家投票锁;
  • 个人本目前是「单人房 + 选项 chips + 存档/记忆」,独立的视觉小说页、结局收集、多存档导出尚未实现;
  • 舞台目前是房间页顶部的一块区域,不是独立视觉小说页;立绘没有入场/切换动画,也没有语音;
  • BGM 受浏览器自动播放限制,需要玩家第一次手动点播放;
  • 选项投票是所有成员(含观众)各一票,暂不支持按职位加权或 GM 一票否决;
  • AI 画我猜的图是几何图形,不是 AI 生图;每轮一次模型调用,坏 JSON 走内置题库兜底; 游戏会话存在内存里,重启房间服务会清空;
  • 插件市场尚未提交:市场条目需要你确认后再走 issue 流程;npm 上仍是 0.1.48,0.2.0 需要更新发布 token 后再发。

阶段 3 预留

  • 与 galgame 舞台结合:立绘 / 对话框复用,房间只做同步;
  • 选项投票与舞台跟随(一方选、另一方看到同一段演出);
  • room.scenario 就是舞台要渲染的开场设定,将来可从 cosplay 角色卡导入。

Comments

Loading…

From the same category

awesome-dsh-plugin

by awesome-dsh-plugin

A curated list of plugins for DeepSeek Harness (dsh) · DeepSeek Harness 插件精选列表

Development & Infrastructure

★ 20.4k

CC0-1.0

Python

Oct 23, 2026

Index only — not installable

by 0xsline

DeepSeek Harness (DSH) ecosystem: curated plugins, tools, and infrastructure from dsh-external/hub and the public dsh-plugin topic.

Development & Infrastructure

★ 1.3k

CC0-1.0

Python

Oct 16, 2026

Index only — not installable

by pax-beehive

Open-source CLI, schemas, resolver, and DSH agent tools for DSH Plugin Hub

Development & Infrastructure

★ 277

MIT

TypeScript

Oct 20, 2026

Index only — not installable

by xiajiajun516

DeepSeek Harness (DSH) backup & restore plugin — export, import, migrate and sync your complete DSH configuration, plugins, MCP servers, skills and workspace. One-click migration to another machine.

Development & InfrastructureManifest valid

★ 230

MIT

TypeScript

Oct 23, 2026

dsh plugin --profile web add dsh-config-manager

by yjh051108

推荐组件(非必须):DeepSeek Harness 运行时注入器;已随 dsh-routing-suite 单仓库化保留,本仓库继续维护/发布。

Development & InfrastructureManifest valid

★ 166

TypeScript

Oct 16, 2026

dsh plugin --profile web add @dsh-external/dsh-super-injector

by jigjoy-ai

A CLI that turns a goal into a pull request - and a sandbox for testing concurrent AI coding agents on the Mozaik runtime.

Development & Infrastructure

★ 125

MIT

TypeScript

Oct 22, 2026

Index only — not installable