dsh-rp-tools
给 DeepSeek Harness(dsh)用的 跑团 / DM 工具插件:
中立随机裁决(rp_random)+ 本地 ComfyUI 配图(场景、NPC 立绘、道具线索、氛围图)+
按会话隔离的战役配置(世界设定 / 角色卡 / 随机表 / 风格偏好)+ 世界书与状态追踪 +
PNG 故事书(SillyTavern 角色卡)导入 —— 选一张卡就能开团。
面向通用 DM 活动:任何模组、任何战役都能用;配图完全走本机 ComfyUI,不依赖任何云端服务。
设计取向:只依赖 ComfyUI 本身(直连
POST /prompt+ 轮询/history), 不使用 dsh-comfyui 插件的工作流库,因此两者可以各自独立使用。
特性
| 🎲 中立随机 | rp_random:骰子表达式(2d6+3 / d20 / 3d8+1d4-2)、区间、加权抽取、布尔翻转;seed 可复现,count 可批量(1..20,越界报错而不是静默截断);每一掷都给可核对的逐颗明细(11(2d6[6,2] + 3)),骰式必须整串合法(2dd6 / 2d6++3 一律拒绝) |
| 🖼 本地生图 | rp_illustrate:直连本机 ComfyUI,Krea-2 Turbo(8 步 / CFG 1)+ Qwen3-VL 文本编码;1024² 约 13–18 秒,1344×768 约 25 秒 |
| 🎨 10 种风格 | manga(黑白漫画,默认,无 LoRA)+ 9 个官方 Krea-2 风格 LoRA(水墨 / 点绘 / 蜡笔 / 抽象 / 雨窗 / 复古动画 / 水彩 / 运动模糊 / 塔罗);每个风格 = 一套生图工作流,含触发词 / CFG / 步数 / 尺寸预设 |
| 🧑 角色卡 | rp_character:登记「名字 + 外观」,之后任何画面描述里提到该名字就自动补外观 —— 保持角色长相一致的主要手段,并可直接出立绘 |
| 📖 PNG 故事书导入 | 工作区那一行的「📖 导入 PNG 故事书」:从本地卡库(SillyTavern PNG 角色卡)选一张 → 世界书追加进本会话自己的 rp-sessions/<会话 id>/rp-worldbook.md、卡全文与卡面落进 rp-sessions/<会话 id>/cards/、角色卡/世界写进会话配置、自动切 dm 预设并把开场指令发给 DM。实测 3269 张卡库:解析 160/160 成功(中位 1 ms) |
| 📚 世界书 | 会话工作区的 rp-worldbook.md:## 分条,keys / constant / order / prob 标记;只有命中的条目进上下文(每轮预算 12 条 / 6000 字),被裁的列标题供按需补读 |
| 📌 状态追踪 | rp_state:场景 / 时间 / 地点 / 在场 / 线索 + 队伍(状态·持有·伤病·目标)+ 自由旗标;空串即清除;注入在每轮上下文最前 |
| 🌍 会话隔离 | 世界设定 / 角色卡 / 随机表 / 提示词前缀 / 会话默认风格按会话独立,互不干扰;真正全局的只有风格库、ComfyUI 地址、全局负面词、全局默认风格、卡库目录 |
| 🎬 整幕批量 | rp_scenes:吃 scenes[].panels[] 结构(含每格 positive/seed/宽高),一次出一整幕,单格失败不中断 |
| 🎲 随机表 | rp_table:遭遇表 / 掉落表 / 情绪表…… 定义(表名 + 骰式 + 条目)、掷表、count/seed;面板上也能掷 |
| 🖥 三处界面 | 设置页「RP工具」(全局配置 + 风格库 + 工具清单);DM 会话头部的「🎲 RP」按钮(右侧栏面板:世界 / 角色卡 / 随机表 / 本会话生图配置);工作区那一行的「📖 导入 PNG 故事书」 |
| 🔒 只进 DM 会话 | RP 工具只在 dm 预设作用域注册,其它预设的会话既看不到工具、也没有任何 RP 界面(故事书导入入口只在空白会话或 DM 会话出现 —— 否则没法从零开团) |
安装
# 从 GitHub 装
dsh plugin --profile web add github:SiriusWJ/dsh-rp-tools
开发期用本地目录(本仓库即源码):
// profiles/<profile>/package.json
"dependencies": { "dsh-rp-tools": "file:D:/Code/dsh/rp-tools-plugin" }
安装/改动后必须重启 dsh web(宿主代码与客户端 bundle 都只在启动时装载)。
前提:本机 ComfyUI(Comfy Desktop 亦可)
模型放在 ComfyUI 的模型目录(models/):
models/diffusion_models/krea2_turbo_fp8_scaled.safetensors ← 生图主模型(12.2GB)
models/text_encoders/qwen3vl_4b_fp8_scaled.safetensors ← 文本编码(4.9GB)
models/vae/qwen_image_vae.safetensors ← VAE(0.24GB)
models/loras/krea2_*.safetensors ← 9 个风格 LoRA(各约 448MB,可选)
来源:https://huggingface.co/Comfy-Org/Krea-2(国内可用 https://hf-mirror.com/Comfy-Org/Krea-2)。
没有 LoRA 也能跑:manga 风格不使用 LoRA。ComfyUI 地址默认 http://127.0.0.1:8188,可在设置页改。
工具
11 个工具,全部以 rp_ 开头,全部只在 dm 预设作用域注册。设置页「RP工具 → 工具列表」会实时展示同款清单(含每个参数的说明)。
| 工具 | 作用 | 主要参数 |
|---|---|---|
rp_random |
中立随机裁决(骰子 / 区间 / 抽取 / 布尔) | kind? dice? choices? weights? min? max? count? seed? |
rp_styles |
列出风格(触发词 / CFG / 步数 / 尺寸预设) | — |
rp_illustrate |
按风格生成一张图(并自动存进资源库) | prompt* style? seed? aspect? width? height? label? tags? kind? |
rp_assets |
浏览资源库(找回来复用,不必重出) | action? kind? characters? tags? q? limit? id? label? |
rp_character |
角色卡增删查 + 出立绘 | action* name? appearance? portrait? style? |
rp_state |
状态追踪(场景 / 时间 / 地点 / 在场 / 线索 + 队伍 + 旗标) | action* field? value? party? party_mode? party_remove? flags? |
rp_lore |
世界书按条读取 / 生成模板 | action* query? limit? |
rp_session |
本会话设置(世界 / 前缀 / 会话默认风格 / 风格备注 / 战役名) | action* world? prompt_prefix? default_style? style_notes? campaign_name? |
rp_scenes |
按场景文件逐格批量出图(scenes[].scene_id + panels[].panel_id;整幕共享一个组存进资源库) |
scenesFile* sceneId? style? limit? label? tags? |
rp_config |
全局配置(负面词 / 全局默认风格 / ComfyUI 地址 / 单个风格的触发词·步数·CFG) | action* negative? default_style? base_url? style_key? trigger? steps? cfg? |
rp_table |
随机表定义与掷表 | action* name? dice? entries? count? seed? |
* = 必填。
PNG 故事书导入
界面入口在工作区那一行(输入框上方,只在空白会话或 DM 会话出现)。点开 → 在本地卡库里搜卡 → 预览(世界书条数 / 开场白来源 / 世界与性格摘要)→ 「导入并开始」。
导入做的事:
- 卡里的
character_book→ 追加合并进会话工作区的rp-worldbook.md(按标题去重,绝不覆盖你自己写的条目;无keys的条目不补 constant —— 运行时用标题当触发词, 而导入来的条目一律标source: card,不占系统提示,命中了才进上下文); 条目名优先用卡给的,卡没给名字时依次用正文里的第一个标题 → 触发词 → 正文首行 (所以不会出现「条目 4」「条目 5」这种认不出是什么的名字); - 卡全文 →
<工作区>/rp-sessions/<会话 id>/cards/<slug>.md(世界书有 60 条 / 6 万字预算,超出的设定在这里按需read); - 卡面 →
<工作区>/rp-sessions/<会话 id>/cards/<slug>.png,登记成会话封面(不是某个角色的立绘); - 角色卡 / 世界设定 / 战役名 → 会话配置(
creator_notes里的广告、社群号、CC 协议逐行剔掉, 只有真正的说明才进【世界设定】;整段都是广告就不注入,全文仍留在卡文件里); - 开场引导文件 →
<工作区>/rp-sessions/<会话 id>/cards/<slug>.launch.md(选定开场 + 文件清单 + 已写入什么 + 待人工确认),并切成dm预设、只把这个路径发给 DM (首条消息约 60 字,绝不内联开场白)。
导入之后要收尾的事全在
launch.md里(DM 读那份文件就有全部开局任务):角色字段归位、 世界书过滤(状态/历史类改成触发式、删空壳、补触发词、constant只留 1–3 条)、 以及按当前语言收拾标题与属性(name:→名称:、gender: Female→性别:女;条目 4→ 「战斗」这类实义名)。整本一起收拾用rp_lore(action:"localize")/rp_lore(action:"rename_unnamed"),一次调用做完。 所以面板上没有「属性中文化」「重命名条目」这类按钮 —— 同一件事两个入口会让人以为没做。
注入分层(省 token 的关键)
| 通道 | 放什么 |
|---|---|
| system standing(每轮都在、字节稳定) | 通用 DM 规则、session.dm.prompt、战役名、世界设定、紧凑人物索引、路径指针、生图策略、手写且在预算内的常驻世界书 |
| runtime context(每轮重新装配) | 当前状态、在场人物的详细卡、命中触发词的世界书条目、导入来的条目(含原卡标了 constant 的) |
| 文件(按需 read) | 卡全文、全部备用开场白、被预算挡下的条目、导入映射 |
两条硬规则:导入来的世界书正文不因 constant 自动进系统提示(它按触发词走 runtime);
system standing 里的常驻世界书有 6000 字预算,超出的降级为「按需」,而不是截断成残句。
同一个人物若既在世界书里、又作为人物卡在本轮展开,装配时会跳过世界书那份(诊断里记为
character-duplicate),避免同一批正文注入两遍。
卡库位置在设置页 → RP工具 → 卡库目录(默认是会话工作区下的 rp-cards)。
目录结构是 cards/<分类>/*.png;没有私有索引文件时退回按文件名扫目录,功能一样可用。
出图:默认尺寸与立绘复用
出图默认尺寸(设置页「图像」可改):场景 768×432、立绘 512×768、道具 512×512。 出图时间基本正比于像素,而聊天里也渲染不到 1024 宽,所以 1.12.8 起调小了(单张大约 8~14 秒); 老配置里没动过的那一档会跟着换成新值,自己改过的保持原样。
立绘复用:常驻段的【本会话设定】里有一行「已有可用图」—— 角色已有的生成立绘、
玩家自己导入的图、以及导入卡的卡面(对角色卡来说那张 PNG 就是它的立绘)都在那里。
DM 第一次出场时直接展示它,不必再花十几秒重出一张;确实没有图时才调 rp_illustrate。
DM 也可以在叙事里直接把这些图摆进回复(零成本),不必为了「让角色露个脸」重新生图。
立绘是纵向的:rp_illustrate 不传 width/height/aspect 时,画面里只提到一个已登记角色
就按 portrait(纵向,默认 512×768)出,否则按场景横幅 —— 所以「给某人出一张立绘」不必自己算比例。
DM 出的第一张单人图会自动记成那个角色的立绘(已有立绘时不覆盖)。
角色卡编辑器里可以:生成立绘 / 重新生成(覆盖,纵向)、导入图片(png / jpeg / webp,≤8MB,
存进 <工作区>/rp-sessions/<会话 id>/portraits/)、或直接删掉这个角色(会顺手清掉它的立绘记录)。
资源库:出过的图都留下来,能找回来
只记引用会在几周后变成一堆死链,所以出图后插件会把图真抓一份存进会话目录,并按分类分文件夹:
<工作区>/rp-sessions/<会话 id>/
├── assets.json 索引:一张图一条(id / 分类 / 标签 / 角色 / 尺寸 / 风格 / 提示词 / 时间)
└── assets/
├── portraits/<id>.png 角色
├── scenes/<id>.png 场景
├── items/<id>.png 道具
└── other/<id>.png 其他
入库的四个入口:rp_illustrate 出图、rp_scenes 每格(整幕共享一个 group)、
rp_character(portrait:true)、面板「导入图片」。导入卡的卡面不入库 —— 它属于卡库。
按内容去重:入库前比对 sha256,同一个分类里字节完全相同的图复用已有那条(合并标签、不写第二份文件)。
本地出图是「同 seed + 同提示词 → 同一张图」,没有这一步,重出一张一样的就会在库里留下两条只有 id 不同的记录
—— 图墙看着两张、磁盘占两份、人还分不出区别。查重放在写锁内(否则并发归档同一张会各写一份)。
rp_scenes 的场景文件字段名是固定的:幕的 id 是 scene_id(不是 id),分镜是 panel_id;
sceneId 参数筛的就是它(也兼容 id/title 这类常见写法)。筛不到会直接报错并列出文件里实际有哪些 id ——
不会静默返回 0 张(那看起来像生图服务坏了)。字段名与最小合法示例写在工具的 description 里。
DM 侧用 rp_assets 按 kind / characters / tags / q 查,返回的每行都带能直接放进
dsh-ui image 组件的地址;常驻段里只报条数(资源库:本会话已有 23 张图(角色 6、场景 14、道具 3))
—— 常驻内容是每轮都发的,把上百条列进来会白白吃掉几千字。DM 不能删图,删除是玩家在面板里做的事。
玩家侧面板有一张「资源」卡片:分类筛选 + 搜索 + 图墙(服务端降采样缩略图)+ 点开看原图,
能改名称/标签、显示到对话(拼成 dsh-ui 围栏填进输入框,不自动发送)、
设为某角色的立绘(不复制文件,只改引用;会清掉该角色旧的生成立绘,否则读取端会优先显示旧的)、
删除(连磁盘文件一起删,并自动解除指向它的立绘引用,不留死链)。
⚠️ 索引是 read-modify-write,而
rp_illustrate是并发安全的(宿主并行池最多 10 个在飞)—— 所以所有写索引的路径都过一把按会话串行的写队列。没有它,同时出 5 张图可能只入库 2 张, 而且不报错。
需要一次出多张时,DM 会在同一步里并发发出多个 rp_illustrate(插件已把这两个工具声明为
并发安全,宿主才会真的并行调度)。ComfyUI 是单卡队列,GPU 总时长不变 —— 省掉的是每张图
之间那几轮模型往返(长局里一步就是几万 input token)。
⚠️ 实测结论(3269 张卡,见
docs/PNG-CARD-DECODE.md):first_mes100% 被广告污染 (deepseektavern.com),所以导入一律改用alternate_greetings的第一条; 40% 的卡正文只在character_book里,所以导入的主战场是世界书而不是角色字段。
备份 / 会话包
长一点的团需要最低限度的保障。会话配置在全局数据目录、世界书与资源图在工作区 —— 两处分离,手工备份必漏一半,所以插件把它们打成一个 zip:
MANIFEST.json 格式与版本 / 导出时间 / 原会话 id / 每个文件的 sha256
session.json 会话配置
rp-worldbook.md 世界书(可能没有)
assets.json 资源库索引
assets/<分类>/<id>.<ext> 出过的图与导入的图(1.13.0 起真存了一份,所以包是自包含的)
cards/<slug>.{md,json,launch.md,png} 导入卡产物(含卡面与开局引导)
- 导出:面板「备份 / 会话包 → 导出会话包」是一个
<a download>,浏览器自己存盘。 - 快照:同一个卡片里的「拍快照」把包写进
<工作区>/rp-sessions/<id>/snapshots/, 只保留最近 5 个;只管自己写的snapshot-*.zip,你放进这个目录的别的包不会被当成快照、也不会被删。 - 导入:选一个 zip。默认不覆盖 —— 目标会话已有内容时宿主回 409,界面问一句,
确认后才带
overwrite:true重来,而且覆盖前会自动拍一个快照兜底。 - 包是 STORE(不压缩) 的 zip:里面装的是已经压过的 PNG,再压一遍没意义。 用别的工具重新打包时会默认压缩 → 导入会明确报「只支持 STORE 包」,不会给你一堆乱码。
⚠️ 解包是外部输入:条目名可能带
../(zip-slip)。所以每个条目名都要过safeEntryName()(逐段判定../绝对路径/盘符),落盘时再做一次目标路径前缀校验 —— 两道锁都留着,单点失效不至于写穿会话目录。清单里的 sha256 也会逐个核对。
轻量地图(可选)
只在模组本身有地点结构时才用(地牢、宅邸、城镇)。没有地图就正常叙事 —— 不给每个场景造图。
<工作区>/rp-sessions/<会话 id>/
├── rp-map.json 静态结构:nodes(id/label/public)+ edges(id/a/b/label/state)
└── rp-map-state.json 运行时:node 当前节点 / revealed 已揭示 / edges 变化的边 / tokens 标记位置
首版不加 rp_map 工具:两个文件都由 DM 用通用的 read/write 读写。插件只做两件事:
- 每轮注入一行摘要(跟在「本场当前状态」之后):
【地图】旧钟旅店·大堂|已揭示 4/5|可走:厨房(木门)、二楼客房(楼梯)|队伍@大堂、老板@大堂—— 实测 64 字/轮。DM 不必为了看一眼「我在哪、能去哪」去读整个文件。 - 结构出问题时把话说明白(而不是静默或渲染垃圾):
【地图】⚠ node="nope" 不是 rp-map.json 里的节点。两条纪律都是踩出来的: 文件坏了 ≠ 还没建(状态文件存在但 JSON 坏了时,不能说「还没初始化」,否则 DM 会覆盖一份 本可救回的文件);坏数据不渲染摘要(拿node="nope"画出一行看起来正常的地图, 比什么都不显示更危险)。
为什么状态不放进 rp_state.flags:applyStateUpdates 写旗标时是 String(value) →
对象直接变成 "[object Object]"(写进去了、不报错、永久丢失),数组被压成逗号串;
再叠上 FLAG_VALUE_CHARS = 120 的截断与 FLAGS_MAX_SHOWN = 16 的显示上限
(20 个地图键会把剧情旗标挤到对 DM 不可见)。实测:
写 flags:map_edge_states=[object Object];map_tokens=[object Object] ← 第一回合就死
两个地图文件都计入会话包(导出/快照会带上)。DM 侧的约定(何时建图、怎么画、
按钮只从当前节点的邻接边生成、action 命名 <地图id>:move:<节点id>)写在 dm 预设的
「## 地图」小节里。
数据与配置
~/.dsh/data/dsh-rp-tools/
├── styles.json 全局:风格库 + 全局负面词 + ComfyUI 地址 + 全局默认风格 + 卡库目录
├── sessions/<sessionId>.json 会话级:世界设定 / 角色卡 / 随机表 / 前缀 / 会话默认风格 / 状态 / 立绘登记
├── dm-sessions.json DM 会话登记表(界面据此决定是否显示 RP 入口)
└── _agent-probe.json 诊断用:agent/created 事件里可读到的字段快照
<会话工作区>/
├── rp-worldbook.md 世界书(可手写;导入的故事书条目也追加在这里)
└── rp-sessions/<会话 id>/
├── cards/<slug>.{md,json,png} 导入产物:卡全文 / 规范化结果 / 卡面
├── assets.json 资源库索引(一张图一条)
├── assets/<分类>/<id>.<ext> 出过的图与导入的图(portraits / scenes / items / other)
└── snapshots/snapshot-<时间>.zip 恢复点(只保留最近 5 个;只删自己写的那些)
styles.json 关键字段:
{
"comfyui": { "baseUrl": "http://127.0.0.1:8188", "dshOrigin": "http://127.0.0.1:3080" },
"defaultStyle": "manga",
"negative": "low quality, worst quality, blurry, ... , lowres", // 全局负面词(预置一套)
"styles": {
"manga": { "label": "黑白漫画", "workflow": "krea2", "lora": null, "cfg": 1, "steps": 8,
"trigger": "black and white manga panel, screentone shading, crisp ink lineart, ...",
"sizes": { "scene": [1344,768], "portrait": [768,1024], "item": [1024,1024] } }
}
}
⚠️ 负面词与 CFG:Krea-2 Turbo 建议 CFG=1,此时负向条件在数学上不参与计算(官方模板也如此)。 想让全局负面词真正生效,把对应风格的
cfg调到1.5~2.5(过高会让 turbo 模型过曝/崩坏)。
DM 预设接线(关键,否则工具不出现)
RP 工具只在 dm 预设作用域注册。需要在预设目录做两件事:
1. 放行工具(~/.dsh/.agent-presets/dm/agent.cordis.yml)—— 该预设默认 deny 掉所有全局工具,只保留白名单:
- id: dm-filter
name: ./session-filter.mjs
config:
keepGlobalTools:
- render_ui
- validate_dsh_ui
- web_search
全部
rp_*工具现在都在本预设作用域注册(由rp-bridge.mjs调用registerRpTools), 所以白名单里不再需要放行任何rp_*—— 它只用于保留少数几个全局工具。
2. 挂桥接插件(把 RP 工具注册进本会话 + 登记 DM 会话):
- id: rp-bridge
name: ./rp-bridge.mjs
rp-bridge.mjs 的副本见本仓库 preset/rp-bridge.mjs。
架构
dsh-rp-tools/
├── lib/index.js 宿主半侧(ESM)
│ ├── apply(ctx) 全局:**不注册任何模型工具**,只挂 HTTP 路由 + 监听 session/created(fork 继承、记录工作区)
│ ├── registerRpTools(ctx) dm 作用域(由 rp-bridge 调用):全部 10 个 rp_ 工具 + 两条提示词注入通道
│ ├── 生图链路 组装 API 工作流 → ComfyUI POST /prompt → 轮询 /history → 同源媒体 URL
│ └── 配置层 styles.json(全局) / sessions/<id>.json(会话)
├── lib/card-png.js PNG 角色卡解码(tEXt / iTXt / zTXt,ccv3 优先,截断容错)
├── lib/card-import.js 卡 → 会话配置的映射(丢广告开场白、无 keys 条目补 constant、限量 + 全文导出)
├── client/client.js 客户端半侧(plain JS + React.createElement,无构建)
│ ├── settings.section「RP工具」 全局配置 + 风格库 + 卡库目录 + 工具清单
│ ├── conversation.session.header.utilities 仅 DM 会话渲染的「🎲 RP」按钮(打开右侧栏面板)
│ └── conversation.input.dock「📖 导入 PNG 故事书」 空白会话 / DM 会话里的故事书导入入口
├── preset/rp-bridge.mjs dm 预设作用域桥接插件(副本,供安装参考)
├── cordis.patch.yml bundle 补丁层
└── docs/ 交接文档(HANDOFF)/ 状态(STATUS)/ 卡格式实测(PNG-CARD-DECODE)
HTTP 路由(POST 全部同源保护,Origin 必须等于 Host):
| 路由 | 方法 | 用途 |
|---|---|---|
/rp-tools/state |
GET | 全局配置 + 风格摘要(同时学习浏览器 origin,用于拼媒体 URL) |
/rp-tools/config |
POST | 写全局配置(含 negative / baseUrl / cards.root / 风格字段与增删) |
/rp-tools/reset |
POST | 恢复默认全局配置 |
/rp-tools/check |
GET | ComfyUI 连通性(版本 / GPU / 显存) |
/rp-tools/inject |
GET | 注入自检:某个会话会被注入什么(只读,不参与运行) |
/rp-tools/loras |
GET | 本地 LoRA 清单(读 ComfyUI /object_info) |
/rp-tools/session |
GET/POST | 读写某个会话的 RP 配置(角色卡 / 世界 / 随机表 / 状态 / 立绘) |
/rp-tools/dm-mark |
POST | 登记某会话为 DM 会话 |
/rp-tools/tools |
GET | 工具清单 + 参数说明(设置页用) |
/rp-tools/roll |
POST | 掷随机表(面板用) |
/rp-tools/media |
GET | 同源媒体代理:把 ComfyUI /view 转成同源,图片才能在聊天里渲染 |
/rp-tools/portrait |
POST | 登记/清除某个角色的立绘(只存 ComfyUI 三要素,媒体仍走 /rp-tools/media;保存时也会归档进资源库) |
/rp-tools/portrait-upload |
POST | 导入外部立绘(/rp-tools/asset-upload 的别名,等价于 kind=portrait + 角色名) |
/rp-tools/portrait-image |
GET | 把登记过的导入立绘发回浏览器(只认会话配置里的相对路径 + 会话目录前缀校验) |
/rp-tools/assets |
GET/POST | 资源库:列出(可筛分类/角色/标签/关键词)/ 改名称与标签 / 删除 / 设为某角色的立绘 |
/rp-tools/asset-upload |
POST | 导入外部图进资源库(data URL → assets/<分类>/<id>.<ext>,只收 png/jpeg/webp、≤8MB) |
/rp-tools/asset-image |
GET | 发资源图(按 id;thumb=1&width=N 走服务端降采样,图墙用它) |
/rp-tools/export |
GET | 下载会话包(STORE-only zip:配置 + 世界书 + 资源库 + 导入卡产物) |
/rp-tools/snapshots |
GET | 列恢复点(只列自己写的 snapshot-*.zip) |
/rp-tools/snapshot |
POST | 拍一个恢复点并修剪到最近 5 个;删不掉的如实报在 failed 里 |
/rp-tools/import |
POST | 导入会话包(path=会话目录内的包,或 dataUrl);默认不覆盖,覆盖前自动拍快照 |
/rp-tools/preview |
POST | 试出一张(设置页 / 面板用,可带 sessionId;sizeKey 选场景/立绘/道具档) |
/rp-tools/cards |
GET | 列卡库(服务端搜索 / 分类 / 分页) |
/rp-tools/card |
GET | 解析单张卡 → 摘要与预览(不落盘) |
/rp-tools/card-import |
POST | 导入到某个会话(写世界书 / 卡全文 / 卡面 + 更新会话配置,返回开场指令) |
/rp-tools/card-image |
GET | 卡面图(只服务卡库内的 .png) |
⚠️
/rp-tools/card*三条会把磁盘内容交给浏览器,路径一律经safeCardPath()(resolve后前缀比对卡库根 + 只认.png); 逃逸 / 绝对路径 / 非 png 全部 400。/rp-tools/portrait-image同理,而且是双锁:只发会话配置里登记过的那张, 再把相对路径解析到会话目录下做前缀校验;文件名由宿主用portraitFileSlug()生成(用户给的名字不进路径)。
开发
所有改动都在这个 git 仓库里做(
D:\Code\dsh\rp-tools-plugin,它本身就是github.com/SiriusWJ/dsh-rp-tools的克隆)。不要改 profile 里那份安装副本 (~/.dsh/profiles/web/node_modules/dsh-rp-tools)—— 它是重装时会被覆盖的产物, 改了既不进版本库,下次安装就没了。
node --check lib/index.js && node --check client/client.js # 语法检查
这个插件在 profile 里是从 GitHub 装的("dsh-rp-tools": "github:SiriusWJ/dsh-rp-tools"),
所以「本地源码 → GitHub → profile」是一条链,本地不再是权威副本:
# 1) 改完 → 提交并推送(profile 装的就是 push 上去的那个 commit)
git -C D:\Code\dsh\rp-tools-plugin add -A
git -C D:\Code\dsh\rp-tools-plugin commit -m "feat(x): …"
git -C D:\Code\dsh\rp-tools-plugin push origin main
# 2) 重装,让 profile 跟上新 commit(不重装的话它还停在旧 commit)
dsh plugin --profile web add github:SiriusWJ/dsh-rp-tools
# 3) 改的是 dm 预设那一半,还要同步**活动预设目录**(它不属于这个包,只能手动拷)
Copy-Item preset\agent.cordis.yml "$env:USERPROFILE\.dsh\.agent-presets\dm\agent.cordis.yml" -Force
Copy-Item preset\session-filter-v2.mjs "$env:USERPROFILE\.dsh\.agent-presets\dm\session-filter-v2.mjs" -Force
Copy-Item preset\rp-bridge.mjs "$env:USERPROFILE\.dsh\.agent-presets\dm\rp-bridge.mjs" -Force
重启 dsh web 后生效(lib/ 与 preset/ 在启动时装载;client/ 只需刷新页面)。
只改了文档(
docs/、README.md)时第 2 步可以跳过 —— 安装副本里的文档不参与运行。
想跳过「push + 重装」这两步(改成改完即生效):把依赖换成
dsh plugin --profile web add link:D:/Code/dsh/rp-tools-plugin。 代价是 profile 直接读源码目录,与「商店里声明的是 GitHub」不一致 —— 二选一。⚠️ 本机到
codeload.github.com(GitHub 打包下载域名)吞吐只有 ~25KB/s,且 Node 的 fetch 比系统下载慢十倍量级 —— 仓库 tarball 必须保持小(这也是temp_output/被移出仓库的原因: 四张试出图占了 4.3MB,会让github:安装卡满超时)。
测试:
node tools/smoke-dm.mjs # 宿主:作用域隔离 / 路由 / 世界书 / 状态 / 风格库 / 卡库导入
node tools/smoke-card.mjs # PNG 卡解码 + 映射 + 开场指令 / 引导文件(合成 PNG 字节)
node tools/smoke-client.mjs # 客户端 bundle:样式注入时机 / 槽位注册 / 面板渲染
node tools/verify-roundtrip.mjs <card-import.js> # 导入↔解析往返(需 profile 里那份)
node tools/probe-cardlib.mjs # 真卡库探针(只读 + 临时目录,手动跑)
当前开发状态、验证记录、已知问题与路线图见 docs/STATUS.md, 给新会话的交接文档见 docs/HANDOFF.md。
License
MIT
No comments yet. Be the first to write one.