dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
为 DeepSeek Harness 提供的短篇集创作插件,提供「世界观 → 多短篇集」项目格式,共享人物档案跨集引用,SHA-256 资产修订号保证并发一致,所有写入经原生一次审批。
状态:
0.2.1。目标运行时dsh 0.2.1-rc.2(engines.dsh: ">=0.2.1-rc.2 <0.3.0-0")。0.2.0 是运行时适配版:领域模型、工具面与面板功能与
0.1.1一致,改动集中在依赖线、准入声明与构建/校验链。0.1.1在0.2.1-rc.2上会被静默跳过(见下)。已落地:项目文件层 · 模型面工具(V1 可写 / V2 提议)· 内联预设声明 · 只读工作台 · 非权威提案收件箱 · 跨集巡检与人物编年史 · 插件卡片的中英文标题/说明与图标(
locale/+icon.svg)。158 个测试,node scripts/release-check.mjs一条命令跑完构建链、测试与打包面不变量。尚未落地:面板内改稿(刻意不做,见下)· 批量跨集写作业 · 人物状态的跨集累计(缺承载字段)·
./agent-v2还没有挂进预设。升级候选与排期见docs/features-and-progress.md§10。
0.1.1 升级:它不会报错,只会消失0.1.1 的 peer 全写 0.1.0-rc.6(当时唯一能装到的线),且没有 engines.dsh。dsh 的准入规则是:
有 engines.dsh → 由它单独裁决,peer 一律忽略
否则 → 每个 @deepseek-ai/dsh / dsh-* peer 都必须满足运行时版本
于是运行时升到 0.2.1-rc.2 后,16 条 peer 全部不满足 → 整个 bundle 被跳过,只进 skippedBundles,
界面上不会出现任何错误:侧栏没有「短篇集工作台」,预设列表里也没有「短篇集作家」。
profile 里那条 …@0.1.1: ["0.2.0-rc.2"] 的豁免救不了它 —— 豁免按"包名@版本 + 精确 DSH 版本"双键控,
DSH 一升级就不再继承。
0.2.0 的修法是让 engines.dsh 单独裁决,因此不需要任何豁免。实测(用目标运行时自己的
evaluatePluginCompatibility,安装期与启动期共用这一个函数):
| manifest | 结果 |
|---|---|
0.1.1 形态 | {"requirements":{"@deepseek-ai/dsh-agent":"0.1.0-rc.6"},"exempted":false} → 拒绝 |
0.2.0 形态 | undefined → 接受 |
本包在 DeepSeek Harness 上提供 「世界观 → 多短篇集」 项目格式:世界观 拥有共享人物档案与共享设定;短篇集 是该世界观下一部独立的作品,各自拥有蓝图、时间线与章节;同一位角色可以在多个短篇集里出演,身份通过稳定 id 引用,不复制、不重定义人物资料。
对标形态是「同一批角色在多个独立短篇里出演」——像偶像大师闪耀色彩那样。
| 决定 | 内容 | 为什么 |
|---|---|---|
| 身份即目录名 | 短篇集的身份是 collections/<slug>/ 目录名;blueprint.json 里不存 slug,读取时由目录注入 | 消除"两处身份不一致"的可能;slug 形态约束同时阻断路径穿越 |
| 修订号即并发 | 每个非空资产的修订号 = 规范化 UTF-8 字节的 SHA-256;替换请求只带"上次读到的修订号" | 模型永远不需要复述权威旧文本,也就不可能因复述出错 |
| 落盘即规范字节 | JSON 经严格 schema 校验(未知字段直接拒绝)后按 2 空格缩进 + LF 重新序列化;正文 LF 归一化 | 换排版重提同样语义 ⇒ 修订号不变 ⇒ 不产生假冲突 |
| 写入必经审批 | 任何写入都要经过 Harness 原生一次性审批;浏览器侧不暴露任何 mutation 通道 | 模型不能单方面改稿;审批卡片展示的就是即将落盘的内容 |
另外两条来自真机试用的决定:
serial_read kind="audit" 会把"有正文没蓝图"的章逐个点出来。authoritative: false;要真的改,仍然只能走原生审批。| 入口 | 文件 | 暴露什么 |
|---|---|---|
| 根入口(Host) | src/index.ts | 只声明 workspaceRegistry 依赖;不挂模型面工具 |
./agent | src/agent.ts | V1:serial_read + serial_apply_change(原生一次性审批) |
./agent-v2 | src/agent-v2.ts | V2:serial_read + serial_propose_change(提案式,尚未挂进预设) |
./client | src/client/index.ts | 侧栏图标 + 只读工作台面板 |
Client 半边注入 ['slots', 'remote', 'remote.workspaceFiles']:slots 用于注册面板,remote 是 dsh 的 Typert Remote 载体,remote.workspaceFiles 保证只读文件命名空间已绑定。
dsh 0.2.0-rc.2 曾有一个上游缺陷(讨论 #5926):第三方插件注册 HTTP 通道时,connection.rpc.handle 与 webServer.register 会走到同一段嵌套 inject,而 dsh-client-connection 自身的 inject 是 ["credentials"] 而非 ["webServer"],激活时报:
cannot get property "webServer" without inject
在 0.2.1-rc.2 上这条已消失:dsh-client-connection 取 webserver 改成了可选查表 ctx.get('webServer'),注入断言不再触发。也就是说,现在自建通道是可行的。
但本插件仍然不建:workspaceFiles(@deepseek-ai/dsh-api-workspace-files)正好满足只读工作台的全部需要 —— 它不暴露任何 mutation 操作、路径以会话工作区为根(面板只传 .serial/... 这类相对路径)、并带文件变更订阅。上游缺陷消失只是解掉了一个限制,并没有造出换掉这条通道的理由。
因此 Host 入口不注册任何 loopback 通道;浏览器侧不存在任何写入端点。
.serial/ 目录在文件 Tab 里的真实形态:世界观级资产(project.json / characters.json / world.json / inbox/)与各短篇集目录并列;每集是独立子目录,内含蓝图、时间线、章蓝图与章正文。

.serial/
├── project.json # 世界标识与创作方针
│ # worldId / worldName / language / tone / creativeStrategy
│ # / createdAt / updatedAt / writingRules
├── characters.json # 世界观级共享人物档案 { items: [...] },每人 7 个字段
├── world.json # 共享设定
│ # 必填 setting / locations / notes
│ # 可选 ◇era / ◇organizations / ◇rules / ◇glossary
├── inbox/ # 非权威提案收件箱,一条一文件
│ └── <时间戳>-<哈希8>.json
└── collections/<slug>/
├── blueprint.json # 该集蓝图(文件内不含 slug)
│ # title / theme / summary / characterIds / targetWords
│ # / status / ◇threads
├── timeline.json # 该集自己的时间线:season / startDate / endDate / keyDates
└── chapters/
├── NNNN-blueprint.json # 章节蓝图 + ◇threadIds
└── NNNN.md # 章节正文(纯 Markdown,唯一不用 JSON 的资产)
world.rules、world.organizations)是世界内事实;"我怎么写"(现实向、单一限知视角)是创作方针,放 project.writingRules。threads 声明在短篇集蓝图里,章节蓝图用 threadIds 标记推进。跨集的连续性由人物承担,不由线承担 —— 这保住了"每集是独立作品"这条不变量。era: "" 与省略 era 语义相同却字节不同,会算出两个修订号、制造假冲突。因此步 3 的 schema 扩容不需要迁移 —— 旧文件原样通过校验(已用真实项目验证:三个资产的修订号扩容前后逐字节一致)。worldId 不可更改:跨集与跨修订引用的都是它。serial_readkind | 返回 |
|---|---|
asset | 一个资产的文本 + 修订号 + 截断标记 |
world | 共享人物档案 + 短篇集蓝图(可选 slug 收窄到一集) |
appearances | 某人物在哪些集/章出场(整集记为 chapter: 0) |
threads | 每条故事线出自哪个集、哪些章节推进过它 |
audit | 跨集巡检:一次报出全部结构问题 |
chronicle | 人物编年史:有时间锚的集按时间排序,没锚的单独列出 |
kind="world" 是投影,不含 world.json 的正文与修订号;要写它必须先 kind="asset" 拿 revision。
serial_apply_change两个闭合分支:initialize(只在 NOT_INITIALIZED 后用)与 replace(一次一个资产,带 baseRevision)。
deny 返回原因。否则用户要为每一次格式猜错点一次确认,而模型从中学到的只有"又被拒了"。| 码 | 级别 | 含义 |
|---|---|---|
character.undeclared / thread.undeclared | error | 引用了不存在的人物 / 本集未声明的故事线 |
world.missing / characters.missing | warning | 共享设定或人物档案缺失 |
collection.blueprint.missing | warning | 有目录没蓝图 ⇒ 这一集不进世界投影 |
collection.timeline.missing | warning | 没有时间锚 ⇒ 编年史只能列为"无时间锚" |
collection.chapters.missing | warning | 这一集还没有任何章 |
chapter.blueprint.missing | warning | 有正文没蓝图 ⇒ 这一章的出场与故事线推进都不会被记录 |
chapter.draft.missing | warning | 有蓝图没正文 |
thread.unadvanced | warning | 声明了却没有任何一章推进过的线 |
character.unused | warning | 建档了却没有任何集或章引用的人物 |
巡检是报告而不是校验器:发现问题不会让读取失败 —— 一份有缺口的手稿仍然应该能读。
startDate)的集按时间升序,没有锚的进 undated。绝不按 slug 硬塞进时间线 —— "短篇集之间没有隐含顺序"这条不变量不能被一个投影偷偷推翻。侧栏「短篇集工作台」面板与「短篇集作家」预设是同一个 .serial/ 项目的两侧:预设是模型侧,面板是人侧。
revisionOf,按整文件字节算 SHA-256 —— 与模型看到的号逐字节一致面板不做的事:不改稿。浏览器侧不持有任何 mutation 通道 —— 改稿仍然走会话里的原生审批。样式只用主题 token(inherit / currentColor / color-mix),不引入 @deepseek-ai/dsh-client-ui-primitives。
会话身份来自 useSessions 的 retainedBy.mainView:main 插槽是 root 作用域,宿主明说 "other keys receive no Session binding",这是根作用域面板拿到"当前会话"的原生途径。
V2 的 serial_propose_change 把一条修改建议写进 .serial/inbox/。
summary(措辞不同不该排两条),但保留 baseRevision(它是并发令牌;剥掉会存下一条带着旧令牌、将来必然 STALE_REVISION 的建议)。authoritative: false / status: "pending"。注意:
./agent-v2还没有挂进任何预设,所以目前没有任何预设能用它提议;提案的落地路径也还没定(见docs/features-and-progress.md§7 决定 7)。
# 桌面应用:设置 → 插件 → 安装 bundle,目标填下面的 github 规格(或本仓库目录)
# CLI 管理的 profile(如 web):
dsh plugin --profile web add github:LeeGuanWei-a/serial-story-writer
dsh --profile web
尚未发布到 npm:
@leeguanwei/dsh-serial-story在 npm 上不存在,所以安装要用github:规格或本地目录。
desktopprofile 只能由桌面应用安装:dsh plugin --profile desktop …会直接拒绝 (profile "desktop" is managed exclusively by the Electron application)。
插件自带一条内联预设声明(cordis.patch.yml 里的 preset-short-story-writer),在会话的预设选择器里选「短篇集作家」即可。预设只挂 persona、agent-instructions 与 ./agent —— 不挂 shell、通用文件读写、文本替换或 Code Mode。
assetBytes / workingSetBytes(默认 512 KiB)、queryMatches(默认 20)、maxProposalBytes / maxPendingProposals(默认 2 MiB / 20)。越界值在加载时直接报错。maxProposalBytes / maxPendingProposals。pnpm install # 0.2.1 起 dsh 子包全部来自 npm,不再需要 junction 修补
node node_modules/typescript/lib/tsc.js --noEmit -p tsconfig.json
node scripts/clean-lib.mjs
node node_modules/typescript/lib/tsc.js -p tsconfig.build.json
node scripts/build-bundle.mjs # Client 源码 → 宿主 __ModuleLoader__ 的 CJS 工厂包
node scripts/link-self.mjs
node scripts/verify-built.mjs # 准入 + 产物契约 + 面板 SSR 真渲染
node node_modules/vitest/vitest.mjs run # 158 passed
一条命令跑完上述全部,外加打包面不变量:
node scripts/release-check.mjs # 51 项检查
它在测试之外还断言:准入判定通过(直接调用运行时自己的 evaluatePluginCompatibility,不需要 exemption)· 每个 exports 目标真实存在 · files[] 里声明的每个文件存在 · README.md 与 LICENSE 既存在又被声明 · dsh.bundle.patch 指向的文件存在 · dsh.client 声明了 platform 且 inject 为空 · manifest 具备发布元数据 · 没有遗留临时文件 · keyless 快照文件必须已存在(否则 vitest 会新建它并判通过,漂移检测就被静默绕过了)。
scripts/verify-built.mjs:准入判定(直接调用目标运行时自己的 evaluatePluginCompatibility)→ 装载 Host 入口 → 断言内联预设声明的形状 → 用目标运行时导出的 Config 逐个校验预设子行配置 → 组一个与线上同构的 roster 断言预设被发现且整棵子行挂载成功 → 校验 Client 半边并真渲染一次工作台面板(SSR)。tests/authoring-journey.spec.ts(keyless 快照):全程没有模型、不需要 API key,用模型真正会用的那一层(serial_read / serial_apply_change 工具定义 + 真实文件系统)从零写完一集:initialize → 世界观 → 人物 → 蓝图(含故事线) → 时间线 → 章节蓝图 → 正文 → 六种读取投影。快照把规范字节、修订号、审批决策与各投影形状一并钉住。环境提示:本机
pnpm run <script>会先触发依赖同步,而该步骤在当前 profile 下会失败。因此上面的命令都直接调node,不经pnpm run。
.vela 项目。engines.dsh 上界是刻意收紧的:>=0.2.1-rc.2 <0.3.0-0 不接受 0.2.0-rc.2 —— 那一版配的是 0.1.0-rc.6 子包,本版没在上面验证过。上界写成 <0.3.0-0 而不是 <0.3.0,是因为在 includePrerelease 下后者会把 0.3.0-rc.1 也放进兼容区间。dsh.client.inject 是空数组:它只为动态客户端包排激活顺序,而本插件的 bundle 只向宿主静态模块表要 react。列一个运行时并不存在的包(如已更名的 dsh-client-runtime)会让组合阶段判为缺失提供方。dsh-ai-novel-writer 的关系本插件沿用其 Host / Preset / Client 三层形态、SHA-256 资产修订号与提案式创作流,但不复用其源代码。「世界观 → 多短篇集、人物档案跨集共享」是本插件新增的产品领域。
下面截图取自一个真实跑通的偶像应援题材项目「应援色」(cheer-color)。
左侧栏是项目—视点—人物—待审提案—跨集巡检—各集的章节树;主列随当前选中节点切换。世界观与人物档案跨集共享,短篇集各自独立。

开启新会话时可在模式列表里选中「短篇集作家」。该预设只挂 serial_read 与 serial_apply_change,不挂 shell / 通用文件读写 / Code Mode。

时代 / 机构 / 地点 / 世界如何运转等世界级共享设定,落盘到 world.json,跨集共享。

世界观级共享人物:每人有 id、目标、整集出场、章级出场。同一人物可在多集出演,身份由稳定 id 引用。

选中一个短篇集后,主列给出主题、梗概、出场人物、故事线,以及每条故事线由哪几章推进过。故事线与集同寿,跨集连续性由人物承担。


章节正文是本项目里唯一用 Markdown 的资产(NNNN.md),其余都是 JSON;渲染在只读工作台里。

模型侧的 serial_read kind="audit" 与工作台的「跨集巡检」项都基于同一份巡检器:报告,不是校验器 —— 有问题不会让读取失败,带有 ▲ 警告的章仍能正常读取。

所有图片位于
docs/assets/;本节均引用相对路径,渲染于仓库根的README.md。
把一集订成一份 Markdown 文档,供模型读取与人类下载共用同一条装配链 —— src/serial-export.ts 是唯一的实现,两侧只负责把数据凑成 ExportCollectionInput。这样"模型读到的整集"与"人下载到的整集"在字节上一致,不存在两份装配逻辑悄悄漂移的可能。
两个调用入口:
serial_read kind="export" —— 模型侧,返回完整 Markdown 文本。<slug>.md」按钮。装配规则:
theme / status / season · startDate · endDate / summary)落在集标题下的题记区。--- 分隔。## 之下 —— 否则作者的 # 暮山紫 会出现在 ## 第 1 章 下面,层级倒挂。## 第 N 章 … 作为定位。(本章还没有正文) —— 静默丢章会让导出看起来比实际完整,这是有意的可见缺。
客户端打包时只对
serial-export做import type,零运行时依赖;导出函数本身也不引任何 Node 内建。