dsh-story-party
Manifest valid★ 1DSH 多人联机剧情房间: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.
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 能打开:

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

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

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


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

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

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:舞台字幕
两种改法:
- GM 侧栏「设置舞台」:选背景 / BGM、加立绘、写字幕,保存后 SSE 广播
scene, 所有客户端不刷新就跟过去 - 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 | 成员 / GM | claim 领取 / assign 指派 / release 释放 |
| POST | /api/rooms/:id/phase | GM | next 下一阶段 / set 指定阶段(narrate:false 不触发 AI) |
| GET | /api/rooms/:id/truth | GM | 读取汤底 / 真相正文 |
| 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
玩家那边不用做任何事:本地副本先显示,再和房主对齐。
连接到房主的房间(版本一致)
「联机模式」标签页默认显示本机房间(自己开房)。要进别人的房间:
- 房主在房间里点「复制邀请链接」发给你(弹窗里可以选公网 / 局域网 / 本机)
- 你在页面里切到「加入房间」,粘贴链接 → 点「进入房间」
- 如果这页是从 DSH 标签页打开的(地址带
?v=本机版本),加入前会先请求对方GET /api/version,版本必须与本机一致;不一致直接提示,不会跳转 - 加入后你看到的消息、参与者、剧情包全部来自房主的主机, 你这边只是显示;身份存在对方主机那一侧的浏览器存储里
标签页顶部只保留:本机插件版本 + 「回本机」(加入房间不再重复做一份)。
会话绑定与本地副本
- 从「联机模式」标签页建房时,地址里的
?sid=会把当前 DSH 会话 id 记进房间 (room.sessionId,宿主侧可见) - 玩家端会在浏览器里留一份本地副本(
localStorage,最近 200 条消息 + 会话 id + seq) - 打开房间时先用本地副本渲染,再和房主对齐:
- 会话 id 一致 → 直接补增量
- 会话 id 不同 → 提示「房主换了会话」,用房主整份覆盖本地副本
- 房主随时可以「关闭房间」;关闭后玩家再打开会看到「房间已关闭」
版本门禁两种力度:
- 客户端校验(默认):标签页里连接时才比对;朋友直接用浏览器开链接不受限
- 服务端强制:
cordis.patch.yml里strictVersion: true,进房必须携带clientVersion且与房主一致,浏览器直连会被拒(409)
这个比对只挡「版本不一致导致的接口差异」,不是安全边界。
走公网(樱花 frp)
- 樱花控制台建隧道,本地地址填
127.0.0.1:7302:- HTTP(S) 隧道(推荐):拿
https://xxx.sakurafrp.com域名,自动 HTTPS - TCP 隧道:拿
frp-boy.com:56804这种「域名:端口」,没有 https
- HTTP(S) 隧道(推荐):拿
- 开房页的「公网地址」填进去,点保存(存
$DSH_HOME/galgame-room/publicUrl.json, 立即生效,不用重载插件);也可以写在插件配置publicUrl里 - 房主正常开房,点「复制邀请链接」就会优先用公网地址
- 朋友打开链接即可;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/:id | GM | 改标题 / 世界观设定 |
| DELETE | /api/rooms/:id | GM | 关闭房间 |
| POST | /api/rooms/:id/join | 邀请 | 加入(role = player / spectator) |
| GET | /api/rooms/:id/members | 邀请或成员 | 成员列表 |
| PATCH | /api/rooms/:id/members/:mid | GM | 改职位(player / spectator) |
| DELETE | /api/rooms/:id/members/:mid | GM | 踢人 |
| POST | /api/rooms/:id/messages | 成员 | 发言;带 to: <成员id> = 私聊(观众只进消息流、不进 AI 上下文) |
| GET | /api/rooms/:id/messages?since=0 | 邀请或成员 | 增量拉取历史(私聊按身份过滤) |
| POST | /api/rooms/:id/advance | GM | 强制 AI 推进一轮 |
| POST | /api/rooms/:id/speak | GM | 点名子代理接话({agentId}) |
| GET | /api/rooms/:id/roles | 成员 | 角色名单 + 自己的角色本(GM 看全部) |
| POST | /api/rooms/:id/roles | 成员/GM | claim / assign / release |
| POST | /api/rooms/:id/phase | GM | 剧本杀阶段机:next / set |
| GET | /api/rooms/:id/truth | GM | 汤底 / 真相正文 |
| 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/purge | GM 或本机 | 永久删除房间(关闭只是打标记) |
| 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=<邀请令牌>"
选择性发言怎么定的
- 玩家 / GM 的发言进队列,观众不触发模型;
debounceMs(默认 1200ms)内的发言合并成一轮,避免一句一回;- 一轮交给模型时,system 里写明「闲聊就只输出
<silent>,剧情需要推进才回应」; - 模型输出
<silent>(或空)→ 不产生消息,但这批发言标记为已读,不会反复重发; - 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(市场兼容表)
导入方式二选一:
-
插件管理 → 安装插件,在安装框里填本地包路径
.\dsh-story-party-0.2.0.tgz(插件管理器支持 registry / path / git / tarball 四类来源),装完重启 DSH; -
命令行(插件管理器不可用时):
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)
| 键 | 默认 | 说明 |
|---|---|---|
port | 7302 | 房间服务端口(7301 在本机被系统保留,故用 7302);开房页「开放端口」改过的值会存进 $DSH_HOME/galgame-room/port.json,优先级高于这里 |
hostname | 0.0.0.0 | 监听地址;只给本机用可改 127.0.0.1 |
publicUrl | 空 | 公网地址;开房页里保存的值(publicUrl.json)优先于这里,留空则用页面当前 origin |
maxPlayers | 4 | GM + 玩家 的座位数(观众另算) |
debounceMs | 1200 | 发言合并窗口 |
contextMessages | 30 | 交给模型的历史消息条数(滑动窗口);0 = 全部历史(每轮重发,费 token);建房时可单独覆盖 |
aiName | 旁白 | AI 在消息流里的名字 |
strictVersion | false | true = 进房必须带 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
by awesome-dsh-plugin
A curated list of plugins for DeepSeek Harness (dsh) · DeepSeek Harness 插件精选列表
★ 20.4k
CC0-1.0
Python
Oct 23, 2026
by 0xsline
DeepSeek Harness (DSH) ecosystem: curated plugins, tools, and infrastructure from dsh-external/hub and the public dsh-plugin topic.
★ 1.3k
CC0-1.0
Python
Oct 16, 2026
by pax-beehive
Open-source CLI, schemas, resolver, and DSH agent tools for DSH Plugin Hub
★ 277
MIT
TypeScript
Oct 20, 2026
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.
★ 230
MIT
TypeScript
Oct 23, 2026
dsh plugin --profile web add dsh-config-managerby yjh051108
推荐组件(非必须):DeepSeek Harness 运行时注入器;已随 dsh-routing-suite 单仓库化保留,本仓库继续维护/发布。
★ 166
TypeScript
Oct 16, 2026
dsh plugin --profile web add @dsh-external/dsh-super-injectorby 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.
★ 125
MIT
TypeScript
Oct 22, 2026