🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI
广告☁️ 云服务器特惠阿里云首购 8 折 · 腾讯云合作特惠
DeepSeek Harness Hub
← 返回列表

Furry-wucheng/dsh-story-mode

DeepSeek Harnessspec-screened扫描:中风险在 GitHub 查看 ↗
⚠ 装前注意

自己用来做小说的

基本兼容但装前注意:未发布到 npm registry,仅可从源码安装 · 最近上游提交 2026/9/26 · 已提供中文文档
综合分
30.8
GitHub 分
30.8
用户评分
—
★ Stars
1
周下载量
—
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add Furry-wucheng/dsh-story-mode
未发布到 npm registry,仅可从源码安装,改用 GitHub 源安装
信任档位:已验证本站已于 1 天前真实安装成功
是什么
dsh 原生插件 · other
装得上吗
本站已真实安装成功(非静态推断)
安全吗
本站尚未对该插件做风险分级(暂未覆盖,不等同于无风险)
还在维护吗
活跃:最近一次提交在 0 天前

档位由下列信号合成:本站实装验证(真实安装,当前最高到 L4)· 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。下方各区块是它的证据明细。 验证判据与等级说明 →

🟢实装验证通过· 2026/9/24
由本站实装验证器在真实 dsh 环境安装成功,非静态推断。
数据截至 2026/9/26(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
安装兼容性检查⚠ 装前注意

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✗npm 包dsh-story-mode(未发布到 npm,仅可源码安装)
✓Node 引擎要求 >=20 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

未发布到 npm registry,仅可从源码安装

验证方式:npm registry 存在性 + package.json 静态校验 · 最后验证 2026/9/23 21:24:19

用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动

README

由 DeepSeek 最新模型翻译生成
dsh-story-mode · 短篇小说模式

给 DeepSeek Harness (DSH) 的一个写作专用模式。装好之后,新建会话时模式选择器里会多出两个写作模式——「短篇小说模式」(五位角色化审读员)与「短篇小说模式(精简)」(一位合并审读员)——进去的不是编码 agent,而是一个接稿的短篇小说作者。

整套能力(两个模式、技能、工具)都在一个包里,一条命令落地。两个模式共用同一份文风契约与同一套写作工具,只有审读的组织方式不同:完整版按五个维度各派一位独立读者,精简版派一位读者一次读完五个维度。

它解决什么问题

让可测量的内容由程序负责,让需要上下文的内容由读者判断。

| 程序测量或定位 | 审读判断 |
|---|---|
| 中文字数、分场、相对已确认目标的偏离 | 场景是否有效、节奏是否合适 |
| 引号内文字比例、段落长度 | 对话是否自然、人物口吻是否可区分 |
| 重复双字组合、常见用词位置 | 修辞是否有效、心理与情绪是否冗余 |
| 人物卡字段是否非空、姓名与别名字面提及 | 新人物、专名一致性、动机与跨篇设定 |
| 稿件内容版本、原文行号 | 视角、时态、因果与必要交代 |

字面命中不代表违规,统计正常不代表故事好看。工具不再根据“我/你/他”判定视角滑移,也不依据固定对话比例评价节奏。

新稿与实质性改写保留独立审读;规划随任务规模缩放,纯文字小改由主代理核对上下文。近似字数允许合理浮动。

安装

dsh plugin --profile  add github:Furry-wucheng/dsh-story-mode

就这一条。 不需要第二步、不需要改任何配置文件。装完新建会话,模式选择器里就有「短篇小说模式」与「短篇小说模式(精简)」。(CLI 启动的 profile 下当场生效;桌面版若没看到,重启一次 DSH——它的 profile 组装只在启动与插件状态变更时跑,没有监听 bundles 的 watcher。)

它是怎么做到的

模式是包内补丁 插入的 preset 声明行(插件 @deepseek-ai/dsh-agent-preset)——完整版一层、精简版一层,共两行。DSH 0.1.7 起 agent preset 不再是"扫描某个目录里的 agent.cordis.yml",而是普通的 loader 行:注册表既不扫描目录,也不接受预设路径,新建或覆盖预设都靠 bundle 补丁插一行。那一行的 config.plugins 就是模式的全部子插件(人设、文件工具、精确编辑器、写作工具、技能、压缩、委派),config.name / config.description 是模式选择器里显示的文本。

于是没有任何"复制到 home"的步骤:patch 跟着包走,技能由各行用 customSkillDirs 从包内挂载(完整版一个根 skills/;精简版两个根:skills-lite/ 与 skills/writing-style-contract/)。包内文件路径一律运行时算出——这也是社区插件(如 dsh-TUI)解决同一个问题的做法。

两条与 0.1.7 前相反的解析规则(改这个文件前必须知道):

- 子插件行的名字写裸包名 dsh-story-mode。 preset 子插件的解析基准是 profile 目录,不是 patch 文件所在目录、也不是包目录;profile 的 node_modules 里就是本包(社区插件 dshmarket、dsh-better-sidebar 用的是同一条路)。相对路径 ./lib/index.js 会按 profile 目录解析而找不到;name: !!js … 更不行——loader 只对 config 插值,name 会原样丢给 import()。
- 包内文件路径在运行时问出来。 !!js 里的 baseUrl 现在是 profile 目录(解析锚点),不再是组合文件所在目录,所以每个读文件的行都先用 createRequire(baseUrl).resolve('dsh-story-mode/package.json') 拿到包根,再 path.join。官方组合(dsh-web-app 的 cordis 预设)用的也是这个写法。

两个模式怎么选

| | 短篇小说模式(short-story) | 短篇小说模式(精简)(short-story-lite) |
|---|---|---|
| 审读员 | 5 个角色工具:subagent_review_b1 … b5,各自读一遍 | 1 个:subagent_review,冷读起点、一次读完五维、一份报告 |
| 一次会话的子代理名额 | 6(自限) | 2(自限:审读员一位 + 通用出口一位) |
| 常驻工具面 | 28 个工具(含 subagent_review_b1…b5、web_、_goal) | 19 个(1 个审读员;无 web_ / _goal) |
| 每轮前缀 | 约 24,000 字符 | 约 17,300 字符(少约 6,700 ≈ 1,700 token) |
| 一次审读轮的阅读量 | 同一篇正文读 5 遍(10,000 字稿 ≈ 5 × 12,000 字符) | 读 1 遍(≈ 12,000 字符) |
| 一次审读轮的总开销(阅读 + 人设 + 工具面 + 报告) | ≈ 103,000 字符(≈ 74k token) | ≈ 24,000 字符(≈ 17k token) |
| 技能 | short-story + 文风契约 | short-story-lite + 文风契约(同一份契约文件,不复制) |
| 长程目标(/goal) | 有(command-goal + tool-goal) | 没有:读/建/标记目标的工具都砍了,只留命令会留下一个"目标置为 armed 却收不回来"的坑 |
| 适用 | 交付级审读、长篇、关系线复杂、要五维交叉验证、要目标管理 | 日常写作、快速迭代、长会话、token 敏感 |
数字是怎么来的:审读子代理是独立 spawn 的(零继承上下文),所以每个孩子的固定成本里最大的一项是再读一遍正文——10,000 字中文稿 read 带行号返回约 12,000 字符(按 400 行左右的分行正文估);五位读者就是同一篇正文读五遍。两个“总开销”数字是把子代理的工具面(只读工具约 2,400–5,200 字符)、人设、派发消息与报告上限都算进去的每轮派发成本,工具 schema 部分按已装构建的插件源码估算,误差约 ±15%。精简版不是“读得浅”,是省掉四遍重复读,并把五维覆盖留在同一份报告里:读不懂的地方、故事逻辑与关系依据、阅读体验与节奏、文风契约执行、动作与空间连续性。

它换掉了什么(说清楚,免得踩空):

- 五位读者之间的交叉验证与冗余没有了。精简版用“冷读起点”补偿了一部分——那位读者默认只拿正文,不拿人物卡、节拍、大纲和你的写作推理,所以“我没看懂”仍然是读者证据,而不是抄你的意图。但它确实只有一双眼睛:B5 的独立状态重建、B3 的独立节奏判断,都由同一位读者在同一遍阅读里给出。
- 文风契约在精简版里是“尺子”而不是意图:合并审读员既要冷读、也要对文风,所以契约(它写着“冷读者不读本契约”)会装进它的人设。人设里明确要求:先读完、判完其它四维,再拿契约定第 ④ 项的尺度,不用它预判剧情与人物;契约里提到 short-story 技能/审读面板的段落都指完整版。
- 最终盲读要另起一位新读者:重写或旧报告可能让原读者带入预期时,精简版的规则是再派一次 subagent_review(全新子代理、不给旧报告),而不是 send_message 复用——这条占掉第 2 个名额,所以“盲读”和“复审批次”二者按需取一。

要更严的审读时:预设是会话级选择,中途不能切换。所以做法是给这一篇另开一个完整版会话,把稿子交给它做交付级审读(含五位角色、/goal)。精简版留在会话里的通用 subagent 不是审读替代品——它没有信息边界、也没有报告格式,只适合查资料或核对某条设定这类不属于独立审读的活。

卸载

dsh plugin --profile  remove dsh-story-mode

两个模式与它们自带的技能都跟着包一起走,这些不需要任何清理——它们从来没被复制到你家里去过。(模式是包内两层 patch 各自插入的声明行,技能由 customSkillDirs 从包内挂载,所以它们只在这两个模式里可见。)

只有一件事要在卸载前确认:

dsh plugin --profile  exec dsh-story-mode check

如果它提示「默认模式指向本模式」,先清理再卸载:

dsh plugin --profile  exec dsh-story-mode cleanup

为什么要跑:如果你在模式选择器里把任一个写作模式设成了默认模式,卸载包之后新建会话会直接以 agent-preset/not-found 失败——DSH 找不到默认 preset 时不会回退到 standard,而真正会顺手清掉这个默认值的那条路径被 dsh plugin remove 绕过了。这一步必须在卸载之前做,因为包一旦 remove,这个命令也就没了。

(0.1.7 起这个默认值存在 agent-preset-registry 行的 volatile 字段 selectedDefault 上,由 loader 写回 profile 的 cordis.patch.yml;cleanup 只删那一行,不动文件里的其他内容,并且两个写作模式的 id 都认——只认完整版的话,选了精简版当默认的人卸载后会撞上同一个 agent-preset/not-found。它顺带清掉两处历史残留:v1.0.1 复制到 /.agent-presets/short-story 的模式副本,以及 v1.0.1/v1.0.2 曾可选安装的 /skills/writing-style-contract 副本——后者只在带 .dsh-story-mode.json 归属标记时才删,绝不会误伤你自己手写的同名技能。不是本包放的东西一律不动。)

一条命令的边界

ctx.agentPresets.register() 是多发布接口,所以本包不碰任何官方行:它插入自己的两行声明——preset-short-story(order: 5)与 preset-short-story-lite(order: 6)——都排在官方 standard / ptc / minimal / cordis 之后。0.1.7 以前那种“接管官方 agent-presets 行、整体重述它的 config”的代价(官方加字段就被静默抹掉)已经不存在了。

两行声明住在两个补丁文件里(cordis.patch.yml 与 cordis.lite.patch.yml),由 package.json 的 dsh.bundle.patch 一次声明——它是路径数组(@deepseek-ai/dsh-app-boot 的 bundlePatchFiles():字符串或路径列表都接受),按顺序合并成两层。为什么分开:那份完整版补丁与它周围的校验设施(scripts/verify-composition.mjs、test/preset-contract.test.js、story_doctor)都是按“一个文件一个 preset”写死的,同文件再插一块会让“原预设已被验证”这件事整体失效;分开之后,完整版的检查一字不改地继续守着它,精简版由同一个脚本的 --preset lite 档位来守。
还有两条要记住:两个 preset id 必须不同——config.id 撞了会在注册表里抛 Duplicate agent preset,而两行的 loader 行 id 撞了更隐蔽:后者静默覆盖前者(loader 按行 id 建表、last wins),表现为其中一个模式“莫名其妙不见了”。声明行是普通条目,可以被用户 patch 覆盖。 profile 的 cordis.patch.yml(以及 Web 配置编辑器保存的修改)能按 id preset-short-story 或 preset-short-story-lite 覆写它的 config——而 config 是整体替换、不是深合并,两层互不影响。所以你改过某个模式的话,升级包不会覆盖你的改动;想拿回包里的版本,删掉 profile patch 里那一段再重启。story_doctor 会检查这一点。

需要什么版本

DSH 0.1.7-rc.1 及以后。 更早的 DSH 没有 @deepseek-ai/dsh-agent-preset 这个包,本包插入的声明行加载不到——模式不出现,启动日志里留下一条 import 失败。反过来,v1.1.4 及以前的包(覆写 agent-presets 行)在 0.1.7 上只会得到一句“patch 目标不存在”的警告,模式与四个工具一起消失。两条路不通用,请按 DSH 版本选包。

验证安装

下面的命令只检查卸载残留,不证明模式已经加载成功:

dsh plugin --profile  exec dsh-story-mode check

安装验证请在新会话里让 agent 调用 story_doctor —— 它会逐项报告:包是否进了 bundles、package.json 是否可解析(带 BOM 会让 DSH 读不出 dsh.bundle)、两层 patch 是否各插入了一行声明(preset-short-story 与 preset-short-story-lite)、子插件行的写法能不能加载、包内模式文件与审读员人设是否齐全、技能根能不能从包内读到、profile patch 有没有覆盖其中任一行;在写作模式里还会问运行时本身:roster 里这两个模式在不在、组合是不是 broken、本作用域里有哪些技能与 story_ 工具。

其他安装方式

全局安装
pnpm add -g github:Furry-wucheng/dsh-story-mode

从源码目录直接开发:打成 tgz 再装进 profile(link 安装也可以,见下面的开发验证)
git clone https://github.com/Furry-wucheng/dsh-story-mode
cd dsh-story-mode && pnpm pack        # 得到一个 tgz
dsh plugin --profile  add ./dsh-story-mode-1.4.0.tgz

升级

dsh plugin --profile  add github:Furry-wucheng/dsh-story-mode

模式与技能都跟着更新——它们都住在包里,没有任何需要手动刷新的副本。

(唯一的例外:你在 profile 的 cordis.patch.yml 里按 id preset-short-story 或 preset-short-story-lite 改过那一行。patch 是整体替换 config 的,所以那一份以你的为准——升级不会覆盖它,也不会提醒你。story_doctor 会把这种情况报出来。)

用它

主代理先加载写作技能。复杂新稿或结构改写需要细化规划时再读共用流程;准备独立审读时再读审读面板的相关章节。只有缺失信息确实影响交付时才向作者追问;“写一段”“短一点”可以自行确定合理篇幅并说明。作者要求直接写、且范围已清楚时直接写;长稿或复杂关系线按需先拟节拍。

- 新写整篇或片段:明确范围与停止点 → 按规模规划 → 成稿 → 独立审读 → 必要时修订复核。片段无需全篇闭环。
- 续写:读取获准前文与相关设定,按新增内容规模规划并独立审读;不擅自补完全文。
- 整体改稿:核对并补齐受影响的人物卡和节拍,按确认范围修改,独立审读新版。
- 局部润色:读取必要上下文并核对改动与接缝。纯措辞、标点等不改变事实与阅读含义的修改由主代理自查;改变动机、因果、设定、视角或动作状态时派对应审读员。
- 只要评价或方案:独立审读对应材料后交付意见或方案,不自动写正文;评价不擅造作者设定。

完整作品的篇幅档位仅供参考:微型 3k–5k、标准短篇 5k–10k、中短篇 10k–20k、系列每篇 3k–10k。单点近似目标默认允许约 ±15% 浮动;作者给了区间就优先落在区间内。明确写出精确值、最低或最高字数时按硬约束执行。工具报告实际字数,小幅偏差不自动触发补写、删字或复审。

故事资产

较长新项目可用 brief.md 保存方向、bible.md 保存人物卡、outline.md 保存节拍、draft.md 保存正文;短片段可以在当前任务中保留简短规划,不强制创建四个文件。已有项目沿用既有记录,只整理本次受影响的内容。

人物卡包括身份、当下动机或需要、关系、性格和口吻、当前状态与相关事实。节拍安排当前内容的先后、快慢详略、字数与停止点;可以有不推进事件的闲聊和停留。详见共用流程的示例。

时间关系复杂时再用 timeline.md,专名多时再用 glossary.md。搜索、枚举和审读输入都限于授权目录,不能为避免新目录重名先列出其他作品。

四个工具

全部只读。正文和设定的修改走宿主的常规文件工具。

| 工具 | 实际能力与边界 |
|---|---|
| story_wordcount | 字数、按分隔符或标题分场、目标偏离、段落长度、引号内比例、引号外重复双字组合;不评价节奏好坏 |
| story_lint | 引号外用词线索,带原文行号和版本;不判视角、时态、情绪冗余或“AI 文风”,也不要求按命中删改。自查与定位用,不进审读流程 |
| story_bible | 校验人物卡必填字段非空、姓名及别名字面提及、术语清单;不识别全部新人物或判断设定合理性 |
| story_doctor | 安装自检与残留提示:静态检查 patch 行与包内文件,在写作模式里还会读运行时 roster、把本作用域里的技能与四个工具列出来;不能代替真实会话与子代理运行验证 |

按大纲目标统计

~~~json
{“path”:“故事/draft.md”,“sceneTargets”:“1000,3000”}
~~~

sceneTargets 是按场景顺序排列的正整数字数。工具计算(实际字数 − 目标字数)/ 目标字数,不按场景均分。
逐场成稿时可传包含后续未写场景的完整列表;已写场景必须都有目标。没有计划目标时省略参数,只看分布。
配置 sceneDriftPercent 表示相对单场参考目标的提示阈值,默认 15%。提示用于查看分布,不代表场景不合格,也不要求逐场凑字。旧 dialogueLowPercent / dialogueHighPercent 已不再使用。

引号内比例仅是对话的近似量,包含引用,不含无引号台词。重复项是未经分词的双字组合,不自动构成用词错误。
标题与开头的完整元数据块不计入正文;报告行号仍对应源文件。

用词线索与人物检查

story_lint 的 only 可筛选:template-simile、psychological-summary、emotion-explained、dialogue-tag-overuse、emotion-adverb-tag、ai-rhythm、cheap-adverb。这些旧规则 id 保持兼容,报告现在只表示待阅读的线索。旧 pov 参数和 only: pov-drift 仍接受,但不执行人称检查,会说明判断已移交审读员。

story_bible 的“缺卡候选”仅来自 bible 的“已知事实”清单里,在正文出现至少两次、却未建卡的条目。正文中新出现但未进清单的名字不会自动识别;人物、指代与拼写一致性要由审读员核对。

审读流程

新写、续写正文或大幅重写,包括片段:B1 冷读与 B2 故事逻辑独立审读初稿或实质性改写稿;B3 阅读体验、B4 文风执行、B5 动作与空间连续性按本次风险派。主代理修改后,只有实质问题或受影响状态链需要交原读者复核;旧报告可能影响判断时才换全新 B1 盲读。各角色独立读稿,不预先看彼此报告。
一句或一段的纯文字润色由主代理核对上下文,不逐处派审。若一句话改变人物动机、事件因果、视角或重要的动作与物品状态,派对应角色审受影响范围。自查、计数和 lint 不冒充独立审读;需要的报告或复核缺失时标待审。

精简版(short-story-lite)只有一位审读员。 新稿与实质性改写派 subagent_review 一次:它从冷读起点出发(默认只拿正文,不拿人物卡、节拍、大纲与你的写作推理),一遍读完,在一份报告里分五节给出五个维度,每节最多 3 条、每条带原文锚点与影响。复核用 send_message 发回同一位读者,只给改动范围与要问的事;一次会话新建的子代理不超过 2 位。它的派发规则写在 skills-lite/short-story-lite/SKILL.md 里,不读完整版的审读面板(那份规则对应的是精简版没有的五个角色工具)。

关系与关键转折

B2 除了核对事实和因果,也会检查重要关系转折是否有读者能感知的依据。两项在成稿后同一轮完成,不另派角色;没有关系线时不强加关系轴报告。独立读者能发现主代理因熟悉大纲而忽略的跳跃,但审读意见仍需回到正文判断。

关系依据可以来自行动、对话、积累的细节或叙述。暧昧和留白可以保留;没有固定的露出次数、对称主动要求、起点场景或逐拍代价。B2 只报告确实妨碍理解的断裂,给出原文证据和可选修法。纯文字小改无需重跑;实质改变关系转折时复核受影响范围。

台词是否自然、人物判断是否可信,也由读者结合上下文检查。闲谈可以存在,紧凑对话也可以成立;年轻人和弱势者同样可能敏锐。字面工具无法替代这类判断。

作者说“我没看懂”时,先核实困惑来自信息顺序、缺少必要依据,还是有意的悬念,再决定修改范围;不预设必须补一整场。
审读员是可复用的持久子代理。 每个角色派发一次就拿到它的 childId;需要复核时用 send_message 交给同一位读者,只检查相关实质改动。需要排除旧报告影响的最终盲读才另起新读者。派发时不要传 run_in_background: false,那会退化成一次性会话,后续消息送不到。

这套行为由 preset 的 backgroundMode: continuable 加 send_message / list_agents 两行工具提供;story_doctor 会静态检查它们还在不在。

读者数量有配额,所以复用不只是省钱。 DSH 给可续接委派设了一个存活子代理名额池:Host 的 dsh-subagent 设置 maxActiveSubagents 默认 8,同一棵可续接父子树共享;池满之后新建或冷恢复会被直接拒绝(ACTIVATION_LIMIT_REACHED),那一次派发拿不到报告,只能等已有读者空出来。三条机制值得记牢:send_message 回已驻留的读者复用它的名额;一次性运行不占这个池;名额只在当前进程里,限制的是"同时活着的读者数",不是累计报告数。

所以本模式自限:一次会话(一篇文章从接稿到交付)新建的审读子代理不超过 6 位——B2 一位、B1 一位、B3/B4/B5 合计最多三位、最终盲读一位;通用 subagent 最多 1 位。之后的每一次"再读一遍"都是 send_message 发回原读者。额度不够时先复用、再借用边界允许的读者,最后在汇总里标"因额度未派 X,该项待审",不虚报验收。

逻辑审读与关系轴是同一位 B2 的同一轮:都读已经落地的正文——核设定与因果(一致性),再答"这一跳有没有依据"(充分性),派发消息里加一行"审读类型:关系轴"即可。所以 B2 只有一次落点(第一个成稿版本),方案阶段不派读者:那时没有正文,判不出可信结论;方案的必答项由主代理拟节拍表时写死并自查,交付方案时注明"未经独立审读"。

派发预算写在审读面板的第一节;npm test 会检查 6 位上限、额度不足时的处置和复用规则。作者需要更多并发时可以在 DSH 的「插件 → 子智能体」里调高 maxActiveSubagents,但 6 位自限仍然成立。

| 角色 | 工具 | 可以读取的材料 | 什么时候派 |
|---|---|---|---|
| B1 冷读者 | subagent_review_b1 | 本篇正文;连续阅读可读已发布前文,不读大纲、设定、作者摘要或旧问题 | 新稿与实质性改写;需要排除旧报告影响时换全新读者 |
| B2 故事逻辑 | subagent_review_b2 | 正文、作者有效要求、人物卡、节拍、必要前文与设定 | 新稿、续写正文、实质性改写;关系线审读与逻辑同轮,方案阶段不派 |
| B3 阅读体验 | subagent_review_b3 | 正文与获准前文;不读大纲、设定与文风契约 | 长篇幅、多场景、结构调整或作者关注节奏时 |
| B4 文风执行 | subagent_review_b4 | 正文、作者要求、文风契约(已装进它的人设) | 有明确文风要求,或重点修改口吻、视角、台词与心理时 |
| B5 动作与空间连续性 | subagent_review_b5 | 正文、必要前文及明确的身体或物理规则;不读大纲、节拍、主代理的场景状态摘要、文风契约或其他读者报告 | 重要的动作、位置、持物或身体状态链需要独立核对时 |

每次派发记录正文路径和工具给出的内容版本。审读期间暂停修改;回收后核对版本。
问题位置采用“场景 + 原文短引 + 行号”,主代理追踪改稿后的对应位置。不同版本的段落号不能直接交叉判断。

需要最终盲读时使用全新读者,只看最新正文。 初次 B1 已读最终版本、期间没有实质改写时无需重复盲读。读者的困惑是需要核实的证据,也可能是合理悬念。

审读员不再委派是权限层的限制,"不改文件"是权限层加职责两层。 每个角色由 preset 里独立的一行 tool-subagent 提供,带 toolFilter:read / read_image / glob / grep / str_replace_editor,write / edit / present 与全部委派工具都被 tools.restrict() 在这个子代理的 scope 上剥掉。str_replace_editor 留着是为了查看长正文——它的 create / str_replace / insert 命令仍在,所以不要把审读员说成"物理上无法写入":只读靠角色职责约束。角色的人设(身份、读什么、报告格式)同样固定在那一行里,由 !!js 从 skills/short-story/references/reviewers/.md 读出,所以派发时只给变量、不必重述边界。独立审读无法运行时如实说明,不虚报验收。

(精简版的审读员名单故意不含 str_replace_editor:它的命令是 view / create / str_replace / insert,允许它就不是物理只读;而且它的 schema 占一个审读子代理工具面的一半以上(约 2,803 / 5,171 字符),去掉它每次派发少一半工具面。审读只需要读与定位——read 已经返回带行号的正文,glob / grep 负责找。)

共用流程和审读面板位于包内的 skills/short-story/references/ 下。复杂规划与准备派发时按需读取相关章节;skill 调用不会自动加载参考文件。文风契约的长篇示例也已移到 references/style-examples.md,只有需要比较写法时读取。

包结构

dsh-story-mode/
cordis.patch.yml                  完整版:插入 preset-short-story 声明行(@deepseek-ai/dsh-agent-preset),
人设、工具、技能、压缩与五位审读员全在那一行的 config.plugins 里
cordis.lite.patch.yml             精简版:插入 preset-short-story-lite 声明行,一位合并审读员 +
更窄的工具面(两层都由 package.json 的 dsh.bundle.patch 数组声明)
skills/short-story/               完整版的写作流程技能
SKILL.md                        写作入口、柔性字数与分级审读
references/                     writing-workflow.md:人物卡、节拍、成稿与修订;review-panel.md:派发规则与角色触发条件
reviewers/                    B1–B5 各自的固定人设(身份、读什么、报告格式),由审读员行读出
skills-lite/
short-story-lite/SKILL.md       精简版的写作流程与派发规则(规则内联,没有额外的面板文件要读)
references/reviewer-merged.md   合并审读员的固定人设:冷读起点 + 五个维度 + 报告格式
skills/writing-style-contract/    文风契约(两个模式共用同一份,由各自的 skill-filesystem 行挂载)
references/style-examples.md    需要比较具体写法时再读取
lib/index.js                      主入口:四个写作工具
lib/doctor.js                     独立入口 dsh-story-mode/doctor:只注册 story_doctor
lib/tool-kit.js                   零依赖的工具构造器与参数校验(两个入口共用)
bin/cli.mjs                       check / cleanup(不安装任何东西,只做卸载前清理)
scripts/cleanup.mjs               实际干活的那份
scripts/verify-composition.mjs    离线组合自检(--preset lite 校验精简版那一层,见「开发验证」)

模式是那一行声明,不是目录。 0.1.7 起预设不再从文件系统发现,所以包里没有 presets/:子插件行写在补丁文件里,行名用裸包名 dsh-story-mode 指回本包的 lib/index.js。完整版的技能根是包内 skills/;精简版声明两个根——skills-lite/(只装精简流程)与 skills/writing-style-contract/(单独挂契约,不复制第二份)。skill-filesystem 扫描根时,根下的 .md 文件直接算一个技能,所以第二个根指向契约目录本身即可;好处是精简会话的技能清单里不会出现完整版的 short-story 技能(否则模型可能加载它,再去找精简版没有的 subagent_review_b 工具)。

两个入口是 exports 子路径实现的同包多入口。./doctor 可以单独挂到任何模式里做诊断(例如创造模式),不必连带加载三个写作工具。

已知约束与设计取舍

这些都是查过框架源码、并且踩过之后才写下来的:
- 审读员是 continuable 子代理,不是一次性调用。 一次性模式(v1.1.0 及以前)下每轮复核都要再派一位读者,把同一篇正文重新从头读一遍;可复用模式下复核走 send_message,子代理带着上一版正文的阅读和自己的报告继续。坑:可复用模式里只有后台调用会产生持久 child,前台分支走的是 subagents.start(),拿不到 childId——所以技能与审读面板都明令派审读员时不要传 run_in_background: false。
- 五个审读角色各自一行,人设与只读限制都在组合里。 每个角色是一个独立的 tool-subagent 行(subagent_review_b1 … b5):persona 由 !!js 从 references/reviewers/.md 读出并装到那个孩子身上,toolFilter.allow 只列读类工具,deny 拿掉 write / edit / present / 全部委派工具。于是派发时主代理只给变量(路径、版本、范围、改动清单),不再从面板抄模板——抄一次就可能漏一条信息边界。两个坑:① tools.restrict() 对未知工具名抛错,而它抛在孩子的创建窗口里=那次派发直接失败,所以名单里只能出现本组合真实注册过的工具名;② 审读员人设里不要给 skill 工具,B4 需要的文风契约在挂载时拼进它自己的 persona。
- 两个模式住在两层补丁里,这是刻意的。 完整版那一层与它的校验设施是“一个文件一个 preset”的:scripts/verify-composition.mjs 顶层只允许一条 insert、且只读 insert[0],test/preset-contract.test.js 按整份文件数审读员行数,story_doctor 只读 /cordis.patch.yml。把精简版塞进同一份文件,等于让“原预设已被验证”这件事整体失效。拆成两层之后,完整版的检查原样守着它,精简版由同一个脚本的 --preset lite 档位守。两个坑:config.id 撞了会在注册表里抛 Duplicate agent preset(响亮),而两行的 loader 行 id 撞了是静默的——loader 按行 id 建表、后面的覆盖前面的,表现为其中一个模式无声消失,所以 id 必须成对唯一(preset-short-story / preset-short-story-lite)。
- 精简版是一位“合并审读员”,不是“少派几位角色”。 审读子代理独立 spawn、零继承上下文,所以每个孩子的固定成本里最大的一项是再读一遍正文:10,000 字中文稿的 read(带行号)约 12,000 字符,五个人就是五遍。合并版把五维(冷读困惑、故事逻辑与关系依据、阅读体验与节奏、文风契约执行、动作与空间连续性)放进同一遍阅读与同一份报告,代价要说清楚:五位读者之间的交叉验证与冗余没有了,B5 的独立状态重建、B3 的独立节奏判断都由同一位读者给出。补偿手段是“冷读起点”(默认只给正文,不给人物卡与大纲),以及另开一个完整版会话做交付级审读——预设是会话级选择,中途不能切换。
- 精简版砍掉的是工具面,不是写作能力。 常驻工具 schema 是每轮前缀里最大的一块(完整版 28 个工具约 22,259 字符 ≈ 5,565 token/轮,而它自己的写作人设只有 873 字符),所以精简版把 4 个角色工具合并成 1 个、去掉 tool-web(2 个工具)与 tool-goal(3 个工具)。command-goal 保留:斜杠命令不进工具面,长稿仍能用 /goal 定目标。四个 story_ 工具、压缩、精确编辑器、技能与文风契约一个不动——省的是重复读和每轮的 schema,不是写作与自查能力。
- 写作模式故意不开 subagent_fork。 fork 继承主代理的全部上下文(大纲、写作推理、修改理由);读者看过作者的底牌之后,“我没看懂”就不再是读者证据。审读员也因此不开子级模型选择,一律与主代理同路由。
- 技能根由 preset 声明,所以这些技能只在这两个写作模式里生效。 完整版挂包内 skills/(写作流程 + 文风契约),精简版挂 skills-lite/ 与 skills/writing-style-contract/;路径由各行的 customSkillDirs 在运行时从包内算出来,注册落进各自 preset 那一层,所以模式与技能永远同进同出。它们故意不放进 /skills:那是用户根(rank 400),而每个 preset 自己挂的 skill-filesystem 实例都会扫它(includeDefaultRoots 默认 true)——放进去等于让它们出现在所有模式里,包括编码会话,而它们只属于写作模式。
- 写作模式保留了搜索工具(glob / grep)。 shell、计划模式、后台任务、工作流、多级委派都拆掉了,但“找”不能拆:系列连载里核对名字与细节靠搜,不靠通读。这一行要注意 sampleOverCapGlobResults 在 framework 侧是必填配置(z.boolean().required()),漏了它整个 preset 会挂不上。
- preset 是普通 loader 行,注册表不扫描目录。 @deepseek-ai/dsh-agent-preset 的一行 = 一个预设:config.id 是会话保存的标识符,config.plugins 是子插件列表。插入用 bundle patch 的 insert 层,不碰官方任何一行。坑:子行的 name 必须是字符串,且解析基准是 profile 目录——所以本包自己的行写裸包名,包内文件路径一律用 createRequire(baseUrl) 在运行时问出来(baseUrl 是解析锚点,不是路径基准)。
- insert 里的相对路径会被启动器改写,config.plugins 里的不会。 启动器只把 insert 条目(及其嵌套分组)中形如 /绝对路径、./、../ 的行名转成相对 patch 文件的 file URL;子插件行的名字不在这个改写范围内。这就是为什么本包用裸包名,而不是 ./lib/index.js。
- !!js 后面若是折叠标量,整段代码会压成一行。 所以那段 JavaScript 里不能有 // 行注释(一个就吃掉后面全部),也不能靠自动分号插入。这两条都是实测踩出来的(本包现在把这类表达式写成单行双引号标量)。
- 双引号 !!js 里不要出现反斜杠转义。 YAML 先处理转义,'\n' 会变成真换行、JS 源码跨行;需要换行写 String.fromCharCode(10)。story_doctor 与 npm run verify 都有针对这一条的检查。
- createRequire 的锚点必须按文件 URL 给。 给它一个不带尾斜杠的目录路径,Node 会把该目录当成文件解析,直接 MODULE_NOT_FOUND。preset 树的 baseUrl 带尾斜杠,所以直接可用。
- package.json 不能有 BOM。 带 BOM 会让 JSON.parse 失败,DSH 因此读不出 dsh.bundle 声明,dsh plugin add 不会把包加进 profile 的 bundles,patch 永远不生效——表现为“装好了但模式不出现”。这个坑本包也踩过一次,story_doctor 里有常驻检查(package.json 无 BOM 那一项)。
- 升级包之后要确认模式真的换了。 你改的是哪一份包决定模式的行为:从 GitHub 装的(dsh plugin --profile  add github:/dsh-story-mode)取的是远端默认分支,本地改动没推上去就不会生效。更隐蔽的一种:早期版本或插件市场留下过一份 profile 里的包快照,dsh plugin remove 之后不会自动删掉它——它可能还留在 node_modules 与插件目录里,于是“装了新版但模式没变”。判断办法是看模式里有没有 subagent_review_b1 这个工具;清理由市场或手动删除那份快照完成(本包的 cleanup 脚本只处理 home 下的技能副本与旧模式副本)。开发期用 link: 装本地目录时,源码改动立刻生效,但改完要重启 profile。
- 本包零运行时依赖(连 schemastery 都没有)。ESM 的解析基准是加载入口的父路径,所以插件若 import 任何 @deepseek-ai/ 包就必须自带 node_modules;它什么都不 import,于是任何布局下都能加载。改代码时不要引入裸导入。

以下是旧版审读规则的实测记录,用来说明独立读者为什么需要检查关系转折,也记录了当时过于严格的处理方式;当前版本以本页前面的分级审读和正文证据为准。

- v1.2.0 之前,这套流程有一个已实测的失效形态:五个角色全绿而主轴空心。 一次真实调用里,五个审读员对一篇 23,399 字的稿子出了 19 份报告(含两次全新读者盲读),逐条命中字形、雨季倒序、浮毛时令、椅子数量、扣子崩落、字数台账,最终盲读明确写「没有大结构问题」;作者随后在半小时内用七个连续发言拆掉了它——主人公在八场里零主动推动、另一方“想要”的露出全部可被解释成尽职、关键那一跳没有任何不可否认的依据。

盲测对照(v1.2.0 补做):同一份 23,000 字稿、同一组派发变量、不带任何对话上下文的新子代理,唯一差别是 B2 的人设文件(标签随机化、判前不知对应):

| | 旧版人设 2.8 KB | 新版人设 8.7 KB |
|---|---|---|
| 报出关系轴缺口 | 否 | 是 |
| 关键结论 | 第 14 条把因果链判为「已建立的理由与因果(通过)……越线不是凭空发生……逐级落到具体动作与位置」 | 「转折点之前没有一条不可否认的露出」「推动者分布失衡,被动方在读者眼里是空的」「承认是告知而不是揭示」 |
| 逐条判定 | 无(核的是“有没有铺垫”) | 第一组 34 条逐条带行号判定,定位缺口的场次与必要条件 |

旧版把“逐级有铺垫”当成了通过项——它核的是“有没有铺垫”,不是“这些铺垫立不立得住”。这正是「一致性」与「充分性」的差别,也是这道缺口能在一整套流程里活下来的原因。

留这一条在这里,是因为缺口的形状(自洽但空心)不会自己暴露,只会以“读者看不懂”的形式回来。

另有一条人设本身的修订,也是盲测逼出来的:最初第一组的判据是“全部条目都可否认 → 报缺口”。第三轮盲测报了 34 条、其中 15 条判为不可否认,缺口却仍然成立——因为那 15 条集中在关系已经变化之后,转折点之前读者手里还是零。所以判据已补成“可否认总数不是结论,分布才是”:落在哪、是不是被逼近后的反应、有没有一件是“他单独为对方做的”。概括的数量判断会漏掉这一种。

- v1.2.0 还漏了同一个方向的第二种形态:露出有据可引,起点没有。 一次真实调用里,一篇 17,900 字的师生短篇把关系轴跑了三轮(方案阶段一次、成稿后两次复核),B2 逐条列出 22 条露出、逐条判定、逐条给正文短引,推动者分布也数过、结论是“不失衡”;补拍之后缺口关掉,交付时验收状态是干净的。作者读完后仍然说这篇“没有人味”:两个人从什么时候开始不一样的、老师是怎么开始注意这个学生的,正文一场戏都没交代。

形状:作者的定调是“学生主动进攻、老师屡次退守”,于是老师的朝前动作只落在转折点那一刻;而在转折点之前,正文给老师的两处来路是——去年十一月一节语文课上“忽然听不见学生的声音”,和五月里“他记得那个学生的气味……高二下学期还没有。到了冬天,那个味道变了。他没有理由去分辨它变成什么样,他分辨了。”学生的来路是唯一一句台词:“我画你的手,是去年十月开始的。”三处都是叙述者的句子,不是场上的事。 稿子处处自洽、前后对得上,读者却说不出这份想要是从哪儿长出来的——“他动过心”有据可引,答不了“他为什么会动心”。
为什么会漏:关系轴的必答项管的是“要”(露出有哪些、可不可否认、谁推动),而这一篇的两个人的动心起点都在开篇之前——节拍表把“从老师知道学生的心思开始”当成了既定前提,规划阶段就没有为起点留位置,于是审读环节也没有东西可核。更直接的一条在护栏里:判据原文写着“允许留白他什么时候开始动心、为什么是这个人”,把“什么时候”和“为什么”并列放过了——但两者的代价完全不同:时刻可以留白,依据不能。 读者不是不能接受留白,是不能接受空白;没有依据时他只能自己替作者编一个,或者干脆不接。同一处还有一条可核对的信号:作者问的是“从什么时候开始有异样”,而这一篇里三个候选起点(去年十月/去年十一月/“高二下学期还没有,到了冬天变了”)互相之间没有换算关系,读者连“哪一个是起点”都推不出来。

v1.2.1 的改法(都在规划与审读两层,不在工具层——正则写不出这类问题):B2 增加第零组“想要从哪儿来”,先于第一、二组做,对双方各要一条起点与“为什么只对这个人成立”,判据是硬的(只有叙述者说得出、任何一场戏都说不出的理由=没有理由;把主语换成另一个学生或另一位老师仍然成立的由来=没有不可替代性);人物卡为动心的双方各加一条注意起点(与“当下动机”分开——动机是现在要做什么,起点是这份要的来路);护栏改成“什么时候可以留白,为什么是这个人不能”;缺的起点补在开篇之前,或者把开篇提前,不靠“其实他早就……”这类补注。

盲测对照(v1.2.1 补做,同一份 bible/outline、同一份派发 prompt、无上下文污染):两名选手互不相识,唯一变量是 B2 的人设文件版本:

| 轮次 | 旧人设(v1.2.0) | 新人设(v1.2.1) |
|---|---|---|
| 第 1 轮 | 起点整节缺席;只在末段“第零组起点单列一句”里承认“起点整场落在开篇之前”,但没把它当缺口,四条补拍方向全落在中段 | 新增第零组整节;“熊谷全篇零起点”列为第一大缺口;引到“我不知道他什么时候开始不一样的”;写出“需在开篇前补一场,或把开场提前” |
| 第 2 轮(确定性检索) | 需在开篇前补一场 0 次、把开场提前 0 次;且把学生侧的起点判成“依据齐全,不报缺口” | 需在开篇前补一场 1 次、把开场提前 1 次,三条判据全中;两位互不知情的评委会独立判 found=true / found=false |
| 终局(精简后的人设) | 全篇 起点/来路/开篇 0 次,只把它读成“露出分布不均”(“不是写得不够多,是写得全都在对方看不见的地方”) | 单列第零组:学生侧“起点落场且不可替代”;熊谷侧“没有起点,只写了由来”报缺口,并点出“去年十一月失手”这个锚换成另一个学生在场照样成立 |

精简:第零组加进去之后人设是 5115 字符,压缩重复表述(开头与关系轴各自讲一遍“空心”、留白一处的三重表述、第一二组的冗词)后落到 4617 字符——比加组之前的 4673 更短,而新增的判据一条没丢。

一条没达标的观察,留在这里:终局那次新人设对学生侧判的是“起点落场、不算缺口”。学生侧的依据确实可引(画本+去年十月),但“为什么开始画”这一场仍然没落场,正确读法应该是双方都报。所以判据“有一场戏,或者有一个可被引出的锚”在宽度上仍偏松——它放过了“锚引得到、但那次注意本身没有戏”的中间形态。下一轮该把这一档单独写死,或者接受“一侧可引、一侧零”也构成本组缺口。

留这一条在这里,理由和上一条相同:自洽但空心不会自己暴露,它只会以“作者说没有味道”的形式回来,而那时稿子已经过了三轮审读、每一轮都验收通过。

相关的两个次级因素,一并记下来:① 那次调用里方案没有被独立审读——五路审读全部派发于正文落地之后,而方案阶段本来可以一句话拦下它(v1.2.0 曾用"§4.0 方案阶段审读"补这一条,v1.3.3 起按"审读都在成稿之后"取消:方案改为由主代理自查并在交付时注明,这一条重新成为已知缺口);② 主代理收到的子代理回报里,读者的思维链比报告正文还多(实测 91,204 字 vs 47,974 字,1.90 倍),注意力被“过程”占满,属于宿主回报格式,尚未解决。

一条方法论备注,也是踩过的坑:盲测要用不带对话上下文的子代理。早期我用 subagent_fork 做新旧人设对照,两次都“报出了缺口”——但 fork 会把整段对话(包括我写的判定标准)一起继承给被测者,等于把答案先交给它。那组结果分辨力为零,已作废。上面的表是改用无上下文子代理(ralph 的 fresh child)重跑的。

安装会改动你的 home 目录吗

不会。 dsh plugin add 只写 profile 的 package.json、node_modules 与 patch 层:两个模式都是包内补丁插入的声明行,技能由各行从包内挂载,两层补丁都由 package.json 的 dsh.bundle.patch 数组声明。/skills/ 与 /.agent-presets/ 都不碰,也没有需要用户去批准的构建脚本(pnpm 默认就会拦截依赖的生命周期脚本,本包不依赖它)。

cleanup 是唯一会写你 home 的命令,而且只删本包自己留下的东西:指向本模式的用户默认值(0.1.7 起是 profile patch 里 selectedDefault 那一行)、v1.0.1 的模式副本、带 .dsh-story-mode.json 归属标记的技能副本。不是本包放的一律不动。

注意:v1.0.1 把模式复制进 /.agent-presets/short-story。0.1.7 不再从目录发现预设,所以那一份已经不参与加载,但留着会让人以为"改了包内文件模式却没变"。check 会报出来,cleanup 会清掉。

文风契约

skills/writing-style-contract/SKILL.md 提供默认文风:克制、自然、具体。作者风格要求优先,不把所有作品改成同一种声音。

修改前后与保留示例已移到 skills/writing-style-contract/references/style-examples.md,需要比较具体写法时再读。默认风格用于提示取舍,作者要求和当前文本优先;示例不作为仿写素材,也不以缩短篇幅为统一目标。

修辞、心理、台词、动作和直接交代都按上下文判断;允许增写、删减或保留,不设最低删除百分比。
必要信息要在读者需要时可得;合理推断和有效留白可以保留,不要求所有专名首次出现就讲完背景。
视角检查交给审读员:区分叙述者、人物台词、内心引语与面向读者的称呼,指出具体的认知越界,不从人称字样下结论。

开发验证

~~~sh
npm test          # 单元与契约测试(Node 自带 test runner,无需依赖)
npm run verify    # 组合自检:两层补丁各读一遍(完整版 + 精简版)
npm run verify:full   # 只查 cordis.patch.yml
npm run verify:lite   # 只查 cordis.lite.patch.yml(--preset lite)
~~~

回归测试覆盖分场、引号统计、工具提示、人物卡和卸载检查,也验证审读角色各自成行、只读权限、按改动风险派发、关系转折审读与逻辑审读同轮、纯文字小改免复审,以及近似字数的柔性规则。组合测试继续检查 0.1.7 的 patch 形态、包内路径和技能挂载。测试只使用内存稿件和临时 DSH 主目录,不修改真实用户设置。
npm run check 与 CLI check 都是卸载残留检查,不是单元测试或完整安装验证。

npm run verify 补上单元测试碰不到的那一半:它用 loader 自己的解析器(js-yaml + entryListSchema)交叉验证组合,对每个 !!js 表达式做编译与求值检查(求值环境按运行时的样子搭:临时目录里造一个 node_modules/dsh-story-mode 指回仓库,baseUrl 指向它),用插件自己的 Config 校验每行 config,并把 toolFilter 点名的工具与插件源码里真正注册过的名字对账;--preset lite 档位用同一套纪律查精简版那一层(一个合并审读员、两个技能根、只读名单不含写入类工具、tool-web / tool-goal 确实不在),默认档位查完整版(五个角色行、五份人设、两份技能)。
它需要能读到 harness 已安装的插件(仓库本身零依赖):

~~~sh
harness 是普通目录(CLI 安装、解包后的运行时)
DSH_HARNESS= npm run verify

harness 只存在于 Electron 的 app.asar 里:普通 Node 读不了 asar 内的模块路径,
先解包再指过去(解包脚本见仓库外的任意 asar 工具),或用能读 asar 的运行时跑
~~~

两件它不能代替的事:真实会话里的工具清单,以及子代理真的能起来。装好后请开一个会话跑一次新写或片段:完整版里确认 subagent_review_b1 … subagent_review_b5 五个工具都在、派一次能拿到 started subagent ;精简版里确认只有 subagent_review 一个审读工具、而且它派出去的孩子工具面里没有 str_replace_editor。

为什么 !!js 里不能写 '\n'

!!js 的值在组合里是 YAML 双引号标量,YAML 会先处理转义:'\n' 在 loader 拿到源码之前就变成了真正的换行,于是 JS 里出现跨行的字符串字面量 → SyntaxError: Invalid or unexpected token。

v1.1.3 正是栽在 B4 那一行的换行拼接上(另外六个 !!js 不含转义,所以只有它炸)。当时的后果特别难查:旧版 roster 在挂载期才求值,抛错回滚整次 agent 创建,表现为“点新会话没反应”,而 roster 里 broken 仍是 null。0.1.7 把这类失败变响了——预设在启动时提前挂载,任何一行失败都会让 roster 里那一条带上 broken 文案,story_doctor 直接把它打印出来。

结论:需要换行就写 String.fromCharCode(10)。npm run verify 会把这类写法拦下来(交叉验证比对两种解析结果 + 对双引号标量里的反斜杠做预防性检查),story_doctor 也会静态检查一遍。

License

MIT

上游仓库有新提交时邮件通知你(每天最多一封,无更新不打扰),随时一键退订。

💬 加入社群

插件用法、部署报错、新插件第一时间同步——群里问,比一个人翻文档快。

DPharness QQ 群二维码,QQ 扫码进群
QQ 扫码进群
DPharness 飞书群二维码,飞书扫码进群
飞书扫码进群