john-walks-slow/dsh-aivn

dsh-aivn

AIVN stage engine inside DeepSeek Harness: a workspace is a play, the playwright preset writes Stage DSL, and a Stage tab in the conversation renders it live (backgrounds, sprites, dialog, stop-point choices, line-by-line TTS). AIVN 舞台引擎的 DSH 插件。

미분류ai-agentdeepseek-harnessstage-dsl

dsh-aivn

简体中文 · English

把 AIVN 的舞台引擎装进 DSH。

一个工作区就是一部剧目:在里面开会话、选「剧作家」预设,对话页就多一个「AIVN」tab (只给剧作家会话——搭台助手的会话里没有这个 tab), 剧作家写的 Stage DSL 被实时演出来——背景、立绘、台词条、停止点选项都在这里;玩家点一条选项, 这句话回到剧作家手上,它接着往下写。

这是 AIVN 的 DSH 适配版。 同名的 stage-ai
是独立版(自包含桌面应用 / 独立 Web 站)。两者共用同一套引擎与领域
(@aivn/core、@aivn/stage),差别只在宿主:独立版有路线树、周目存档与完整工坊,
这边用 DSH 的原生会话、文件侧栏与技能机制。**想要完整产品体验用独立版;
已经在用 DSH、想要 AIVN 能力而不另开一个应用,用这个插件。**

装

包是 DSH 的 bundle(package.json 里声明了 dsh.bundle.patch):

dsh plugin --profile web add dsh-aivn

从 GitHub 直装也行——lib/ 是跟踪的构建产物,所以不需要构建脚本、也不会触发 pnpm 的 allowBuilds 拦截:

dsh plugin --profile web add github:john-walks-slow/dsh-aivn
  • 手工挂:在 profile 的 cordis.patch.yml 里加一行:
- insert:
    - id: dsh-aivn
      name: dsh-aivn
      config: {}
  • 客户端半边(「AIVN」tab)挂在 conversation.view 槽上,需要 dsh-web-app。它在

package.json 的 dsh.client{platform:"web"} 里声明,不走 Loader。

可配的有六处——语音、联网检索、生图、配乐、命令行、按预设禁用工具;前五项都不配也能照常跑(只是那几项能力没有,对应工具不注册、提示词不提),禁用项默认空。

两种改法,改完都就地生效(不用重启 dsh):

  • 界面(推荐):侧边栏 Plugins → dsh-aivn 里的配置卡。分两段——P0 决定能力开不开(Key 与后端),P1 是默认就能用的口味项;顶部一行状态灯显示当前哪几项能力是开的。密钥只写不回显,卡片上另有「清除已存 Key」。
  • 配置文件:profile 的 cordis.patch.yml。界面保存的也是这里边——两种改法落在同一份配置上。
- id: dsh-aivn
  name: dsh-aivn
  config:
    ttsKeys: ["<your-fish-audio-key>"]     # 多把可轮询,摊免费额度(界面上逗号分隔)
    ttsBaseUrl: https://api.fish.audio      # 默认值,一般不用写
    ttsModel: s2.1-pro-free                 # 默认值
    ttsConcurrency: 2                       # 同时合成的上限
    searchKeys: ["<your-exa-key>"]          # 给了它搭台助手才有 web_search
    searchBaseUrl: https://api.exa.ai       # 默认值
    shell: false                            # true = 给搭台助手放开 bash
    imageFormat: gemini                     # gemini | openai | modelslab(接口格式,不是产品名)
    imageBaseUrl: http://127.0.0.1:38000    # 本机 flow2api;官方 API 或别的网关填它自己的地址
    imageModel: gemini-3.0-pro-image
    imageSize: 1K                           # 1K / 2K / 4K(K 大写),或字面像素 1536x1024
    imageApiKey: ""                         # 本机网关不校验就留空(留空 = 不带鉴权头)
    imageTimeoutMs: 180000
    musicBaseUrl: http://127.0.0.1:38000
    musicModel: flow-music-lyria-3.5
    musicApiKey: ""
    musicTimeoutMs: 240000
    nsfwModel: ""                           # 限制级通道的专用模型(provider/model 最稳);逐剧目可用 play.json 覆盖
    nsfwPrompt: ""                          # 限制级创作指引后面的补充口径;逐剧目可用 play.json 覆盖
    playwriterDisabledTools: ""             # 按预设禁用工具(逗号分隔的工具名),默认空 = 两预设一致
    stagehandDisabledTools: ""

配置面是扁平的 21 个字段(DSH 设置界面的控件只认顶层字段,嵌套写法它寻址不到):

配置项 · 默认 · 给了会怎样 · 环境变量兜底 ttsKeys · 空 · 舞台有语音开关,两个预设多一个 list_voices(音色库与合成共用这池 key) · DSH_AIVN_TTS_KEYS ttsBaseUrl / ttsModel / ttsConcurrency · Fish 官方 / s2.1-pro-free / 2 · — · — searchKeys · 空 · 两个预设多一个 web_search(一次调用同时拿回结果与正文) · DSH_AIVN_EXA_KEYS searchBaseUrl · https://api.exa.ai · 换代理 / 镜像时改 · — shell · false · 两个预设的 tool-bash 行真装上(默认关,与 AIVN 的 defaultOff 同口径)。bash 是预设的「一行」,只对新建会话生效 · — imageFormat / imageBaseUrl / imageModel · — · 地址与模型都给齐才有 generate_image / generate_asset(两个预设都有);cut / import_asset 不依赖它。格式是接口协议形状,不是产品名 · — imageSize · 1K · 档位 1K/2K/4K(K 大写)或字面像素 1536x1024;gemini 格式只认档位,openai/modelslab 两种都收(modelslab 封顶 1024) · — imageApiKey / imageTimeoutMs · 空 / 180000 · 本机网关不校验就把 key 留空(不带鉴权头);出图慢就把超时调大 · — musicBaseUrl / musicModel · — · 都给齐才有 generate_bgm(只有 Gemini 形状,没有 format) · — musicApiKey / musicTimeoutMs · 空 / 240000 · 同上;一首曲子实测约 84 秒 · — nsfwModel · 空 · 限制级通道的专用模型:写 provider/model 最稳(如 cpa/某模型),只写模型 id 会在已注册的 provider 里找;留空 = 进通道不换模型(提示词照旧切)。逐剧目可用 play.json 的 agents.playwriter.nsfwModel 覆盖 · — nsfwPrompt · 空 · 限制级创作指引后面的补充口径;逐剧目可用 play.json 的 agents.playwriter.nsfwPrompt 覆盖 · — playwriterDisabledTools / stagehandDisabledTools · 空 · 按预设禁用工具(逗号分隔的工具名,取值为「Agent 工具」清单里的工具名)。默认空 = 两预设工具面完全一致 · — 抠底调参 · 见「素材生成」 · DSH_AIVN_CUTOUT_TOLERANCE / _KEY_SMOOTH / _EDGE_BAND / _SPILL / _SOLID_RESIDUAL 五个环境变量 · 同名环境变量

key 两处都没有 = 那项能力停用:工具不注册、提示词里也不提。两个预设、一条 /new-play 命令、 /aivn 路由都是固定的,不可配。

改完的配置由 loader 原位提交进运行中的引用,插件据此重建能力面:那一轮的工具装/卸、搭台助手 提示词里门控的那几章、语音后端,开着的会话下一轮就用新的;唯一例外是 shell——它翻的是预设行集, 已开着的会话保持原样,新建一个才吃到。

开发期 @aivn/core 与 @aivn/stage 走 file: 依赖(指向 stage-ai 仓库)。发布版换成版本号。

用

  1. 新建会话,在座位 chip 上选 剧作家(备料的那位是 搭台助手)。

预设是在会话开始时定下的,中途换不了——想换就新建一个会话。

  1. 会话的工作目录根要有 play.json。没有的话舞台会明说「这个会话的工作目录里没有 play.json,

还不是一座剧目」——那就先把它立起来,见下。

  1. 说一句话开演。之后的一切都在舞台上发生——那句话会把剧目的开局指令(`opening`)一并带给剧作家,

所以不必照着剧本念开场白,随便说句什么(「开始吧」)就行。

「AIVN」tab 是会话头部的第三个 tab(Chat / Trajectory / AIVN),只在这个会话是剧作家预设时出现。

新会话怎么进舞台

输入框上方那行里,「剧作家」chip 右边有一枚「开始游戏」——选好「剧作家」预设,工作目录里 也有 play.json 的时候它就出现。按一下:

  1. 把剧目的开局指令(play.json 的 opening,缺省「(游戏开始,请演出第一轮)」)投给剧作家

——与舞台上那枚「开演」按钮发的是同一条消息;

  1. 剧作家起笔、页签列被带出来的那一刻,这一页自己切到 AIVN。

只替你切这一次:之后手动切回 Chat 不会被抢回来。切过之后这个选择由 DSH 自己记住 (同一个会话再打开、刷新页面,都还在舞台上)。

为什么需要这一枚:会话还没发过任何消息时,DSH 连页签列都不画,所以那时候没有「AIVN」可点。
「开始游戏」正是替你把「发第一句话」和「切到舞台」合成一次点击。

不想用它也行:自己在输入框里说一句话,然后点「AIVN」tab,效果一样。

全屏

舞台左上角有一枚「全屏」键(右上角是导演工具栏)。按下去整块舞台——画面、台词条、 演出工具栏一起铺满屏幕:

  • 桌面与 Android 走浏览器的原生全屏,连状态栏一起收掉。退出按那枚键(字面会变成

「退出全屏」),或按浏览器自己的 Esc。

  • iPhone 上的 Safari 不支持元素全屏(只有 iPad / macOS 的 Safari 支持),那一档自动退到

「铺满视口」:盖住 DSH 界面,但收不掉 Safari 自己的地址栏——这是该平台给得出的上限。

玩家按了 HIDE(净画面态)时这枚键跟着台词条一起藏起来。

导演工具栏(提示 / 改写 / 重写)

舞台右上角那排键是给导演用的,三轮动作都走宿主侧的同一条路:

动作 · 它会做什么 · 什么时候能点 提示 · 引导 · 把这句话排进剧作家的下一轮。正在演的这一拍不受影响,停止点还在,玩家照选不误 · 随时 提示 · 打断 · 先停下正在写的这一轮,再用这句话接着写 · 随时 改写 · 把最后一步写出来的内容作废,让剧作家按你交代的那句重写一遍(弹窗里预填的是当前这一句,改完提交) · 不在写的时候 重写 · 把这一拍整段作废,让剧作家重新写(指令可留空 = 纯重写) · 不在写的时候

两件要紧的事:

  • 改写 / 重写和 AIVN 那边的同名功能不是一回事。AIVN 有一份自己的脚本模型,「改写」是直接改

那一行脚本、不经过模型;DSH 这边的剧本就是剧作家的输出、会话日志才是权威,所以「改这一句」 只能是让剧作家重写这一拍:插件往会话里追加一条替换标记(surfaceOp.replace,与 dsh-rewind 同一个手法),被作废的那一段从上下文里真的消失,模型看不到旧内容。 代价是一次重写要花一轮 LLM(AIVN 的原地改写不花),换来的是上下文不会和画面对不上。

  • 生图不在这一排:出图在搭台助手那条会话里做(generate_asset / generate_image),舞台这排键点了也没有落点,

所以不摆。

导演说的话带自己一支来源(dsh-aivn-director),不会变成一行玩家台词:舞台的时间线上 只有玩家真正说过的话。剧作家那边由系统提示词的《演出契约》第 7 条交代「(导演…」是片场喊话。

限制级(NSFW)通道

剧情推进到亲密 / 成人内容时,剧作家调 enter_nsfw 把这一段交给限制级专用模型(插件配置 nsfwModel,逐剧目可用 play.json 的 agents.playwriter.nsfwModel 覆盖;留空 = 不换模型)。 进通道后:

  • 提示词换成《限制级(NSFW)创作指引》(含剧目的 memory/always/nsfw.md 与补充提示词),

并先投一条合规声明;舞台左上角亮起「限制级」角标;

  • 退出走 exit_nsfw:引擎用主模型把这一段改写成 1-3 句全年龄摘要,再把这一段从

模型可见面上折掉——此后主模型只看得到那句摘要,而日志与舞台照旧留着原文;

  • 中途进出、冷开、分支、回退都从会话日志重推:段内重建角标照样亮,退出之后不再亮。

对模型来说这一段读作「已收束为一句摘要」;对人类这边什么都不藏。

回退与分支:舞台跟着会话走

  • 回退(dsh-rewind 的「回退到这里」):它往会话里追加一条替换标记,插件收到就把舞台

整段重投影一遍——回退之后刷新或重新打开,「AIVN」tab 演到的就是你回退到的那个点。

  • 在新对话中分支(DSH 原生的分支):新会话带着父会话的前缀,插件这边不做任何事——

第一次打开它的舞台时,帧流里一帧都没有,插件就从会话面重铺一遍,于是新会话停在分叉那一刻 (连同那一刻的停止点)。

  • 插件不做自己那一套谱系分支(AIVN 的路线图 / 旧枝那套):要分岔就用 DSH 原生的两样。

进行中浮层

右上角那枚「进行中」徽标(有东西时才出现)里是两样:排队中的提示(还没进会话的那些) 与正在合成的语音。出图与配乐不在里面——插件里它们是同步的工具调用,没有回合之外的作业可报。 玩家按了 HIDE 时它跟着舞台一起收起来。

起一座新剧目

工作区里没有 play.json 就是「这还不是一座剧目」:写一份出来就是立起一座剧目,没有生成器。 字段与布局写在两个预设的提示词里(《剧目文件》那一章,同一段文本),模型用文件工具直接写。

  • 人:输入框敲 /new-play 雨中的旧校舍。不给剧目名就取工作目录名——这一步替你写 play.json

与骨架文件,不花一次 LLM 往返。