dsh-ai-with-u
Verifieddsh-ai-with-u · v0.5.3 · MIT · Web UI
AI WITH U · 角色扮演管理器 —— 选卡即插即用人设注入、日记记忆与可视化角色管理(DSH 插件版)
Install
dsh plugin add dsh-ai-with-u Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Tags
Readme
dsh-ai-with-u
给 DeepSeek Harness 的角色扮演插件。 蒸馏任意ip角色卡并让你的大肥鱼成为TA,选一张角色卡点「启用」,人设立刻生效;点「退出」,干净拔出,正常工作不受影响。
面板实拍(v0.5.2,v0.5.3 只改了按钮语义与左栏纪律包胶囊):左栏只负责「选谁」——角色簿、搜索与过滤;右栏是详情——绑定到本会话 / 全局启用 / 编辑 / ⋯,34 个字段都能就地改。
目录
最近更新
当前版本 v0.5.3。历史版本见 docs/DEVELOPMENT.md。
- 面板重新排过:分段器搬到右栏顶部,左栏只回答「选谁」(选中的卡有蓝色轨,日记页还会显示「正在看:X」);详情页动作区固定四枚——绑定到本会话(主)/ 全局启用(同级中性) / 编辑 / ⋯,两个作用域同级并列,删除挪到页面底部的危险区;
<900px走单栏导航栈(列表 → 详情 → 返回)。 - 纪律包有了面板入口:左栏工具栏紧跟「纯文本 / 文本 + 动作」胶囊后面多了「关闭 / 精简 / 完整」三档纪律包胶囊(
data-group="discipline",三者是同一组单选,切档走POST /discipline)。以前这三档只出现在注入预览的只读文字里——主人看不到就等于没有入口。 - 宿主侧修了四个会咬人的:世界书正则的灾难回溯(实测
(a|a)+$在 26 个字符上要 745ms,扫描窗是 8000 字符——一张恶意卡就能卡死宿主)现在按五族判据拦在编译前,命中即退化字面量匹配;日记里「没有**时间线**分节」的自由文本不再被静默丢弃;预置卡删除后写墓碑、重启不复活;子代理不再回落全局角色(它既不扮演、也不写盘,awu_*会如实报reason: subagent)。 - 动效系统重做:玻璃面板本体不再参与动画(这是我们在 Chromium 上踩了四次才定下来的:
backdrop-filter的元素只要有带动画的祖先,玻璃就会变成假半透明),改成「玻璃静止 + 内容放大上浮 + 压暗淡入」;弹层、编辑器、折叠、提示条都补齐了退场。 - 前端全量体检:五名检查员通读前端 12,000 行 + 三份 CSS,修掉 P0 10 条(含「日记页静默换了操作对象」这种会伤数据的)与几十条中危;新增
verify-ui/verify-a11y两套静态自检,自检总数 1047 项(后经契约对齐轮到 1107)。 - 工程侧:
tools/preview富化 fixture(可模拟慢响应 / 报错 / 崩溃 / 已绑定会话),tools/qa出截图矩阵——它现在会判红(导航没落地、打包失败、步骤点空、图与"什么都没点"的基线一样,任一命中即失败)。
完整过程见 docs/DEVELOPMENT.md 第十六节;设计与审查报告在 docs/design 与 docs/audit。
它是什么
一个 DSH 插件,负责三件事:把角色卡注入系统提示词、维护角色的日记记忆、提供一个管理面板。
自带完整的蒸馏系统,可指引大肥鱼联网收集资料,整理一张任意 IP 的角色卡并启用角色扮演——只要网上能搜到,就能蒸出来! 内置三张卡:Ave Mujica 的丰川祥子、明日方舟的阿米娅、崩坏的昔涟。 标准的酒馆卡格式,可导入现成酒馆卡,也可自制。
聊天本身仍然走 DSH 自己的会话——不做自定义聊天界面,不劫持消息流。插件只在「人设从哪来、记忆写到哪」这两个环节介入。
启用前:和普通 DSH 会话没有区别
启用后:模型以角色身份说话,但该跑测试跑测试、该读代码读代码
退出后:角色相关的五段全部清空,和没启用过一样
预设段例外见 分层注入:预设是宿主侧的文风层,不随角色退出。
为什么会有它
原项目 ai-with-u 是一个 skill:角色卡、日记、时间差全有,但所有确定性逻辑都交给模型——建目录、读写 JSON、追加 markdown、算索引。模型得同时扮演角色和文件系统,出岔子是早晚的事。
这个插件把确定性部分收进代码,只把「怎么写台词」留给模型。
| 原 skill | 本插件 | |
|---|---|---|
| 提示词 | SKILL.md 13 KB,每轮全量注入 |
启用态约 2.8–3.3 KB(lite 档),不启用时为 0 |
| 文件读写 | 模型自己动手 | 插件代码保证,原子写 + 跨进程锁 |
| 角色管理 | 在对话里说指令 | 可视化面板 |
| 生效时机 | 一直生效 | 可插拔,退出即卸载 |
有两处是故意不做(完整边界清单见下一节 明确不做),别当成缺功能:
- 8 个场景的扮演测试(原 skill 里那套「测一遍人设像不像」的用例)没搬。人像不像只能靠聊,靠脚本测不出来——能测的(提示词长度、字段完整性、世界书命中、导入解析)都已经进了自检,共十套。
manifest/full_card不单独落文件,收进card.json的extensions.aiwu里。CCv3 本身只有一份真相,再存一份清单等于制造两个会互相漂移的事实来源;要读清单就GET /character。
明确不做
本插件控制的是 DSH agent 的人格,不是酒馆模拟器。 它把「我是谁、我怎么说话」注入系统提示词,聊天本身留给 DSH 正常会话。 凡是需要「劫持消息流 / 自建渲染层 / 自建运行期状态机」的能力,都不在范围内——这不是排期问题,是边界问题。 下面这份清单的唯一用途,是防止下一轮把它们重新当成「缺口」开工。
| 不做 | 为什么 |
|---|---|
| 立绘 / 表情层 | 卡里的 assets 导入时剥离并告警(src/import-card.ts:445-451)。DSH 没有说话流里「按情绪换图」的渲染点;面板塞一张静态头像解决不了问题,只会换来体积与第三方素材的版权负担。 |
| 多角色同场(群聊 / 多人群像) | 本插件的决议模型写死是一个会话一个角色(会话绑定 > 全局启用 > 未启用,src/prompt.ts)。多角色同场必须先回答「这条回复是谁说的」,那是聊天层的事;搬进插件等于自己写第二套 chat UI,与「不做自定义聊天界面」的定位正面冲突。 |
| 卡内前端 / 卡内脚本渲染 | CCv3 的 extensions 里什么都能塞,但 DSH 侧没有一个「安全执行第三方卡内代码」的沙箱渲染点。在宿主页面里跑陌生卡带来的 JS 不是功能,是事故。 |
| EJS / MVU / TavernHelper | 这三样是 ST 生态的运行期模板引擎 / 状态机 / 脚本助手,靠「每轮重算 + 读写消息」活着。本插件只做「卡 → 提示词文本」的一次性渲染,没有运行期状态机;这些字段原样保留不删,但按普通文本对待。 |
display 侧正则(regex_scripts 的输出替换) |
是可证明的屏蔽,不是「支持了但没用到」:DSH 的回复渲染不归插件管,显示层没有扩展点;提示词侧同样不执行。脚本字段原样保留(用户资产,删了将来回灌会缺),但脚本正文保证不出现在 L1 / 注入预览里(口径常量 tavern.ST_SCRIPT_STANCE,「有无脚本 L1 逐字相同」在自检里)。 |
| PNG / CHARX 导出 | 只做导入(parseCardBuffer 只有一个方向)。导出要自己拼 PNG chunk / zip,还得和 ST、各家前端互认,收益窄、维护面大;要备份直接拷 ~/.dsh/ai-with-u/characters/,要迁移就用 ST 本体导出。 |
深度注入:position: 4 / @D / depth + role |
内容不丢:这类条目照常注入,只是按 after 块摆(src/tavern.ts:1418-1465),导入时统计条数并告警。理由:往「第 N 条消息之前」插内容要改宿主的消息数组,越过 systemPrompt.section() 的边界,还会毁掉 KV 前缀复用。 |
装饰器的运行期语义(@@depth / @@position / @@role / @@probability / @@cooldown) |
认识、且会从正文剥掉(绝不把 @@xxx 当正文注入,src/tavern.ts:1305-1319),但不改变注入行为。@@activate / @@dont_activate 是例外,真的生效。理由同上:这些是 ST 的运行期钩子。 |
probability是按「做了」算的,别照上表最后一行误读:用的 FNV-1a 稳定哈希 + 本轮turnSeed, 同一轮结果固定、不毁缓存(src/tavern.ts:1697-1710)。不做的只有 ST 那种「每轮重摇 / 随消息数衰减」的运行期语义。
需要上面这些能力时,正解是装 ST 本体(或它的前端),不要让这个插件长成第二个 ST。 本插件的价值恰恰在于「只控制人格」——它要能一直留在 DSH 里,而不是变成一个要单独维护的聊天软件。
它是怎么工作的
flowchart LR
U[你的消息] --> S[DSH 会话]
S --> A{每轮组装系统提示词}
A --> L0[L0 底线]
A --> L05[L0.5 纪律包]
A --> L1[L1 角色卡]
A --> W[世界书命中]
A --> D[日记与会话摘要]
L0 --> M[模型]
L05 --> M
L1 --> M
W --> M
D --> M
M --> R[角色口吻的回复]
R --> DB[(写回日记 / 会话状态)]
DB -. 下一轮 .-> A
分层注入
| 层级 | 什么时候在 | 内容 | 体积 |
|---|---|---|---|
| L0 底线 | 启用期间 | 优先级声明 + 扮演底线 | 约 365 字符 |
| L0.5 纪律包 | 启用期间,按档位 | 禁止词库、消息形态、动作格式、输入识别、内容边界 | lite 555 / full 736 字符 |
| 预设 | 启用了预设就常驻 | 宿主侧文风层(独立于角色启用,进退角色都不动它) | 按预设体量,超 8000 字符给警告 |
| L1 角色 | 启用期间 | CCv3 角色卡(声线优先)+ 时间差 + 最近日记 | 约 1.9–2.4K,随卡增长 |
| 世界书 | 命中才注入 | 关键词 / 常驻条目,预算 12 条 · 6000 字符 | 典型约 0.2K |
| L2 长流程 | 工具按需 | 蒸馏、建卡向导 | 不进系统提示词 |
退出时角色相关的五段(L0 / L0.5 / L1 / 世界书 / 时间差)全部返回空串,宿主会过滤掉——这是「干净拔出」的实现方式,不是靠删文件。预设段(aiwu:preset)是例外:预设是宿主侧的文风层,独立于角色启用——进角色、退角色都不动它,全局预设启用时退出角色该段照样非空(src/prompt.ts 的渲染函数不校验角色启用态,这是设计:预设属于「这台宿主怎么说话」,不属于「此刻扮演谁」)。
纪律包有三档:off(关掉)/ lite(默认)/ full(多一段「遮住名字也要猜得出是谁」的语态锚点 + 每 10 轮日记自检)。切档走 POST /_dsh/ai-with-u/discipline,偏好存在 _active.json 里,退出角色也保留。
插拔
- 启用状态落盘,重启 DSH 自动恢复
awu_*工具在全局启用 或 任一会话还有有效绑定时挂载(会话绑定态下模型也要能记日记 / 查关系),两个条件都不成立才整批卸下——不是「启用角色才挂载」。awu_card_save例外:常驻注册,靠蒸馏武装期门控可用性- 世界书按关键词命中,同一轮复用结果,不重复注入、不跨轮累积
界面
入口在左侧栏底部(与用量统计插件同席位)。展开态显示「角色管理 + 当前角色名」,收起态收成 36px 圆徽,点击弹出悬浮面板。
面板是两栏主从结构:左栏只回答「选谁」,右栏看「这个人是什么样」+ 四个分区。
- 左栏:蒸馏 / 自创两枚同级按钮 → 搜索框(名字 / 身份 / 来源)与「共 N 位」→ 模式胶囊 → 预设 / 预览 / 导入 → 卡片墙。选中的卡有蓝色左侧轨;日记页下还会多一行「正在看:X」,避免一边翻日记一边点卡就把操作对象换掉。
- 右栏顶部:角色簿 / 日记 / 统计 / 指南四段分段器(带滑动指示块),右端是刷新与关闭。
四个分区:
- 角色簿 — 卡片墙、导入角色卡、注入预览、详情页、完整编辑
- 日记 — 时间线、关键词搜索、按月折叠、导出 markdown、逐条改写、危险区
- 统计 — 角色数、记忆条数、对话轮次、数据存放位置
- 指南 — 首次打开时的使用说明
窗口窄于 900px 时两栏会收成单栏:列表一屏,点卡进详情,返回再回列表——不再是"详情挤在十几张卡下面"。
注入预览在角色簿详情的「预览」入口:列出此刻真正会进系统提示词的六段(L0 底线 / L0.5 纪律包 / 预设 / L1 角色 / 世界书 / 时间差),每段给段名与字符数,下面是逐段正文,最后给总字符数。它显示的是宿主那一刻的真实渲染结果(GET /preview,纯只读:不刷新缓存、不写盘),不是面板自己另算的一份——两套算法的预览迟早会和实际注入对不上。
日记页支持记忆回顾:关键词搜索(标题、正文、日期都能命中)、按月份折叠,以及把当前角色的日记导出成 markdown 文件(浏览器里直接下载,不经过宿主)。
角色簿左栏顶部并排两枚同级按钮:蒸馏角色卡(从作品里蒸一张)和 自创角色卡(从零写一张)。旁边是两组胶囊:
对话模式「纯文本 / 文本 + 动作」,以及纪律包「关闭 / 精简 / 完整」(off / lite / full)。当前值高亮,点一下立刻生效——
不需要先启用角色,因为两者都是偏好不是角色属性(纪律包是全局偏好,换角色不重置)。
自创角色卡填三格(名字 / 身份 / 一句话背景)就能落档。想省事就勾「让 AI 补全人设」:插件先把这张骨架卡存好,再把补全指令发到当前会话,AI 会先问你 3–5 个关键问题(声线、关系、雷区、称呼),然后按酒馆卡格式补齐人设、开场白和世界书。合理推断标 [AI推断],推不出来的标 [待补],不编造。
角色簿左栏还有「导入角色卡」,吃酒馆卡的三种形态:带立绘的 .png、.charx 压缩包、裸 .json。选完文件先出预览(识别到的名字、格式、全部 warnings),同名卡会提醒冲突并要二次确认;确认后才写盘,取消不落任何文件。文件上限 11 MB——base64 会膨胀 4/3,11 MB 传到宿主正好还在 16 MB 收包线内;带立绘的 PNG 卡常见 3–10 MB。
「世界书太大」不再拦导入,只给警告:世界书是按需注入的(12 条 / 6000 字预算),超大书不会把提示词撑爆,只是超出预算的条目进不去当轮上下文。真正没有预算、直进提示词的那五个原生字段(描述 / 性格 / 场景 / 系统提示 / 历史后指令)仍然有硬上限。
角色卡编辑器是 10 组 34 字段,第 11 个分区是世界书:条目名、关键词 / 副关键词(逗号分隔)、内容长文本(可全屏写)、常驻、优先级、插入顺序、逐条启用与上移下移。高级项里有位置(角色设定前 / 后)、大小写敏感、selective、正则。
顶部体检按三档给结论,「必须处理」的只有两种真正会失效的情况:
| 档 | 报什么 |
|---|---|
| 必须处理(error) | 非常驻且没有关键词(永远命不中)、内容为空(注入了等于没注入) |
| 建议看(warn) | 条数超过建议线(30 条)——不会报错,超出预算的条目这一轮不进提示词,去「被裁条目」看清单 |
| 知道就好(info) | 偏长条目聚合一条(默认不逐条;单条超 3000 字才会单独点名,因为命中一次就挤掉别人)、共享关键词聚合一条 |
字数不是问题:世界书按需注入,总量再大也只是命中变少,所以「总字数超出」这条提醒已整条删掉(v0.3 之前会报,纯噪音)。体检只提示,不拦保存。
编辑器里「声线基因」是灵魂字段:说话节奏、自称、口头禅、禁忌词、小动作。这些决定「听起来像不像他」,所以给了金色描边和独立徽记。
安装
前提
- DeepSeek Harness 桌面版(desktop profile)
- Node 22.19+ 或 24+
- 不需要编译 C++,零原生依赖
步骤
包已发布到 npm,可以跳过克隆直接装(装完仍要按第 3 步配 bundles):
npm install dsh-ai-with-u
GitHub Packages 上也有同一版本(scoped 名,需要带 read:packages 的 token):
npm install @treasure-hub-agent/dsh-ai-with-u --registry=https://npm.pkg.github.com
或者从源码构建:
git clone https://github.com/Treasure-hub-agent/dsh-ai-with-u.git
cd dsh-ai-with-u
npm install && npm run build
# 建 junction(Windows 上不要用 ln -sfn,它会静默退化成目录拷贝)
New-Item -ItemType Junction `
-Path "$env:USERPROFILE\.dsh\profiles\desktop\node_modules\dsh-ai-with-u" `
-Target "<你的克隆路径>"
然后编辑 ~/.dsh/profiles/desktop/package.json:
{
"dependencies": {
"dsh-ai-with-u": "link:<你的克隆路径>"
},
"dsh": {
"profile": {
"bundles": [
// ...
"dsh-ai-with-u"
]
}
}
}
最后重启 DSH。宿主不会热加载 lib/index.js。
⚠️ 不要在 profile 的
cordis.patch.yml里手写插件行——包内cordis.patch.yml的bundle.patch会自动挂载,重复写只会干扰故障定位。
使用
- 左侧栏底部点「角色管理」
- 选一张卡点「启用」
- 开个新会话,像给老朋友发消息一样聊
- 不想玩了点「退出」
想换说话方式(要不要带 (动作))不用回面板:角色簿左栏工具栏有「纯文本 / 文本 + 动作」两枚胶囊,点一下 POST /mode 立刻切;也可以在对话里让模型切(awu_mode)。
开箱自带三张范例卡(丰川祥子 / 阿米娅 / 昔涟,都由插件自己的蒸馏流程产出)。首次启用时逐张幂等种入:目标 card.json 已存在就跳过,所以你改过的卡不会被覆盖;你删掉的预置卡也不会复活——删除时会写一条墓碑(_seed_deleted.json,只记名字与时间,不含任何卡内容),之后每次启动种入都跳过它。想让它回来,删掉那条墓碑记录或手动放回同名卡即可。
角色启用:两种作用域
「启用」有两种,互不覆盖:
| 作用域 | 管谁 | 动作 |
|---|---|---|
| 全局启用 | 所有没绑定的会话 | POST /activate |
| 当前会话启用 | 只绑这一个会话 | POST /bind {name, sessionId} |
决议优先级(写死、可被断言):
会话绑定 > 全局启用 > 未启用
举例:全局启用阿米娅、当前会话绑定丰川祥子 → 这个会话是祥子,其它会话是阿米娅。 全局切换不会动任何已有绑定;取消绑定后该会话自动回落到全局。
两条硬边界(不是限制,是防串台):
- 绑定的卡被删掉 → 该会话视为「未启用」,绝不回落到全局。 否则你会以为在跟祥子聊、其实在跟阿米娅聊——这种「静默串台」比报错难查得多。重新绑定会自动解除这个状态。
- 子代理不扮演任何角色:既不吃会话绑定,也不回落全局。
注入侧不给它 L0 / 纪律包 / L1 / 世界书 / 时间差 / 预设任何一段;工具侧同样拒绝写盘(
awu_*会如实报reason: subagent,不会改用全局角色的身份记日记)。 这是设计选择:子代理是去干活的,让「我是谁」漏进工具链深处,比不注入更坏——而且它一旦能写日记,就会污染主会话那个人格的记忆。
面板怎么显示两态:详情页把「启用」拆成两枚同级按钮并排——「绑定到本会话」和「全局启用」,
不再有一枚藏在 ⋯ 菜单里(藏起来用户得先点开菜单才知道有这个作用域)。两枚同高同区:
绑定走主色,全局走中性实心,谁都不抢谁;已绑定/已全局启用时在原位置换文案(变「解除绑定」/「退出全局」),
不会并排长出第三枚。下方 bindHints 三行说明与按钮顺序一一对应:
「当前会话:X(已绑定)」、「全局:Y」、「子代理不扮演(既不绑定也不走全局)」。
拿不到会话标识时(面板不知道自己在哪个会话)「绑定到本会话」会置灰并说明原因,不会瞎猜一个 id 写进去。
提示词级的隔离是真的:宿主每段提示词都能拿到当前 Agent,所以同一个插件可以按会话渲染不同角色。 技术细节(
PromptSection没有scope、但text(context)里有agent)写在docs/DEVELOPMENT.md§十四。
兼容旧卡与酒馆卡
- 旧 34 字段卡:直接放进
characters/<名>/card.json,读时自动归一成 CCv3,首次保存时才落成新格式并留下card.v1.bak.json - 酒馆卡导入:支持
.png(tEXt/iTXt的ccv3、chara段)、.charx(zip 里的card.json)、裸.json(CCv2 自动升到 V3) - 世界书超限:不再拒绝导入或保存,改为返回 warnings(实际字数 + 上限)并在预览里逐条列出。世界书按需注入,超出预算的部分不会进提示词——书是完整的,只是这一轮用不到那么多
酒馆卡兼容矩阵
一张真卡(CCv3、89 条世界书、98869 字)跑过一遍之后逐字段核对的结论。「支持」= 功能真的生效,不是「没报错」。
| ST 字段 / 特性 | 状态 | 说明 |
|---|---|---|
description personality scenario first_mes alternate_greetings |
✅ 支持 | 原样保留并进 L1;开场白里的 {{char}} / {{user}} 按角色名展开 |
system_prompt / post_history_instructions |
✅ 支持 | 前者作为「角色自带指令」进 L1(优先级低于系统提示词、高于角色描写),后者进收尾提醒 |
mes_example |
✅ 支持(有截断) | 进 L1「示例对话」,单卡上限 1200 字,超出截断并告警 |
creator_notes tags creator character_version group_only_greetings 等元字段 |
✅ 保留不丢 | 不参与注入;认不出的键进 extensions.aiwu.legacy.extra,一个字节都不丢 |
character_book(89 条 / 9.8 万字这种规模) |
✅ 支持(按需注入 + 双预算) | 每轮命中上限 12 条 / 6000 字;constant 条目走独立 allowance;被裁的条目在面板体检里列名字、日志里也带名字 |
use_regex(key 当正则) |
✅ 支持(带护栏) | 模式长度上限、只扫最近文本前 8000 字、明显灾难回溯的构造退化成字面量、坏正则不抛错(否则整轮对话进不去) |
secondary_keys + selective |
✅ 支持 | 四值 selectiveLogic(AND_ANY / NOT_ALL / NOT_ANY / AND_ALL);selective:false 时次级键不参与激活(与 ST 一致) |
position: before_char / after_char |
✅ 支持 | 分两个区块注入:「先读这些设定」与「相关设定」 |
position 写成数字(0 / 1)或 'before' / 'after' |
✅ 支持 | 统一归一到 before_char / after_char(老 ST 导出常见数字写法) |
@D 深度注入 / position: 4 / depth / role |
❌ 不支持(内容不丢) | 不做按消息深度插槽:这类条目仍会注入,只是当普通设定摆(默认 after 块),导入时统计条数并告警 |
constant |
✅ 支持(独立预算) | 常驻走自己的额度(默认 12000 字,卡级可覆盖),不再和关键词命中抢 6000 字。真卡实测 6 条有效常驻 9577 字,全部注入、0 条被裁 |
probability |
✅ 支持 | 顶层 probability 优先,没有再读 extensions.probability(两种写法都有卡在用);稳定哈希,同一轮结果固定,不会每轮抖动 |
scan_depth / token_budget |
✅ 支持 | 前者默认扫最近 4 条(近似 D0..Dn 槽),后者按 1 token ≈ 1.5 字换算成字符预算 |
recursive_scanning |
❌ 不支持 | 字段认了但不执行:递归激活会放大预算与耗时,第一版明确不做 |
regex_scripts(正则脚本) |
🛡 屏蔽(不执行 · 不注入 · 不影响对话,字段保留) | data.regex_scripts 与 data.extensions.regex_scripts 两个位置都保留不删(ST 卡是用户资产,删了将来导出/回灌会缺);同时可证明屏蔽:脚本正文不会出现在 L1 或注入预览里,去掉脚本后提示词逐字相同。导入预览给中性提示「本卡含 N 条酒馆正则脚本,已屏蔽」。口径常量 ST_SCRIPT_STANCE,详见 docs/DEVELOPMENT.md §十三 / §十四 |
depth_prompt |
❌ 不支持(字段保留) | extensions.depth_prompt 随卡保留、写入不丢,但不读、不注入;非空时导入会明确告警「深度提示暂不支持,字段已保留」。需要的话把内容放进世界书常驻条目 |
assets(立绘) |
❌ 不支持 | 导入时剥离并告警:本轮不做立绘落地,卡片其余内容不受影响 |
@@activate / @@dont_activate 装饰器 |
✅ 支持 | 影响该条目是否参与激活 |
@@depth / @@position / @@role / @@probability / @@cooldown 装饰器 |
⚠️ 认识但不生效 | 已知装饰器会从正文剥掉(绝不注入正文),但不改变注入行为;未知 @@xxx 整行丢弃 |
extensions.match_whole_words |
✅ 支持 | 打开后按整词匹配(汉字里不误命中子串) |
三条最容易踩的:①
regex_scripts不是「支持但没用到」,是屏蔽(DSH 侧没有可插入的渲染点,所以不执行、不注入,但字段保留); ② 深度注入(@D/position: 4)不支持,但内容不丢——按普通设定注入,只在位置上让步; ③ 世界书再大也不会整本注入,这是设计而非缺陷。
文件拖放归谁管
面板打开期间,文件拖放归面板处理。
DSH 的聊天区有一个「文件或图片拖动到此处即可添加」的拖放区,它是文档级的:文件在页面任何位置松手,都会被它接住当附件。所以插件打开面板后会接管文件拖放:
- 拖进「导入角色卡」的虚线框 → 走导入预览(不写盘,确认才保存)
- 拖到面板里其它地方 → 提示「把卡拖到虚线框里」,不放行给聊天区
- 拖进面板之外(聊天区自己)→ 正常添加附件,插件不管
为什么这么较真:v0.3.0 真机上出现过「把角色卡拖到面板上,卡被当成附件发进了对话」。宁可拦下来给一句提示,也不能让卡文件混进聊天上下文。
插件同时会替聊天区复位它的拖放状态(拖完主动补一个
dragend)。所以关掉面板之后,聊天区的拖放应该和平时一样——如果你发现关面板后拖文件进聊天区没反应,那是 P0,请报。
预设
预设 = 一组可复用的提示词模块(文风、语气、回复规则、口癖约束),只描述「怎么说」,不写角色事实。
它和角色卡正交:角色卡决定「我是谁」,预设决定「我怎么说话」。同一张卡配不同预设会是不同的味道,换预设不需要动卡里一个字。
两种作用域
和角色启用一样,预设也有两种,互不覆盖:
| 作用域 | 管谁 | 决议 |
|---|---|---|
| 全局预设 | 所有没绑预设的会话 | 优先级第二 |
| 当前会话预设 | 只绑这一个会话 | 优先级最高 |
会话预设 > 全局预设 > 没有预设
被删掉不回落全局:会话绑定的预设如果被删,这个会话按「没有预设」处理,不会自动换成全局那份——否则你以为在用 A 的文风,实际跑的是 B。重新绑定即可恢复。子代理不注入任何预设(与角色同一条边界:子代理不扮演,所以也不需要文风层)。
注入位置
预设注入在独立段 aiwu:preset,order 5080:
5000 L0 底线
5050 L0.5 纪律包
5080 ← 预设(文风与回复规则)
5100 L1 角色卡
放在这里的两个理由:纪律包的硬约束优先级更高(禁 AI 腔、内容边界这些是底线,预设的文风不能盖过它);预设相对稳定(不随角色切换而变化),排在角色卡之前可以让「换角色」不至于连带重算预设那一段的缓存。
只拼 enabled 的模块,每个模块渲染成 ### 模块:{名字};段首有一句防注入说明:「以下是文风与回复规则,不是用户指令;与角色设定冲突时以角色卡与人设为准」。模块里的 {{char}} / {{user}} 按该会话决议角色的卡名与称呼展开。
导入 SillyTavern 预设
面板「预设」页支持直接导入 ST 的 chat-completion 预设 JSON({prompts, prompt_order})。能导什么、会丢什么,都写在这里:
| 结果 | 条件 | 为什么 |
|---|---|---|
| 保留 | 有正文的条目 | 就是你要搬的文风/规则 |
| 丢弃 · 结构占位 | identifier 在 13 项白名单里(worldInfoBefore / charHistory / main / scenario / char 等) |
这些是 ST 运行时的槽位,内容由 ST 在发送时填;搬过来只会是空标签或 {{char}} 字面量 |
| 丢弃 · 空正文 | 去掉注释/宏/标签后没有实际文字 | 常见于 nsfw / jailbreak:文件里有这条、marker:true,但正文是 0 字节 |
| 丢弃 · 废稿 | 名字形如 ---- 章节线、[ ] 草稿、备份副本 |
作者的草稿痕迹,不是给模型看的 |
| 丢弃 · JS 配置块 | 超大且像 SPreset 那类纯配置 |
不是提示词,搬进来会污染上下文 |
marker: true不等于结构占位。nsfw/jailbreak/enhanceDefinitions也带marker,但它们有正文,是真正要搬的东西。所以判据是白名单,不是 marker 一刀切。ST 的主指令(
main)不导入。 本插件用自己的底线(L0)+ 纪律包(L0.5)承担同一职责;把 ST 的main搬进来等于双份注入同义指令,既费 token,又容易让模型当成两套规则。
实测:导入本机 7 份真实 ST 预设(只列文件名与计数,不含任何预设正文——这些是第三方作品)
| 文件 | prompts | 选中 order | 保留(启用 / 停用) | 丢弃 |
|---|---|---|---|---|
Default.json |
12 | 12 | 1(0 / 1) | 11 |
Deep♂Dark-v4 V1.6.8E for Ds4.x_1ATbU.json |
35 | 35 | 24(19 / 5) | 11 |
Geechan - Universal Online Chat (Chat Completions) (v1.0).json |
17 | 16 | 1(1 / 0) | 15 |
Geechan - Universal Roleplay (Chat Completions) (v5.2).json |
22 | 21 | 7(0 / 7) | 14 |
Pura's Director Preset (SillyBunny).json |
47 | 47 | 11(1 / 10) | 36 |
Pura's Director Preset 15.1 (SillyBunny).json |
47 | 47 | 11(1 / 10) | 36 |
TLD Card Conversion Preset (Standalone).json |
26 | 25 | 9(7 / 2) | 16 |
表里有两点值得注意:
- 保留数 ≠ 启用数。
Geechan Online保留 1 条、其中启用 1 条;Geechan RP保留 7 条但全是停用——ST 里「在配置里存在」和「实际生效」是两件事,导入会把停用状态一起带过来(面板里可以再打开)。 Default.json只保留 1 条,这是正确结果。它是 ST 的「出厂默认」,main是结构占位(我们已用 L0 + 纪律包承担同一职责),nsfw/jailbreak在这份文件里正文是 0 字节。所以 12 条进、1 条出(Enhance Definitions,且是停用的),丢弃 11 = 结构占位 9 + 空正文 2。不是解析坏了。
数据存储
默认 ~/.dsh/ai-with-u/,可用 AIWU_STORAGE_ROOT 覆盖:
~/.dsh/ai-with-u/
├── _index.json # 角色索引
├── _active.json # 当前启用状态(含 mode / discipline 偏好)
├── _sessions.json # 会话 → 角色绑定(会话作用域启用)
├── _presets.json # 预设索引
├── _presets_active.json # 预设启用态(全局 + 每会话)
├── _seed_installed.json # 哪些卡是「预置卡种进来的」(决定删卡时写不写墓碑)
├── _seed_deleted.json # 预置卡墓碑(只记名字与时间,不含卡内容;重启不再种回)
├── presets/<id>.json # 单个预设正文(id = p_ + 8 位 base36)
└── characters/<角色名>/
├── card.json # CCv3 角色卡
├── card.v1.bak.json # 迁移前原件,回滚就拷回来
├── diary.md # 日记,按天分组
└── session.json # 会话状态与滚动摘要
session.json 是「这一册读到哪了」,认得这些字段:
| 字段 | 含义 |
|---|---|
recent / turn_count / last_ts |
最近几轮、累计轮次、最后一次时间 |
mood / topic |
此刻的情绪、未竟的话题(写日记时一并落盘,下一轮 L1 用) |
mode |
text 或 text+action,交互模式的权威位置 |
intimacy |
关系档位,五档之一:陌生 / 认识 / 熟悉 / 亲近 / 亲密 |
relation_log |
档位变化留痕:{ at, from, to, why? },最多 50 条,超出丢最旧 |
交互模式按 session.json → _active.json → text+action 的顺序解析:会话里用 awu_mode 切的档立刻生效,_active.json 只作没会话记录时的兜底。
纯 markdown / JSON,可读、可改、可备份、可迁移。不联网、不注册、不收藏数据。
接口安全
面板接口 /_dsh/ai-with-u/* 只接受本机来源:Host 必须是回环或实际监听地址,Origin 必须同源,Sec-Fetch-Site: cross-site 直接拒绝;写接口强制 POST + application/json。浏览器里任何网页都无法跨站触发删除或读取卡片。
如果确实要通过隧道/反代从别的域名访问本机 GUI,用环境变量显式放行:
AIWU_TRUSTED_HOSTS=my.tunnel.example
默认拒绝,不靠猜。
能力清单
模型工具
9 个按需挂载 + 1 个常驻门控(awu_card_save,见本节末),合计 10 个。
挂载判据是「全局启用 或 任一会话还有有效绑定」,不是「启用角色才挂载」——会话绑定态下模型同样要能记日记 / 查关系;
两个条件都不成立才整批卸下(src/index.ts:269-283)。
| 工具 | 用途 |
|---|---|
awu_diary_write |
追加日记(可同时记下 topic / mood) |
awu_diary_read |
读取 / 筛选日记 |
awu_whoami |
查询当前身份 |
awu_forget |
忘记某事(日记 + 会话 + 角色卡三处同步) |
awu_diary_edit |
就地改写日记条目 |
awu_session_reset |
全新开始 / 彻底重置 |
awu_relation |
查关系档位依据;带 set 时按里程碑写回档位 |
awu_mode |
切换纯文本 / 文本 + 动作 |
awu_opening_next |
换一句开场 |
另一个是 awu_card_save:常驻注册、由武装期门控(不是条件挂载)。点「蒸馏角色卡」后开一个两小时的时间窗,
窗口内它才真正可用;窗口外调用一律返回 not-armed,对模型来说和「没有这个工具」等效。保存一次后自动失效,
武装状态不落盘——重启即失效,避免模型在任何会话里突然获得写卡能力。
REST 接口
32 条,前缀 /_dsh/ai-with-u/。
overview preview characters character character/save character/import character/delete
activate deactivate bind unbind mode discipline relation relation/set
opening/next opening/state diary/save diary/edit forget session/reset session/commit
presets preset preset/save preset/delete preset/import preset/activate
distill/flow distill/arm distill/disarm distill/state
/character 与 /characters 同时返回 card(34 字段视图)与 ccv3(完整卡);/character/save 收 { name, card?, ccv3? },只给 card 时保留既有世界书与 extensions。
/bind 与 /unbind 就是「当前会话启用」:POST /bind {name, sessionId} / POST /unbind {sessionId},返回 { ok, bound, character }。POST /activate 仍然是全局启用语义(向后兼容)。GET /overview?sessionId=… 会带上 sessionId、binding(本会话绑了谁)与 bindings 摘要,面板据此判断「当前会话启用」能不能点。
GET /preview 是纯只读快照:返回 { active, mode, discipline, segments[] },segments 现在六段(aiwu:baseline / aiwu:discipline / aiwu:preset / aiwu:active-character / aiwu:lore / aiwu:clock,按 order 升序;未启用角色时 text: ''、chars: 0)。文本走 PromptController 的真实渲染路径,不在预览里另拼一份——拼第二份等于给「预览与真实注入漂移」开口子。
/presets 列索引、/preset 读正文、/preset/save|delete|import|activate 是写操作;/preset/import 收 ST 预设 JSON 并返回 { modules, dropped, warnings }——丢弃条目带原因,面板据此解释「为什么只剩几条」。
世界书的改动必须随
ccv3发:只发card时后端以磁盘上的旧卡为底重投影,character_book按原卡保留——改动会被静默吞掉,还会提示保存成功。
awu_relation 的口径是「查看不写回」:用户只是问「关系如何」时只读档位、不改档;只有里程碑事件(第一次约定、第一次争吵、称呼改变、表白)才带 set 写回,并在 relation_log 留一条。对话里不出现「好感度 +1」这类数值。
面板与提示词
- 注入预览 —— 六段真实渲染的段名 / 字符数 / 正文 + 总字符(含预设段),纯只读快照
- 记忆回顾 —— 日记关键词搜索、按月折叠、导出 markdown
- 导入角色卡 —— 角色簿左栏入口,吃 PNG / CHARX / JSON,先预览后落盘;世界书超限只警告不拦
- 自创角色卡 —— 三格表单落骨架卡,可选「让 AI 补全人设」(走当前会话,AI 先问 3–5 个问题)
- 世界书编辑 —— 角色卡编辑器第 11 分区,条目增删改 + 体检
- 对话模式 —— 角色簿工具栏的「纯文本 / 文本 + 动作」胶囊,点了立刻生效,不需要启用角色
- 纪律包 L0.5 ——
off/lite/full三档,默认 lite,偏好落盘、退出角色也保留;左栏工具栏三档胶囊就是它的入口(与对话模式胶囊同区、紧跟其后) awu_mode—— 对话里直接切纯文本 / 文本 + 动作,不需要回面板
开发
npm run build # 宿主 tsc + 前端 tsc + 打包 + 十套自检(各套通过项总数以 verify 输出为准)
npm run verify # 产物形状 / 新鲜度 / 孤儿产物
npm run verify:bundle # 客户端产物加载
npm run verify:ui # UI 纪律:模板串反引号/动画引用闭环/时长走令牌/选择器带值
npm run verify:a11y # 无障碍:div+onClick/图标标签/错误可播报/键集对称/焦点环 token
npm run verify:wiring # 接线体检:26 组契约(后端做了前端没接 / 验收项回归)
npm run verify:tavern # 卡格式 / 迁移幂等 / 世界书预算 / ST 兼容
npm run verify:distill # 蒸馏 / 补全武装与 kind
npm run verify:import # PNG / CHARX / JSON 导入解析 / 字段保真
npm run verify:session # 会话、指令、纪律包、关系、绑定、预设与预览只读
npm run verify:host # 真实 cordis 宿主装配
npm run typecheck:client # 只查浏览器侧类型
npm run build 串的顺序是:tsc(宿主) → tsc(前端) → esbuild 打包 → verify-bundle → verify-ui → verify-a11y → verify-wiring → verify → verify-tavern → verify-distill → verify-import → verify-session → verify-host。任何一项非 0 退出就整体失败。
徽章上的数字 = build 串里十套自检的通过项之和,失败项必须为 0、skip 也必须为 0。各套总数与合计以 verify 输出实测为准,不在文档里回填逐套分解清单——1025 时代的教训:这套数字从手写成 515 起,先后漂移出 515 / 981 / 1025 三套口径(981 与 1025 曾一度并存),自检断言增减是常态,宁可每次实测也不要维护第二份会过期的清单。
当前徽章 1107 是 2026-11-02 契约对齐轮的全链实测回填:bundle 9 + ui 7 + a11y 8 + wiring 81 + verify 16 + tavern 330 + distill 121 + import 58 + session 351 + host 126。本轮相对上一版(1047)净增 60 条:verify-wiring +9(§[26] 纪律包面板入口 + 两个作用域同级)、verify-tavern +24(ReDoS 九族表驱动 + 正常写法反例组 + 正则编译缓存 + 卡侧未知宏告警)、verify-session +27(日记无分节往返 + 子代理工具守卫 + 预置卡墓碑 + GET /character 的 diag)。下一次增删断言后它就会过期——重新跑全链按实测改徽章,不必另行更新本段。旧徽章的 515 用的是更窄的口径(只数六套,不含 verify 与 verify-wiring),所以数字变大不全是因为新增了断言。
verify-wiring 补的是其余七套查不到的盲区:类型检查只问「类型对不对」,不问「有没有人调用」。一个 17 KB 的世界书数据层可以零引用地躺在仓库里,一个 POST /character/import 可以注册完就没下文——这两种状态下 tsc、打包、其余自检全绿,但用户打开面板什么都看不到。脚本只做静态扫描(读源码找路由字符串与调用点),失败文案写清「缺什么 + 该找谁」。最后一组会把 lib/prompt.js 拿回来跟源码逐字比纪律包文本,抓「改了源码没重编」;产物不存在或比源码旧时跳过,不报红。
抽查
lib/client.js时别用中文去 grep:esbuild 默认charset: ascii,打包产物里的中文一律转成\uXXXX(如「导入角色卡」=\u5BFC\u5165\u89D2\u8272\u5361)。功能无损,但按字面搜中文会误判「产物里没有」。
宿主 API 的类型直接来自 @deepseek-ai/dsh-system-prompt / dsh-tools / cordis 的 devDependencies,inject、PromptSection、ToolDefinition 都有编译期校验——契约漂移会在构建阶段报错,而不是运行时「failed 但不给原因」。
目录结构
src/ 宿主侧:存储、提示词、工具、路由、格式层、导入、蒸馏
client/ 浏览器侧:面板、编辑器、日记、指南、统计
scripts/ 构建与自检(十套 verify-*,共享夹具在 helpers.mjs)
docs/ 设计文档与开发记录(含踩坑大全)
presets/ 开箱角色卡
distill/ 蒸馏流程文档(工具按需读)
tools/preview/ 脱离宿主的面板预览沙盒
tools/qa/ 真机取证工具链(CDP 截图矩阵 / 崩溃与折叠复现)
常见问题
会不会覆盖我 AGENTS.md 里的人设?
AGENTS.md 是以 user 角色消息注入的,插件用的是 system prompt,层级更高。L0 里显式写了边界:只让「我是谁、我怎么说话」,不让「我怎么做事」——代码规范、工程流程、安全约束照常生效。
退出后真的什么都不留吗?
五段(L0 / L0.5 纪律 / L1 / 世界书 / 时间差)在退出后都返回空串,宿主会过滤空段。awu_* 工具同时全部卸载。数据文件仍然在磁盘上,但不参与任何提示词组装。
唯一的例外是预设段:预设是宿主侧文风层,不随角色退出(详见「分层注入」一节的说明)。
为什么不能用 dsh plugin CLI 安装?
desktop profile 由 Electron 独占管理,CLI 会直接报 managed exclusively by the Electron application。按上面的 junction + profile 配置方式装。
删了角色,日记还在吗?
删除时可以选「只删卡」或「连记忆一起删」。只删卡时 diary.md 留在磁盘上,重新创建同名角色还能接上。
提示词到底占多少 token?
启用态约 2.8–3.3 KB 字符量级(中文按字计),未启用 0。实测:丰川祥子的卡 + lite 档,整段渲染 3286 字符。默认 lite 档的纪律包占 555 字符;切 off 全省,切 full 多 181 字符。世界书常态只注入一两百字符。重内容(卡里的详细设定、长日记)不会无条件进提示词。
为什么我的大卡能导入了,但设定只用得上一部分?
因为世界书是按需注入的,不是整本塞进提示词。命中预算默认 12 条 / 6000 字(LORE_DEFAULTS),超出预算的条目这一轮就不进上下文——卡本身一个字都没被改,磁盘上仍是完整的世界书,你在编辑器里能看到全部条目。
constant: true 的常驻条目走独立额度,不和关键词命中抢那 6000 字(真卡实测:7 条常驻共 10512 字,修之前有 2 条被静默挤掉,而常驻正是「必须一直生效」的那类)。
哪里能看到被裁的条目:打开角色卡编辑器 → 世界书分区顶部的体检行,会按等级列出「这一轮被预算裁掉的条目」;宿主日志里也会带条目名(只带名字,不写正文)。想提高命中率就把关键词写准、把常驻留给「必须一直生效」的设定。
这是刻意的:一张 9.8 万字的世界书全量注入,要么撑爆上下文、要么每轮烧掉一大笔费用,而真正用得上的往往只有当轮提到的那几条。
现在导入/保存这类大书只会出警告(附实际字数与上限),不再拦你——v0.3.0 之前这里是硬拦,属于过严。
为什么我的卡里正则(regex_scripts)不起作用?
因为本插件不执行酒馆的正则脚本,而且这不是「暂时没接」——是宿主侧没有可插入的点。
酒馆的 regex_scripts 有两类用法,我们分开说:
- 提示词侧(发送前改写 prompt 文本):能做但没做。理由很简单:它改的是发给模型的内容,属于「用户不知道自己在看什么」的黑箱,真出问题时极难排查。
- 显示侧(收到回复后改写显示文本,比如把
*笑*变成斜体、把某些词替换掉):做不了。DSH 的客户端插槽conversation.chat.node只能新增聊天行,改不了已经渲染出来的助手消息;agent/request钩子只管 provider / model / reasoningEffort 路由,不碰文本;插件规范也明写不许读写组件外的 DOM。
生态里的做法是把这个能力交割出去:dsh-tavern 从 2.7.x 起把显示层整块交给 dsh-muv-engine(自己那套渲染器删掉了,因为两边的 beautifyContentEl 会互相覆盖)。想用正则美化显示,装 muv 系引擎即可,本插件不重复造第二套。
你的卡不会因此丢东西:regex_scripts 原样保留在卡里(data.regex_scripts 与 data.extensions.regex_scripts 两个位置都不动),只是不被执行。导入预览会给一条告警说清这一条——留着字段是为了你将来导出/回灌酒馆时不缺东西,「以为它在工作」则用告警解决,而不是删数据。
如果你依赖的是提示词侧的正则效果,请把规则改写进角色卡的 system_prompt 或世界书条目——那是我们能保证生效的地方。详见 docs/DEVELOPMENT.md §十三。
「当前会话启用」和「全局启用」有什么区别?
两个作用域,互不覆盖:
- 全局启用:所有没绑定的会话都用它。切换全局不影响任何已绑定会话。
- 当前会话启用:只绑你点的这一个会话,别处照旧。
决议顺序写死:会话绑定 > 全局启用 > 未启用。所以「全局阿米娅 + 本会话祥子」= 这个会话是祥子、其余是阿米娅。
两条边界值得记住:
- 绑的卡被删掉,那个会话就当作未启用,不会偷偷回落到全局——否则你会以为在跟祥子聊、其实对面是阿米娅。重新绑定即可恢复。
- 子代理不扮演任何角色,也不继承父会话绑定:它既不注入人设/预设,工具侧也不落盘。这是有意为之,防止人设意外漏进工具链、顺带污染主会话的记忆。
面板上两态分行显示(「当前会话:X(已绑定)」/「全局:Y」),点哪个都会就地提示影响范围。如果面板拿不到会话标识,「当前会话启用」会置灰——它不会瞎猜一个 id 写进去。
面板说卡里有 N 条正则脚本、已屏蔽,会有什么影响?
没有任何影响,而且这是可以验证的:
- 脚本不会被执行(提示词侧与显示侧都不做);
- 脚本正文进不了提示词:自检里拿一个特征串做标记,跑完整 L1 与「注入预览」,产物里搜不到它;
- 对注入零影响:同一张卡去掉
regex_scripts后,L1 与预览的文本逐字相同; - 字段原样保留:你将来把卡导出回酒馆,脚本还在。
换句话说,看到的角色表现与「这张卡本来就没有正则脚本」完全一致。如果卡的表现依赖这些脚本(比如靠正则把某些词换成斜体、或替换成别的台词),那部分效果不会出现——想保留效果就把它改写成角色卡的人设描述,显示层的正则美化需要装 muv 系引擎(本插件不做第二套渲染器,原因见 docs/DEVELOPMENT.md §十三)。
致谢
- 原项目:ai-with-u
- 角色卡格式:Character Card V3
- 宿主:DeepSeek Harness
License
MIT