DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

hualidan42-beep /

hualidan42-beep/dsh-plugin-gzh-studio

Verified

DSH 新媒体工作台:21 条赛道合规、逐张出图、推公众号草稿、视频成片预览

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@df77e00b

id: GZS-PLUGIN-README title: dsh-plugin-gzh-studio — 新媒体工作台 updated: 2026-11-04 status: active

新媒体工作台 · dsh-plugin-gzh-studio

挂在 DeepSeek Harness(DSH)主区面板上的新媒体内容工作台:中间是原生对话,右边是实时看板。 从选题、分内容类型写稿、逐段出图,到推平台草稿、导出 HTML、看视频成片——一条线走完,每一步都留痕。

  • 21 条内容类型(赛道):医疗、母婴、金融、教育、法律、美妆医美、心理、保健品、政策、品牌推广、 科学、历史、AI、财经、职场、美食、家居、运动、宠物、旅行、情感。每条带禁用词、必须声明、 结构骨架、标题公式与出图风格;不选也能用(通用模式)。
  • 实时:对话里改稿、出图,面板自己刷新(SSE),不用点刷新。
  • 推草稿:封面走永久素材、正文图走 uploadimg,直接进公众号草稿箱;推的是草稿,不是发表。
  • 视频期:成片能在面板里播(Range 流式)、字幕能叠、发布包能复制。
  • 跟随宿主外观:面板采样自己背后的背景色,按亮度选深 / 浅两套调色板;纸色在宿主背景上压一层很轻的倾向色(彩色主题自动减半),卡片再亮一档。宿主切主题或换背景时自动重采,不用重启。
  • 一稿多投:顶栏「发布包」按公众号 / 小红书 / 抖音 / 快手各裁一版;平台标题、正文、话题可以改,只存和原稿不同的部分,原稿仍是一份。

平台支持(写清「现在」和「还没」)

平台 现在能做到 还没做
公众号 推草稿(直接进草稿箱)、导出 HTML / Markdown / 图片清单 群发 / 发表(人工点)
视频号 成片预览、字幕叠加、发布文案复制 自动上传(无开放接口)
抖音 / 小红书 / 快手 发布包:同一篇稿按各平台字数 / 封面比例裁一遍、逐条列规格问题、一键复制(顶栏「发布包」) 一键发布(小红书无公开发布 API;抖音 / 快手需企业开发者资质与权限申请)

所以「新媒体」指的是内容形态与投放目标,不是「已经能一键发所有平台」。当前唯一能真推的是公众号草稿。

形态与「版本管理 + 可观测」的内核搬自 Deckline(dsh-deck-studio,MIT)—— 它把 image deck 的这套做进了工作台;图文这条线是同一个病,单位从「一页一张图」换成「一篇:封面 + 每段配图」。

它是什么,不是什么

  • 是:多赛道写稿与合规检查、逐张出图与版本管理、改稿提案(先看对照再确认)、手机宽度预览、 推公众号草稿、导出 HTML / Markdown / 图片清单、视频期成片预览与发布包。
  • 不是:群发 / 发表器(推的是草稿,发表仍要人工在后台点),也不是视频号上传器 (视频号没有面向普通账号的开放上传接口)。
  • 不是:素材库同步器。走导出 HTML 手工粘时,正文图仍需你在后台替换占位符;走「推草稿」不用。

三个视图

打开面板先进首页,不是直接被塞进某一篇的段落列表。

视图 内容 什么时候用
首页 全部文章的卡片网格:封面缩略图 / 标题 / 副标题 / 段数 / 已出图数 / 模板 / 相对时间。最后一张是「+ 新建一篇」。右栏是最近动作(见下)。 打开面板、想换一篇、想知道哪篇卡住了
单篇 三栏编辑(见下节),顶栏有「← 全部」和「治理」。 写稿、改段、出图、切版本
治理 一屏三块:检查结果(按必改 / 提醒 / 提示分组,点一条跳到那段)、提案(含逐段对照与确认/驳回)、账本(最近 40 条动作)。 交付前过一遍、看有哪些提案等着确认

治理视图只做集中呈现,不另算一套口径——数据全部来自 /article 已有的 qa / proposals / ledger,与右栏检查器显示的内容完全一致。

首页卡片不跑 QA:那会让首屏随文章数量变慢。卡片只读 /state 里已有的字段(段数、已出图数、封面版本、更新时间),要看具体哪条问题去治理视图。

最近动作(首页右栏)

回答的是「我刚才干了什么」——跨文章的账本尾巴,点一条直接进那篇。

  • 数据由 /state 的 activity 一次给全(服务端 store.recentActivity,每篇取最后 60 条原始账本、合并后按时间倒序、最多 14 条)。 不让面板按文章数发 N 个请求,文章一多就现原形。
  • 原始窗口必须远大于展示条数。取 12 条时踩过:一次验收跑连点导出,整栏被压成一行「导出 html ×13」, 出图、切版这些真事全被挤出窗口。readLedger 本来就读完整个文件再切片,放宽到 60 不增加 I/O。
  • 连续的同种动作折叠成一条并记次数(导出 html ×11)。不折的话,连点几次导出就把这栏刷成十几行一样的记录, 真正有价值的「出图」「改稿」被挤到看不见。只在相邻发生时合并——中间夹了别的事就是两段不同的操作。 判定签名含 type/slot/vid/format/pid/fields/verbatim:改的是不同字段就是两件事,不能算重复。
  • 文案复用 ledgerText,但走 brief 口径:只留「谁出的 · 多大」,把尺寸吸附说明和 prompt 摘要留给治理视图的账本 ——那两样在 300px 宽的动态栏里只会被截断。
  • 空库、或一条动作都没有时,这一栏整个不渲染,首页回到单栏。

库存:有多少篇、各在哪、搜得到吗

打开工作台先回答的是「我手上有多少活」,不是「这一篇卡在哪」。所以首页顶部是一排可点的数字:

数字 含义
全部 / 草稿 / 草稿箱 / 已发布 生命周期三档,点一下就筛到那一档
空壳 建了但正文还是空的——最该被提醒的那批
未绑期号 没绑期号,流水线状态判不出来(进度只能靠猜)

生命周期与流水线状态是两个维度,不能混:流水线答「卡在第几步」(要台账、要审批、要哈希, 只有绑了期号才算得出);生命周期答「这篇在作者手里到哪了」(所有文章都有)。一篇可以流水线全通 但还没发布,也可以已发布但后来改了字要重发。

  • published 只能由人标,机器无从知晓(没接公众号 API)。所以每个值都带 source (manual / inferred / default),卡片上人标的标一个「·手」字——否则用户会以为插件真的知道发布状态。
  • 不猜 published:有推草稿回执只推到 draftbox。没有证据的「已发布」会让待发的那几篇消失。
  • dry-run 回执不算推过(persisted=false 只是模拟),拿它当证据会让「草稿箱」混进没推的篇。
  • 空串能取消手标。只能设不能撤的开关,用户第一次标错就会讨厌它。

没绑期号也有进度:按内容完整度推五档(有标题 → 有正文 → 有封面 → 配图齐 → 导出过), 并明确标注「粗略估计」。期号与根目录收进「接上产线」的进阶区——小白写一篇文章不该先被要求填绝对路径。

标签与月份是目录检索的两个入口:标签自由填(去重、大小写不敏感、单个 ≤20 字、最多 12 个), 点一下即筛;月份按更新时间归档。两者都进检索串,也都能单独作为筛选条件。

搜索走 /state?state=&q=&tag=&month=,服务端过滤:检索文本(标题 + 小标题 + 正文拼串)可能几十 KB, 不下发;而且前端过滤会让「总数」和「列表」对不上。计数始终基于全量——改了筛选条件, 顶上总数不该跟着跳。

状态词笔误不能把库筛成空:传进来 ?state=draf 若直接比对会筛出 0 篇,用户看到的是 「文章全没了」而不是「条件写错了」。非法值当作不筛,并回 filters.invalid=true。

可读性基线:不是「风格」,是底线

六套模板之前各自定字号行高(正文 15–17px、行高 1.8–2.1),等于让用户挑一个「读起来最不吃力的」 ——那是设计缺陷,不是选项。现在把可读性抽成全局基线 READABILITY,模板只能在其上微调:

项 值 依据
正文字号 15px(模板可 ±1px) 135 编辑器与腾讯 wiki 两份独立指南的区间中位
行高 1.75(1.65–1.9) 同上,两份都指向 1.75 为「万能值」
段间距 20px(16–26) 同上
左右留白 10px(8–16) 同上;之前导出是 padding:2px 0,手机上正文直接贴边
正文色 #3f3f3f 深灰 两份都明确说不要用纯黑

templateOf() 是唯一出口,出口处统一 clampTokens()——手机预览与导出 HTML 都从这里取 token, 所以一处 clamp 就全面生效,不靠每个模板手写合规值(手写的迟早会漂)。

单篇视图:三个栏位

栏位 内容
左:结构 / 对话 上部是文章结构;开启对话后,左侧嵌入 DSH 原生对话,负责自然语言讨论、理解需求和生成修改提案。工作台不另造第二个聊天框。
中:手机预览 375px 微信正文预览,套当前模板的 token。改一个字当场看到手机上什么样,不用导出、不用开微信。点段落也能选中它。
右:检查器 / 看板 只剩三栏:证据(QA / 流水线 / 十道门 / 账本)、图(槽位与版本)、元信息。讨论、改稿和提案确认都在左侧对话与顶栏「检查」抽屉里,右侧不承担交流。

视觉:与 Deckline 同一套语言

界面不是「能用就行」的表格堆叠。口径和 Deckline 对齐,为的是两件事:同一个工作区里不像两个产品、 读长文本不累。

项 取值
底色 暖纸 #f4f1e9 → 卡片 #fffdf8 → 中栏画布 #f1ece1→#e9e2d4(带极淡横纹)
墨色 主字 #0c4651(墨绿)/ 正文 #3c4c50 / 次 #728086 / 弱 #8a9599
强调 青 #1496a8(选中、聚焦)/ 深青 #0f6975(主按钮)/ 金 #c5882f(版本号、当前版标记)
边框 一律暖灰 #d6d0c4,不用冷蓝灰——冷灰配暖底会显脏
字体 正文 Inter / 系统无衬线 12.5px,行高 1.65;标题用 Noto Serif SC(本机已装,Deckline 同款)
顶栏 墨绿实底 + 米白字,一眼区分「这是应用,不是弹窗」
选中态 薄荷底 #e2f1ef + 左侧 3px 青条(Deckline 的 .deck-mini.active 同做法)
形状 圆角 6–8px,阴影只用暖调 #b9ae9b,不用黑阴影

三条具体做法,都是为了「看起来是编辑工具而不是后台表单」:

  1. 中间那栏当画布:手机预览 375px 是真机宽度,不改它;窗口不够宽时用 zoom 等比缩小整块, 宁可图小也不横向裁掉右半边(裁掉的话滚动条在几千像素高的底部,够不着)。
  2. 层级靠字号 + 衬线 + 眉标,不靠加边框:结构 / 检查器 是 10px 大写字距的青眉标,版本(5) 同款, 标题用衬线,正文 12.5px。原来全是 11.5px 灰字,一片平。
  3. 空态与加载态都给人话:加载是骨架条(不是「读取中…」),空列表是虚线卡 + 一句「下一步做什么」。

六个能力

  1. 逐张出图:封面(900×383,2.35:1)与每段配图(1024×576)分开出。改哪段出哪段,不重做整套。 节点可有多个;没指定节点时按「默认 → 其余可用」依次试,一个额度不够自动切下一个(跳过谁、为什么跳过,都进账本)。

  2. 改稿提案:Agent 改稿走 /propose,只落提案、不动正文;面板逐段给旧/新对照(删的行红底、加的行绿底), 人点「确认」才写入,点「驳回」只记一行账。提案记着它基于哪一版正文的内容哈希——中间被人改过就判过期, 拒绝确认,不会拿一份过时的对照去盖别人的改动。 提案分四类性质,决定该退到哪一步(判据是「改了什么」,不是「改了多少字」):

    性质 退到 依据
    改词句 polish 07-deai 只改词句:不许动标题、摘要、配图,也不增删段落
    改结构 structure 04-write 动了段落增删或整段重写(小标题+正文同时改也算)
    改标题 title 03-angles 标题在方案阶段就定了;或整篇只在第 11 步改一次并留痕
    改摘要 summary 11-digest-draft 摘要的正式修订点在推草稿前

    混着改时按退得最远的那一步定(摘要+结构 → 退 04,不是 11)。提出方可以声明性质, 但声明不能当免责声明用:声明「改词句」却动了标题/摘要/段落增删,服务端当场 400, 并把撞了哪条红线说出来。

  3. 记忆账本:ledger.jsonl append-only。第 1 段出过 v001、v002、v003,每版当时的 prompt、节点、模型、尺寸、大小都在账本里,改完 prompt 也能回查当时写的是什么。

  4. 导出:公众号 HTML(全内联样式、没有一个 class,后台不会剥掉)+ Markdown(带 front matter)+ 图片上传清单(哪张、对应哪个占位符 __IMG_COVER__ / __IMG_01__)。

  5. 手机预览:见上表中间那栏。

  6. 六套模板:商务简报 / 杂志编辑 / 日系清简 / 科技蓝图 / 手账纸质 / 夜间深色。换模板只改长相,不动文字。

  7. 流水线状态:这篇文章卡在哪一步。见下一节。

  8. 去 AI 味:两层判据,都落在可核对的东西上。

    • 句子层(30 条词表):命中哪句、命中什么词、属于什么类(编辑腔 / 行业黑话 / 论文连接词 / 模糊限定…)、 为什么像 AI、怎么改。按规则归并理由(不是按词——「此外」和「然而」属同一条规则, 按词拼会把同一条 why 拼两遍,读起来像复读机)。同一句出现在多段时合并成一行并标出在哪几段, 否则模板稿会把同一句列三遍,把结论挤出屏幕。
    • 节奏层(4 条判据):长句连排(连续 ≥3 句 ≥25 字)、段落长度太整齐(≥3 段、均长 ≥60 字、 极差 < 均长 35%)、全篇无短句(≥6 句且没有一句 ≤12 字)、每段都以中心句开头。 词表抓不到「读起来像一条直线」,只有这一层能抓。
    • 改得动:可机器改的(删过渡词、去「所谓的」)标「可采纳」、可逐句勾;句式类的标「手」, 不许可点——机器硬凑只会把 AI 腔换成另一种 AI 腔。采纳产出提案,不直接改稿,人确认才写入。
    • 干净也报分母:结论里写明「扫了 N 句」。只报「没问题」不给分母,用户分不清 「稿子干净」和「功能没跑」。
  9. 05/06 两份清单逐项确认:编辑审与读者审各 8 条验收项(文案逐字取自 SKILL.md 的「验收」小节, 不自造条目),面板上逐项勾,勾完这道门才算过。 三种依据必须能区分:disk(盘上两份清单文件都在)/ fallback(靠多轮审稿记录兜底)/ panel(靠面板逐项勾)。勾满但盘上没有文件时判 panel 并在 detail 里写明依据, 同时对账给出 warn:「勾选只是我看过了的记录,不能替代清单本身」。 勾选只写插件自己的 reviews.json,一个字节都不往真实仓里写——那个仓是内容生产的事实源, 插件往里塞面板状态会让「盘上有什么」说不清。

  10. 出图前确认(一篇文章只问一次):说明会消耗额度、这一版会带上几条反馈 (带上之后那几条就从「待带上」变「已带上」)。不用弹窗——弹窗会挡住 prompt, 而人确认时最想再看一眼的恰恰是 prompt。确认卡贴在按钮下方并自动滚进视野 (踩过:卡落在视口外,用户点完看不到任何变化,以为按钮坏了)。

流水线状态:卡在哪一步

回答的是公众号 13 步流水线里的位置,不是「编辑器里改到哪」。判据全部来自盘上的真实文件, 不是自报进度。

  • 状态词不出自造:只允许 PIPELINE.md §4 那九个(ready / needs-research / needs-browser-audit / needs-user-approval / needs-draft-approval / needs-review / dry-run …)。lib/pipeline.js 里有一张 全集表,测试会对每个返回值断言「必须在词表内」——防止哪天随手加一个 needs-something。
  • 十道门逐条给证据:人物清单 → frontmatter 四项 → 证据台账 → 方案已挑定 → 编辑审+读者审 → Chrome 终核 → 制作确认 → 排版已体检 → 排版校验留痕 → 推草稿回执。每道门给 ok / fail / stale / unknown / skip 之一,附一句能核对的具体内容(「绑的是 6f35a431,现稿 27bee65f」),不是只说「不通过」。
  • unknown 不等于做不到:判据不可用时标 unknown 而不是 fail。把「这台机器查不出」说成「没做到」, 会让人去补一份本来不该存在的文件(027 期的 chrome-fetch-027.json 只绑了《史记》页面,不是没核过)。
  • 哈希说了算:确认记录里绑的是 sha256,正文/排版/封面任何一个字节变了旧确认就作废。 时间戳挡不住「改完又改回来」。但首尾空白不算改稿——否则改个空格就判全篇作废,人不敢动键盘。
  • 过期确认可被重签覆盖:027 期的制作确认早过期了,后面的草稿授权重签过且哈希匹配,所以判 ok 并注明「后被 …-wechat-draft.json 重签覆盖」。直接判 stale 会让人白重核一遍。
  • 绑定是显式动作:article.pipeline = {episodeId, root, articleHash, boundAt},绑定那刻钉下正文哈希; 之后在面板改过字,状态里 drift=true 会明说「面板里改过正文,按规矩要重核、重新交稿」。
  • 绑定/解绑进账本但不进「最近动作」也不进账本视图:那是配置杂事,不是编辑动作。 留在 ledger.jsonl 里做审计,但混进视图会挤掉真正的内容工作(验收脚本每跑一次就多一对绑定记录)。 视图里会注明「略去 N 条配置」,不偷偷藏。
  • 状态词之外还有三样东西(借同事「编辑部观测台」的做法):
    • 节点条:11 步流水线一排 chips,✓立项 ✓台账 … –去AI味,一眼看出走到哪。 没有机器判据的步骤标「无判据」并列名——假装知道比不知道更糟,但也不能因此把整篇判成未知。
    • 时间线:从交付件 mtime 推出来。账本记「谁为什么改」,时间线记「什么时候动过」,不另建事件表。 必须给绝对时刻(10-22 17:23),给「4 天前」的话八个文件全一样,排不出先后。
    • 事实核对:台账 / 证据 / 核验 / 审稿 / 素材包 分开列。混在一张表里会让人误以为「审稿过了 = 事实对」。
  • 侧栏只放够判断的信息:状态词 + 卡在哪 + 下一步 + 一条 11 段进度概览。 完整节点条与事实核对在治理视图(748px 宽)。11 个 chips 塞进 352px 会被压成三行,密得看不清 ——信息量决定放哪,不是「相关的放一起」。

安装

dsh plugin --profile web add dsh-plugin-gzh-studio@0.2.0
# 装完按提示重启 dsh web(profile 开了 patchReload: live 时,改 manifest 会热重载)

本机开发(把目录链进 profile):

New-Item -ItemType Junction -Path "$env:USERPROFILE\.dsh\profiles\web\node_modules\dsh-plugin-gzh-studio" `
  -Target "<插件目录>"
# 然后 package.json 两处:dependencies 加 "dsh-plugin-gzh-studio": "link:<插件目录>",
# dsh.profile.bundles 加 dsh-plugin-gzh-studio

装好后:主区面板列表里出现「新媒体工作台」,点开就是工作台。数据全在本机。

首次打开要做的三件事

  1. 在左侧对话里说「写一篇关于 X 的稿子」;也可以点首页的「新建一篇」手动建(空库时它也是第一步);
  2. 出图节点:默认是「占位图」(不联网、出 SVG),足够把流程点一遍; 要出真图就在 settings.json 里加一个 OpenAI 兼容节点(见下);
  3. 公众号(可选):顶栏「推草稿」里填 AppID / AppSecret,第一次推大概率遇到 40164,把面板给出的 IP 加进白名单即可。

真源仓库(往期与视频期)默认不配:要用就往环境变量 GZH_STUDIO_REPO 指到 social-media-management 那一级,或在 settings.json 里写 pipelineRoot。

配置(可选)

loader 行 config 或 ~/.dsh/gzh-studio/settings.json:

键 默认 说明
root ~/.dsh/gzh-studio 数据根目录
ledgerLimit 200 面板一次回读的账本行数
nodes[] 一个 fake 节点 出图节点:{id,name,baseURL,apiKeyEnv,model,size,fake}
defaultNode 第一个 默认节点 id
defaultTemplate business 新建文章的默认模板

key 只存环境变量名(apiKeyEnv),插件从不把 key 写进任何文件、也不进日志与错误消息。

公众号推送配置

推草稿的凭据放在另一个文件(~/.dsh/gzh-studio/wechat.json),跟出图节点分开—— 那里面有 AppSecret,不该混进会被到处复制的那份设置里。面板上的「推草稿」对话框会写它。

键 说明
appId / appSecret 公众号后台「设置与开发 → 基本配置」。也可用环境变量 GZH_WECHAT_APPID / GZH_WECHAT_SECRET 覆盖(不想落盘时用)
author 草稿作者名(≤16 字,可空)
mode direct(默认,插件直连微信)/ gateway(预留,密钥与出口 IP 都留在网关)
gateway.baseURL / gateway.token 网关模式的地址与令牌

直连的硬门槛是出口 IP 白名单:第一次推大概率回 40164,面板会把微信报的那个 IP 抠出来给你一键复制。 token 走 stable_token 缓存 7200 秒(提前 5 分钟失效),只有 40001/42001/40014 才会强制刷新一次。

fake 节点(默认)不出网,出的是 SVG 占位图:尺寸、槽位名、prompt 摘要、版本号都画在上面。 装好就能把「出图 → 看版本 → 改 → 重出 → 导出」整条流程点一遍,不用先配 key。

真节点是 OpenAI 兼容接口:POST {baseURL}/v1/images/generations, 吃 b64_json 或 url 两种返回;尺寸以返回的图片本身为准(节点常忽略 size 参数),探不到才回填请求值并在版本上标 note。

⚠️ 尺寸必须是 64 的倍数。这是实测出来的坑:网关对非倍数尺寸不报错, 直接把请求改成一对方图(900x383 → 1254x1254、900x384 → 同样方图; 1792x768、1216x512、1344x576 则按请求返回)。所以 generate.js 在发请求前 会先做一次吸附(snapSize):保比例不保数字——封面目标 2.35:1 落到 1664x704 (2.36:1,漂移 0.6%),正文目标 16:9 的 1024x576 本身就是 64 的倍数,原样发出。 吸附过就在版本的 note 里写清楚改了哪一档,面板版本条下方能看到这句话。

赛道包:合规口径挂在内容方向上,不挂在模板上

模板回答「长什么样」,赛道回答「写给谁、按什么结构写、哪些话不能说、缺什么声明会出事」。 医疗和母婴可以共用一套排版,但「治愈率 98%」「零风险」在医疗稿里是红线、在职场上只是夸张。

内置 21 条

档 赛道
A 强合规(写错会出事) 医疗健康、母婴育儿、金融理财、教育升学、法律、美妆医美、心理、保健品营养、政策解读、品牌推广
B 事实核验(写错会失真) 科学科普、历史人文、科技 / AI、财经商业
C 生活通用(结构与口吻) 职场成长、美食菜谱、家居生活、运动健身、宠物养护、旅行、情感关系

21 条覆盖市面主流内容方向;覆盖不到的方向由用户自己加(见下)。

规则怎么判

判据 级别 为什么
命中禁用词 warn 字面匹配分不清「不根治」和「根治」,判 error 会逼人改一句本来没错的话
缺「必须声明」 error 那是真的没写,不是猜的(医疗要「科普不构成诊疗建议」、金融要「不构成投资建议」)
有数字 / 年份但没写来源 info 提醒补出处,不拦发布
科技 / AI 稿没写版本或日期 warn 能力、价格、配额都会过期

赛道条目并进普通 QA(同一套 {level, item, fix}),面板不为它另做一块 UI—— 两块并排时人只会看其中一块。

自定义赛道

往 <root>/tracks/*.json 丢一份包就能加,形状不对的包会在日志里报一句、然后被跳过 (不会把面板带崩):

{
  "id": "pet-care", "name": "宠物养护", "tier": "C",
  "audience": "养猫养狗的人", "tone": "具体、不吓人",
  "structure": ["场景", "怎么做", "何时就医"],
  "titleFormulas": ["…到底要不要紧"],
  "imageStyle": "自然光下的宠物抓拍,避免拟人化",
  "defaultTemplate": "paper",
  "compliance": {
    "forbidden": ["包治", "绝对安全"],
    "required": [{ "any": ["兽医", "就医"], "message": "宠物稿要提示就医", "fix": "补一句就医指征" }]
  }
}

撞了内置 id 的自定义包不覆盖内置:内置那套是兜底,被顶掉会让规则来源说不清。

数据落点

~/.dsh/gzh-studio/
  settings.json                        节点 / 默认模板
  articles/<gzh-YYYYMMDD-xxxx>/
    article.json                       当前状态:标题、段落、每个槽位的 current 指针
    ledger.jsonl                       记忆账本(append-only,只加不改)
    proposals/<p-xxxxxxxx>.json        改稿提案(含逐段对照与 baseHash)
    reviews.json                       05/06 清单逐项确认(16 项;**只在这里,不写真实仓**)
    images/cover/v001.svg              封面各版本(旧版永不覆盖)
    images/sec-s3/v002.png             第 3 段配图各版本

article.json 是当前状态,ledger.jsonl 是历史。状态可以覆盖,历史不行 —— 所以「哪一版说过什么、为什么改」只能从账本查。

提案放在文章目录里而不是全局目录:拷走一个目录就带走了正文、账本、图、提案,不会漏。

路由(全部在 Connection 鉴权栅栏之下)

路由 方法 说明
/api/gzh-studio.status GET 就绪状态
/api/gzh-studio.state GET 文章列表 + 节点(含可用性)+ 六套模板
/api/gzh-studio.article?id= GET 单篇全文 + QA + 账本 + 图片清单
/api/gzh-studio.create POST 新建:{title,audience,takeaway,sections:number|array,template}
/api/gzh-studio.save POST 改稿:{id,patch}(白名单字段,未知字段忽略)
/api/gzh-studio.script POST Agent 起草入口:整篇讲稿一次写入
/api/gzh-studio.generate POST 出图:{id,slot,prompt?,negative?,node?,size?}
/api/gzh-studio.pick POST 切版本:{id,slot,vid}
/api/gzh-studio.feedback POST 反馈原话进账本:{id,slot?,vid?,verbatim}
/api/gzh-studio.export?id=&format=html|md|checklist GET 导出(纯文本响应)
/api/gzh-studio.image?id=&slot=&v= GET 图片字节(no-cache + ETag)
/api/gzh-studio.node POST 节点增改 / 切默认
/api/gzh-studio.brief?id= GET 纯文本 brief(讲给谁 / 带走什么 / 禁忌 / 分段现状),可直接贴进对话
/api/gzh-studio.propose POST Agent 改稿入口:{id,note,title?,summary?,sections?} → 只落提案,不动正文
/api/gzh-studio.proposal?id=&pid= GET 单份提案 + 逐段对照(只给变化行与上下文)
/api/gzh-studio.decide POST 裁决:{id,pid,confirmed};确认走 writeScript,驳回只记账
/api/gzh-studio.deai?id= GET 去 AI 味诊断:句子层(词表)+ 节奏层(4 条判据)
/api/gzh-studio.adoptDeai POST 采纳几句建议:{id,accepted:[{slot,start,end,after}]} → 产出提案,不改正文
/api/gzh-studio.review POST 清单勾一项:{id,kind,item,checked};勾满即视为这道门过
/api/gzh-studio.repo GET 真源仓库(只读):03-scripts 下稿件列表 {root,lines,articles}(只 stat,不读正文)
/api/gzh-studio.repoArticle?dir= GET 单篇:intent/evidence/review/html + sections(H2/H3 切节;html > 200 KB 截断并标 htmlTruncated)
/api/gzh-studio.video GET 视频号各期:{id,dir,status,files},status 形如 分段 · 1/8
/api/gzh-studio.videoEpisode?id= GET 单期:口播稿 / 分镜 / 分段 / 字幕 + title / outline / dirPath / media(成片、封面、分镜图、字幕文件);缺件回空数组 + note,不报错
/api/gzh-studio.media?id=&rel= GET 视频期里的媒体文件:支持 Range(<video> 才能拖进度条),路径三道闸校验,类型白名单(音频/视频/图片/字幕),未知扩展名 415
/api/gzh-studio.gate?dir= GET 质量门:真跑 gate status(20 秒超时、同目录 5 分钟缓存),中文键映射成 no/name/verdict/round/pass

真源仓库(只读,与工作台自己的库是两份数据)

  • 根目录没有默认值:必须用环境变量 GZH_STUDIO_REPO,或用 settings.json 的 pipelineRoot 指定 (指向 social-media-management 那一级)。没配时 /repo 回 configured:false、列表为空,面板显示「未配置」而不是报错;
  • 这些路由一个字节都不写真源仓:扫描只看 readdir / stat,正文只在单篇请求时读;
  • dir / id 一律做包含校验:..、盘符、UNC、绝对路径、符号链接逃逸都回 400,找不到回 404;
  • gate 是真起子进程(node <workspace>/wechat-article-pipeline/gate/index.mjs status <稿件>), 结果按 dir + 稿件目录 mtime 缓存 5 分钟——首页不能每篇都跑一次。

五条口径

  1. 旧版永不覆盖。nextVid() 只往后加号;「现在用哪版」是 current 指针,切版本只改指针,不删文件。
  2. 账本 append-only。改稿、出图、切版本、反馈、导出、提案都进账本;面板上看到的就是宿主里存的那份,不做本地乐观更新。
  3. Agent 改稿先进提案。/propose 只写提案不写正文,人看过对照点确认才落盘; 提案记着 baseUpdatedAt,正文中间被改过就判过期并拒绝确认——不拿旧对照盖新正文。 确认那一步复用 writeScript,不另开一条写入路径,避免「确认过了但正文没按确认的内容写」。
  4. 图片两条路:走「推草稿」时正文图由插件走 uploadimg 换成 mmbiz URL、封面走永久素材, 不用手工;走导出 HTML 手工粘时,本地文件贴不进去,所以图位置写成 __IMG_01__ 这类占位符, 另给清单说明「哪张对应哪个占位符」,上传完在后台替换。
  5. 空段落在读者视角里不存在。没小标题、没正文、没配图的一段,手机预览里整段跳过—— 以前它会留一个「第 N 段还没有配图 · 点这里出图」的虚线框在正文最末尾,那是编辑器控件, 混进预览里像是文章没写完。同理,QA 的「N/M 段还没有配图」也不把空段算进分母: 空段已经单独报了一条 section-empty,再数一遍就是指向一个还不存在的段落。 段号仍按原始位置编号,跳过中间的空段不会让后面「第 3 段」变成「第 2 段」。

Agent 怎么接

不需要在插件里内置 LLM,三步走:

  1. 取约束:GET /api/gzh-studio.brief?id=<id> 拿到一段纯文本——讲给谁、想让人带走什么、 禁忌词、分段现状(段号 + 小标题 + 字数 + 有无图)。这段直接贴进对话,Agent 起草时就有边界。 否则 audience / takeaway / forbidden 只是躺在 article.json 里的三个字符串,没人看得见。
  2. 提提案:POST /api/gzh-studio.propose,body 与 writeScript 同形 ({id, note, title?, subtitle?, summary?, sections:[{id,heading,body}]})。 只写提案,正文不动。 返回 {pid, status:"waiting-user", summary}。
  3. 等人裁决:人在面板上点确认才会真写入;Agent 可以轮询 GET /api/gzh-studio.proposal?id=&pid= 看 proposal.status 与 blocked。

/save 与 /script 仍然保留,但它们是直接覆盖——人工在面板上改字走的就是这条路。 Agent 改稿请走 /propose,别再直接调那两个。

出图 prompt 的「聪明」也不在插件里:它靠人写、靠 Agent 写、靠 seeds/ 里的样张。这部分本来就是可以被公开改超越的(Deckline 的自述同理)。

验证(不需要浏览器、不需要网络)

npm run verify                 # 全套:11 套单测 + 打包边界 + 干净安装验收
npm run verify:fast            # 只跑不落盘的那三套(路由/类名/布局)
npm run pack:check             # 只验包:文件清单、体积、有没有作者私有路径
脚本 盯什么
qa-unit 纯函数:QA(含禁忌词)/ 模板 / 导出器 / 占位图 / 尺寸探测 / 文件清单
generate-unit 出图:尺寸吸附到 64 的倍数、封面比例漂移 <2%(假 fetch,不联网)
pipeline-unit 流水线状态机:造假文件树逐个打破门禁,验状态词指得对
host-smoke 假 ctx + 真落盘:建→改→出图→切版→读图→导出→提案闭环→节点 failover→越权探针
client-harness 假 ModuleLoader/React:apply 注册面 + 三栏组件真渲染 + 原生会话绑定/重开/递话
stream-unit SSE:hello 帧 / POST 写事件(带 id)/ 盘上外部改动 / 断开清理
push-unit 推草稿:本地假微信服务器跑全链路(token 缓存 / 封面走素材 / 正文图走 uploadimg / 占位符全替换 / 40164 带 IP / SVG 拦下)
video-media-unit 视频:成片/封面/分镜/字幕识别、Range 206、越界与未知类型拒
tracks-unit 赛道:12 条内置、坏包被拒、命中只 warn / 缺声明判 error、自定义包生效、出图风格进 prompt
layout-split 两档中栏下限 + 左右栏夹取 + 分隔条指针/键盘/复位
class-check 组件里用到的 gzs-* 类名是否都有 CSS 定义
pack-check 包边界:22 个文件 / 解开 <1.5 MB / 不含 verify 截图与作者私有路径
install-check 从真打出来的 tarball 解包、加载宿主半、挂路由、真调三条读接口 + 建一篇

真界面验收(需要 3080 在跑,用无头 Chrome + CDP 真点真截图):

node verify/live-ui.mjs <token>   # 78 项:首屏三栏/首页/单篇/治理/新建/导出 + 原生对话独立会话/空白态/收起重开 + 窄窗口与几何
node verify/live-entry.mjs <token>  # 10 项:入口可达性——新媒体工作台是侧栏常驻行、一步点开、「更多」里不重复出现
node verify/live-interact.mjs <token>  # 22 项:真鼠标拖分隔条(含极限与夹取)→ 刷新记不记得住 → 双击复位 → 视频号点开单期
node verify/live-pipeline.mjs <token> [期号] [根目录]  # 29 项:绑定→徽标可见性→侧栏概览→观测台→解绑还原
node verify/probe-app.mjs <token> # 只读探针:把首屏 DOM 里能点的东西列出来,排查「入口点不到」时用
node verify/probe-node.mjs <token> # 只读探针:出图节点到底能不能用(缺 key 会静默降级成占位图,先问再按)

本机 3080 只认 dsh-auth-* cookie,URL 的 ?token= 不能代替登录。verify/cdp.mjs 会从 D:/work/skill/output/dsh-cookie.txt 读取 Netscape 格式 cookie,灌进无头 Chrome 后刷新一次; 可用环境变量 DSH_COOKIE_FILE 换路径。cookie 缺失或过期时,页面会是 401 白页,症状通常是所有 live 套件都报「等不到侧栏按钮」——先查登录,不要误判成插件没注册。

live-pipeline.mjs 用演示文章绑真实期号,只为验界面能不能正确显示状态;结尾一定解绑, 不留错配。它查徽标时不止查「存在」,还查尺寸、是否在卡片内、是否在视口内—— 徽标是 absolute 定位,父容器少一句 position:relative 就会跑到视口左上角被侧栏盖住, 而 querySelector 照样找得到。这类「在 DOM 里但看不见」的错,只查存在性永远发现不了。

批量配图与看成品(会花出图额度,按需跑):

node verify/fill-section-images.mjs <token> --dry   # 只打印每段的 prompt,不出图
node verify/fill-section-images.mjs <token>         # s1–s5 逐张出真图;已是真图的槽位自动跳过(--force 重出)
node verify/shot-sections.mjs <token>              # 逐屏截手机预览,人眼过一遍每段配图

fill-section-images.mjs 的约定:prompt 全写在脚本里(STYLE 是统一风格尾,三处「字迹/内容虚化」 是为了让模型不去渲染中文小字——渲染出来就是乱码)。空段落不配图,s6 这种没正文的段直接跳过。 每张图都是一条新版本,旧版本不动,所以重跑可回退。

shot-sections.mjs 为什么不用导出 HTML 截图:导出里的图位是 __IMG_01__ 占位符(公众号后台不认外部图, 得手工往占位符上传),渲染出来看不到真图。要看图只能看面板里的手机预览。

live-ui.mjs 的两个坑记在这里,省得下次重踩:

  • .click() 在这套面板里是无效的:React 的委托监听收得到,但点了不跳转。必须派发 pointerdown → mousedown → pointerup → mouseup → click 的完整序列(verify/cdp.mjs 里已封装)。
  • 挂载 ≠ 数据到了:等工作台要等 .gzs-card / .gzs-body / .gzs-gov 出现,不能只等 .gzs-root, 否则会读到骨架屏并误判成「首屏空白」。
  • 别用 innerText 等侧栏:这一版 GUI 把面板列表默认收成 display:none,隐藏文本不进 innerText,等待会一直假失败(按钮其实早在 DOM 里)。按 button.textContent 找。

live-interact.mjs 是唯一会真派发鼠标事件的套件(Input.dispatchMouseEvent), 所以它能抓到单测抓不到的两类问题:夹取公式算错(拖不动)、宽度没落盘(刷新就丢)。 它跑完会把对话列恢复成开着——测试不改用户的习惯。

左栏原生对话与分隔条:两个不能退回去的设计

  1. 原生对话是会话内嵌,不是 iframe。 左栏通过 ctx.sessions.retain/create 绑定本工作台 自己的会话,再用 session 作用域的 conversation.content 渲染。公众号与视频号各一条;没有就新建 空白会话。不要改回同源 /?gzhChat=1:完整 Web 应用会按 dsh.sessions.current 恢复宿主上次看的 会话,结果就是工作台里出现别的任务。会话 cwd 必须用 pipelineRoot,不能用插件数据目录 root。
  2. 中栏有两档下限。 MID_NEED=427 是自动布局的「够用宽度」(375 手机 + 两侧内边距), 由 CSS 守住;MID_FLOOR=300 是用户拖过左右栏后的「硬下限」,由内联 min-width 覆盖。 全用 427,真实窗口里只剩约 36px 可拖,看起来是「假动作」;全用 300,纯自动窄窗会把手机 预览压小并偏出中线。两档不能合并。

发布

包名 dsh-plugin-gzh-studio。package.json 的 files 白名单只带运行需要的东西 (22 个文件、解开约 0.76 MB):verify/、seeds/、*.bak 和历史截图都不进包—— 早期的包有 31 MB,就是因为没有 files。

  1. 改 version(semver)与 CHANGELOG;
  2. npm run verify —— 含 pack-check(包边界 / 体积 / 私有路径)与 install-check(从真打出来的 tarball 解包,跑一遍宿主半);
  3. npm publish --access public;
  4. 市场索引加一条:versions[].publishTimestamp 必须是真实的 npm 发布时间戳(不能留 0), source.type 用 npm;
  5. 用户侧:dsh plugin --profile web add dsh-plugin-gzh-studio@<version>。

市场索引入口格式见发布仓库里的 market-index.template.json(publishTimestamp 必须是真实发布时间戳)。

已知边界(没做的部分,写清楚不假装完整)

  • 提案是「约定」,不是「强制」:插件分不出一次 /save 请求来自人还是 Agent。所以实际规则是 ——人工在面板改字走 /save 直通不弹窗,Agent 改稿约定走 /propose。这个约定要靠 Agent 侧 的规则(skill / 提示词)约束。想强制,就得把 /save 也关进提案,那样改一个字也要点确认,很难用。
  • 只推草稿,不发布:已接公众号 draft/add —— 封面走永久素材拿 thumb_media_id、正文图走 uploadimg 换成 mmbiz URL、token 用 stable_token 缓存 7200 秒。仍然不做的两件: 群发/发表(要认证 + 群发权限,而且是不可撤销动作)和定时群发。 直连要把出口 IP 加进白名单(40164),家庭宽带的 IP 会变,所以错误里会把微信报的 IP 抠出来给你复制; 网关模式(wechat.json 里 mode: "gateway")留了位置,但还没接真实网关。
  • 推之前不压缩图片:正文图上限 2 MB、封面 10 MB,超了会明确报出来,需要自己压。
  • 推的是草稿不是发布:推完在「内容管理 → 草稿箱」,群发/发表仍然人工在后台点。
  • 占位图不能直接发微信:SVG 公众号不认。fake 节点只用于跑通流程与对齐版式。
  • 不做富文本编辑:正文只支持 **加粗** 和空行分段。要表格、代码块、卡片排版,去后台改。
  • 不做多作者协作:一个目录一个写者。多人同时改同一篇会互相覆盖(article.json 是整文件原子写)。
  • 禁忌词检查是字面匹配:切成词后按字面找(中文 includes、英文加词边界避免 AI 命中 SAID)。 它不懂语义——「裁员」会命中「不裁员」。宁可多一点假警,也不让人真写进去才发现。
  • 模板只有 6 套,且只覆盖正文:没有做封面套版(首图上不加字),因为中文封面字在生图模型上极不稳定。

致谢

形态与「版本管理 + 可观测」的内核直接搬自 Deckline / dsh-deck-studio(MIT): 三栏布局、记忆账本、旧版不覆盖、提案-确认后再写、多出图节点,都是它先做出来的。 lib/diff.js 的 LCS 行级对照同样源自它的 lib/diff.js,本项目在此基础上重写了类型宽度与超长退化处理。

—/ 5

No ratings yet

Verified DSH bundle

Commit df77e00ba508

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout