- ホーム
- プラグイン
- ワークフローと自動化
- dsh-daily
dsh-daily
dingchenhui0618-arch/dsh-daily
Daily brief for DeepSeek Harness. Every day at a fixed hour it ranks the plugin market by real install counts and stars, cross-checks it against your own agent memory and working conventions, and proposes two kinds of item: concrete deltas on the plugins people actually use (original does A, the proposal does B) and directions nobody has built yet. The popularity numbers come from the catalog the market itself publishes and are quoted into the prompt rather than guessed; an entry the market has not measured is shown as unmeasured, never as zero. Approving an item opens a new session named after it and hands that session a fixed delivery contract — implement independently, never fork, test green before publishing. Rejected sources are remembered so they stop coming back.
インストール
dsh plugin --profile web add github:dingchenhui0618-arch/dsh-dailyREADME
dsh-daily
每天 11:00,把 DSH 插件生态筛一遍,产出两样东西:在别人已发布的作品上往前走一步的具体改法,以及生态里还没有的新方向;你点一下,agent 就独立实现一个版本,发到你自己的仓库。
A daily brief for DeepSeek Harness. It reads the published plugin ecosystem against your own profile, proposes concrete deltas on other people's work plus directions nobody has built yet — and turns one click into an independently implemented repository, never a fork.
它解决什么
插件生态里已经有几千个包,靠人翻是翻不完的。但"每天推荐 N 个"本身没什么价值——真正贵的是看完之后动手。所以这个插件把两件事分成不同节拍:
| 频率 | 成本 | 谁做 | |
|---|---|---|---|
| 日报:改进项 + 灵感 + 证据 | 每天 11:00 自动 | 一次模型调用 | 插件 |
| 开发 + 发布 | 你点「做这个」才发生 | 一整个开发流程 | agent |
它不会每天自动造一个仓库。 那样一个月就是 30 个仓,绝大多数是噪声。批准的权力在你手上,插件只负责把选项摆好、把活递出去。
日报产出两栏,来源不同、规矩也不同:
改进项 —— 拿别人已经发布的插件当靶子。核心字段是 delta:原版是 A,我要做成 B。只写「做得更好」「增强体验」这类空话的会被直接丢弃——因为那种话读起来像答案,实际什么都没说。
灵感 —— 生态里还没有、你自己可以立的新方向。没有来源仓库可锚定,所以规矩只有两条:字段不能缺,证据要么引用真实存在的东西、要么老实写「无直接证据」。界面上后者会打「未经证实」的标记——它就是推测,不该长得像调研结论。
两栏都会防重复:改进项按来源仓库记(同一来源不再出现),灵感按标题记并回灌进 prompt(同一方向不再重复提)。
候选池来自市场,并且按真实热度排序
改进项的价值取决于靶子选得对不对。所以候选池和排序都不再由本地快照决定:
- 数据源是市场自己的目录:
https://awesome-dsh-plugin.com/catalog.json,也就是 app 里那个市场读的同一份文档。一次 GET 拿回全部条目,带分类、描述、下载量、star 数与收录日期。实测 2026-10-15:4765 条、23 个分类、2444 条有下载量、3723 条有 star,文档里最新一条是当天收录的。 - 排序按热度,不是按行号。每个分类里优先取装的人最多的那几个。原本按语料行序取,等于随机——本机实测同一份候选集,按热度选出来的 60 条下载量合计是不排序的 46 倍,中位数从 0 变成 25828,60 个名额里换掉了 58 个。
- 提示词里带真实数字,并且明确告知「候选已按热度排序、这些数字取自市场」。模型因此知道哪些插件真的有人在用,以及该往哪儿找缺口。
- 没有数字就说没有。市场没给某个条目下载量时它是
—,绝不按 0 处理:0 是「没人装过」,缺失是「市场没测到」,两句话不一样。报告里那条会显示无热门度数据。 - 分层降级,并且自己说出来。取不到市场(离线、被墙、站点挂了)就回落到本地语料,此时没有热度排序,界面上会写明「候选(本地语料)」+ 一行警告;连语料也没有才报错。市场数据缓存到
$DSH_HOME/daily/market-cache.json,24 小时内不重复拉;失败只记一次(attemptedAt单独记),所以离线一上午只付一次连接超时,而不是每次轮询都付。
热度只有两个来源,且下载量优先、star 只用来打平。这不是随手定的:第一版把两个排名加权相加(0.65×rank(下载) + 0.35×rank(star)),结果当 star 数相同或缺失时 star 项会反压下载量——测试里三条都是 3★ 的条目,star 排名退化成文档顺序,直接把 5000 下载的条目压到 1 下载的下面。第二版还试过一个「字段完整度」加权项,实测在真实目录上它 100% 等价于「是否同时有下载量和 star」,同样是能反转主信号的假信号,所以删掉了。现在按 (下载量, star) 排序,主信号不可能被次信号掀翻。
要人工核对一次:
node scripts/verify-market.mjs # 拉真实目录,打印预筛结果、分类头名与 A/B 数字
它只读、只打印,不改任何数据。
为什么日报不全是开发工具
这是踩过的一次坑。插件读的画像(agent 记忆 + 工作约定)通篇是工程纪律:证据边界、安全门、密钥扫描、开源同步。模型照实读,于是交回来的 8 条全是网关、台账和门禁——这是正确行为,是画像把它塑造窄了。
候选池从来不是问题:它按 23 个生态分类轮转取样,Just for Fun、Themes & Appearance、Voice & Audio 本来就在里面(实测 60 条覆盖 23/23 个分类)。所以修的是两处指令:
- 画像多一节「非官方来源的插件」。官方 bundle 是 harness 本身,其余的(含
dsh-holdem)是他自己写或特意装的——把兴趣范围摆到模型眼前,而不是让它从工程记忆里推断。 - 每条产出带
type,取值工具 / 游戏 / 趣味 / 外观 / 多媒体 / 传播。prompt 明确要求至少覆盖 3 种,并且要主动去找游戏、趣味、外观、多媒体、传播方向。
模型如果还是交回一份全是「工具」的清单,host 会用更强硬的措辞重试一次;重试仍是同一个类型就保留内容,只在界面上标出「今天所有条目都是同一个类型」——宁可告诉你它没变宽,也不假装这是一份均衡的日报。
独立实现,不是 fork
这是这个插件的硬规则,也是它和"fork 别人的仓改一改"的根本区别:
- 禁止
gh repo fork;禁止 clone 原仓库后在它基础上改;禁止整段搬运原版代码。 - 建的是你自己的仓库(
gh repo create <你的项目名>),不是从原仓库派生。 - README 里必须写明灵感来自哪个原仓库(带 URL),并说清你做了什么不同。
- 确实需要复用原版代码片段时,先确认它的 LICENSE 允许,并在文件里保留原版权声明。
借鉴思路可以,复制代码不行。 所以校验里还有一条:如果模型把 title 写成原仓库的名字(dsh-alpha、DSH Alpha、acme/dsh-alpha 都算),这条会被当作"改名重发"丢弃并计数。
两个 title 都必须小写 ASCII、形如 dsh-xxx——它会被直接用作 GitHub 仓库名和本地目录名。
四条硬保证
模型的输出在落到你眼前之前,要过四道纯函数校验(src/report.js,全部有单测):
- 不许编造来源。 每条改进的
basedOn必须逐字命中预筛池;不命中就丢弃并计数。链接和分类也取自语料,不采信模型给的 URL。 - 不许说空话。
delta为空、或只是「做得更好 / 增强体验 / 优化性能」这类空话,丢弃并计数。why同理(「很强大 / 值得一试」会被丢弃)。 - 不许改名重发。
title撞上原仓库的名字就丢弃并计数,见上一节。两栏之间也不许重名——名字一样的两条你分不出来,按名字批准也会歧义。名字里连字母数字都没有(!!!这种)单独计为「无名字」,因为它没法当仓库名。 - 证据要么可核验,要么标明是猜的。
evidence必须引用池子里的某个 repo 或一个真实链接;拿不出来就只能写无直接证据,界面上会打上「未经证实」的标记,不会冒充调研结论。
界面上会显示每次生成丢弃了多少条、分别因为什么被丢,所以模型糊弄不了你。
装
# npm 版 CLI 管理的 profile
dsh plugin --profile <name> add <插件目录>
# 桌面版:desktop profile 由 Electron app 独占,npm 版 CLI 会被拒绝——
# error: profile "desktop" is managed exclusively by the Electron application
# 用它自带的那个 CLI 才行(在 app 的 resources/runtime/cli/bin/dsh 下):
"<app>/resources/runtime/cli/bin/dsh" plugin --profile desktop add <插件目录>
装完重启一次,对话视图里会出现「日报」Tab。
改 host 半区必须重启 app。 Node 按 URL 缓存 ES 模块,
plugin_manager的关开、 甚至卸载 bundle 再装回,都不会换掉已经加载的 host 代码。客户端半区可以热更新,host 不行。
用
- 立即生成 —— 不用等 11:00,随时手动跑一次。
- 改进项的「做这个」 —— 批准一条改进,新建一个窗口去做。
- 灵感的「做这个」 —— 批准一个新方向,同样新建窗口,投递的是另一套框架(说明这是生态里还没有的东西)。
- 「不感兴趣」 —— 把这条的来源记进 seen,以后不再出现。
点下去之后,插件不自己开发,也不往你当前这个会话里塞东西。它按项目名新建一个会话(dsh-xxx),把交付契约当第一条消息投进去,那个窗口立刻开始干活。
新建走的是 GUI 自己「新会话」的同一条宿主路径:
ctx.sessionController.create({ workspaceId })—— 和界面上点「新会话」落到的是同一个函数,所以会话附在你的项目工作区分组里,不会掉进「未分组」;按路径找不到登记的工作区时退回{ cwd }。- 然后
sessionController.prompt({ sessionId, mode: 'queue', content })投第一条消息。这一步是必须的:全新会话是blank状态,侧边栏不画 blank 行,只有真起了一轮 turn 它才可见——只建会话不发消息等于什么都没发生。 - 再把标题钉成项目名(
sessionController.rename({ sessionId, title }),写一条user来源的session/title事件),免得自动命名和你要的名字不一致。
第一条契约消息以用户轮次(source.kind: 'user')投递,因为点「做这个」的人就是你。老的 { kind: 'plugin', plugin } 会被 v4 会话格式直接拒收;plugin:<name> 虽然能过落盘校验,但在这个版本里不是一等生产者、也不算人类轮次,会话活跃度与自动标题都不会认它。
ctx.sessionController 不存在时退回 ctx.agents.create({ cwd }) + agent.followup()。这条路一样能开窗、一样不用刷新页面,区别只是会话会落进「未分组」。
同一个条目点两次只会开一个窗口。 批准接口是幂等的:同名条目已成功过就直接回放上次结果(replayed: true),不会开第二个窗口、也不会白跑一轮开发。开窗失败会如实报告原因(create-failed: …),不会自动重试——自动重试可能给同一个条目开出第二个窗口。
投递的消息里写死了交付契约:
- 独立实现,不要 fork。 禁止
gh repo fork;禁止 clone 原仓库改;禁止整段搬运原版代码 - 在
D:\Projects\<你自己的项目名>起工程,骨架照抄dsh-warden - 写实现和测试,
node --test必须全绿,测试不过不许发布 - 推送前跑三步检查:密钥扫描、
.gitignore核对、无关文件清理 - 三步检查任一命中就停下来报告,不许推送
- 全过才用
gh repo create <你自己的项目名> --public --source . --push建你自己的仓库 - 回报仓库 URL、测试通过数、commit hash、三步检查结果,以及你和原版的具体差异
改 host 半区要重启 app 才生效(见上面「热更新」一节),改客户端半区刷新页面即可。
配置
apply(ctx, config) 的 config 全部可覆盖:
| key | 默认 | 说明 |
|---|---|---|
hour / minute | 11 / 0 | 每日触发时刻(本地时间) |
maxCandidates | 60 | 送进模型的预筛池上限 |
perCategory | 4 | 每个分类最多进池几个。语料有 23 个分类,所以 4 足够填满 60;设成 2 只能凑到 46 |
maxImprovements | 10 | 日报最多几条改进项 |
maxIdeas | 3 | 日报最多几条灵感 |
maxTokens | 16000 | 单次生成的最大输出 token。要留出推理余量:推理 token 也算在里面,所以给 JSON 的余量比这个数字小得多 |
historyKeep | 30 | 保留多少天历史 |
profileName | desktop | 读哪个 profile 的已装插件清单 |
corpusPath | $DSH_HOME/daily/corpus.csv | 语料。不存在时从 seedCorpus 复制一份 |
seedCorpus | 无 | 首次播种来源;不配就是空语料 |
marketSource | 市场 catalog.json | 热度数据源。指向镜像或固定副本时改这里 |
marketTimeoutMs | 20000 | 单次取目录的超时 |
projectsRoot | ~/Projects | 交给 agent 的契约里,新工程建在哪 |
templateRoot | 无 | 骨架照抄哪个现成插件;不配时契约改为描述骨架要求 |
本机配置:$DSH_HOME/daily/config.json
上面凡是跟你这台机器有关的 key(语料在哪、项目建在哪、照抄哪个骨架),都不写进仓库。公开仓库里塞死某台机器的绝对路径,既泄露本机布局,也让别人 clone 下来开箱即废。
本机值放在 $DSH_HOME/daily/config.json,插件加载时读:
{
"seedCorpus": "D:\\somewhere\\plugins.csv",
"projectsRoot": "D:\\Projects",
"templateRoot": "D:\\Projects\\some-skeleton"
}
优先级:显式 config > config.json > 内置通用默认。文件坏了(JSON 不合法)只退回默认值,不会让插件加载失败。
数据落在哪
| 路径 | 内容 |
|---|---|
$DSH_HOME/daily/corpus.csv | 本地语料。只在市场取不到时才作为候选池 |
$DSH_HOME/daily/market-cache.json | 上一次成功取回的市场目录(含 fetchedAt / attemptedAt / 文档里的最新收录日期) |
$DSH_HOME/daily/market.json | 最近一次市场读取的结论(状态、数据日期、带热度条目数、警告),面板直接读它,不再联网 |
$DSH_HOME/daily/reports/YYYY-MM-DD.json | 每天的完整日报(含每条的类型、丢弃统计与原始回复) |
$DSH_HOME/daily/state.json | seen(来源仓库)、提过的灵感标题、历史索引、批准记录 |
$DSH_HOME/daily/approvals.jsonl | 批准审计流水 |
HTTP 面(挂在 webServer 的 /dsh-daily 前缀下):
GET /dsh-daily/state 配置、状态、当天日报、历史、批准记录、最近一次市场读取结论
POST /dsh-daily/run 立即生成(202,异步)
POST /dsh-daily/market 强制刷新市场目录(忽略 24 小时缓存)
POST /dsh-daily/approve { title } 按项目名批准(改进项或灵感都行),或 { basedOn } 按来源批准
→ { ok, spawned, replayed, sessionId, title, reason }
POST /dsh-daily/skip { repo } 不再出现
容错姿态
这个插件任何一处坏掉都不该拖垮 harness,所以:读不到语料、模型路由不可用、模型返回的不是 JSON、stream 以 finish{kind:'error'} 结束、agent 正忙——全部降级成界面上的一行状态,不抛异常。webServer.register 失败也只是没有 HTTP 面,host 半区和定时器照常工作。缺的画像来源会显式列在 status/报告里,而不是悄悄生成一份泛泛的日报。
模型偶尔吐不出 JSON 是常态,不是异常。 本机实测 deepseek-flash 在这条链路上会返回无法解析的回复(同一个模型给另一个插件做审查时 13 次里有 2 次失败)。所以生成不是一锤子买卖:
- 第一次不解析 → 追加「只输出 JSON 本身」的更强约束重试一次;
- 重试仍不解析 → 把
finish原因、收到的字符数和原始回复前 2000 字写进报告,同时在界面上报错。
这一条是刻意的:空报告绝不能被伪装成「今天没有好东西」。 没有这段,模型失败和风平浪静在界面上长得一模一样。
被截断的回复:救回来,而不是扔掉
finish=max-tokens 意味着输出预算用尽——注意推理 token 也计入 maxTokens,所以给 JSON 的余量比看上去少得多。每个 delta 变成一句具体的话之后,一份日报的体积大约翻了一倍,8000 的预算就不够了(默认已提到 16000)。
但这暴露了一个更糟的设计错误:被截断的回复当时是被整份丢掉的。 一份在最后一条写到一半的回复里,前面几条往往是完整的、合格的——把它们扔掉等于丢掉了模型已经做出来的工作,界面上还会显示「0 条」配一个错误,是两头都最坏的结果。
现在的处理:
- 先补救再放弃。 字符串感知的括号扫描器把每个完整闭合的对象捞出来(
salvageReport)。只有完整的才要,而且它们走和其他条目完全一样的校验——补救可以增加条目,但绝不能让不合格的混进来。 - 按预算重试。 重试时不再重复同样的要求(那样只会再次超预算),而是明确要求更短的清单:最多 4 个改进项、2 个灵感,每条 60 字以内。写不完整不如少写几条。
- 两版取内容多的那版。 一份「完整但空」的重试不能顶掉一份有 3 条真内容的补救结果——告诉用户「今天没有值得做的」比告诉他「还有更多没写完」更糟。
- 如实标注。 救回来的报告带
truncated: true,界面上是琥珀色提示(不是红色错误):写完的 N 条已保留,其余丢失。它既不是失败,也不是完整报告。
对着真实失败数据验过:那份 3498 字符、parseReportJson 全盘失败的回复,补救逻辑捞回了 3 条完整改进项,全部通过校验、零丢弃,覆盖工具/多媒体/外观三类。
开发
node --test # 200 个测试
node scripts/build.mjs
node scripts/verify-market.mjs # 需要联网:拿真实目录复核热度排序
零依赖构建:host 半区只 import node: 内置模块和同目录兄弟模块,所以复制 src/*.js 到 lib/ 就是完整构建——没有会在安装时挂掉的 devDependency。client 半区是 CJS,构建时把唯一的相对 require(样式表)内联进去,然后用 new Function() 编译一次,并在浏览器之外直接 import lib/index.js 校验导出,坏构建在构建期就炸。
测试用桩 Context 驱动真实的 apply()(test/host.test.mjs),跑的是真的排期、真的池子过滤、真的输出校验、真的持久化和真的开窗投递,不是这些逻辑的复刻;它默认注入一个必然失败的 fetch 替身,否则在有网的机器上测试会真的去读市场目录,每条语料断言都会随当天发布的插件漂移。test/market.test.mjs 管住热度信号:缺失的数字不许变成 0、不是目录的回复不许变成空池、刷新失败要回落缓存并说出数据日期、失败要记 attemptedAt 而不是假装刷新成功、只允许从 https 白名单主机取数。test/spawn.test.mjs 单独管住开窗那几个会被用户直接看见的细节(窗口名、不动当前会话、双击只开一个窗口、找不到工作区时退回 cwd)。test/corpus.test.mjs 里有一条直接跑真实 3727 条语料的用例。test/client.test.mjs 把客户端半区也拉进测试(只桩掉 React),管住 Tab 的注册契约和版本错配的判断方向。
License
MIT