🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

插件脚本定制 YuMo-233/dsh-kubejs

DeepSeek 客户端
YuMo-23330 天前有提交已提供中文文档扫描:含敏感能力兼容 / 相关生态

DSH 插件修改者:以独立脚本包定制其他已安装插件,不改插件源码KubeJS 模式

30.6/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/7 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

相关生态插件(非 dsh 原生,按其对应运行时安装)
git clone https://github.com/YuMo-233/dsh-kubejs.git

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么[目录]—
生态插件
可安装,未声明 dsh 能力
暂无时间戳·判据 →
装得上吗[实装验证]✓
本站已在 dsh 0.2.1-rc.5 下真实安装成功
非静态推断
L4 · 真实安装等级说明 →
数据截至 2026/11/02·判据 →
安全吗[自动扫描]—
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
数据截至 2026/11/04·判据 →
还在维护吗[仓库]✓
活跃:最近一次提交在 30 天前
暂无时间戳·判据 →
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →

可信度证据

安装兼容性检查⚠ 装前注意

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

展开查看检查明细 ↓

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

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

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/14 16:18:37

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件发起外部网络请求
查看扫描证据(共 38 处,展示前 34 条)
  • 读取本地文件
    index.js:189
    const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
  • 写入本地文件
    index.js:191
    writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
  • 写入本地文件
    index.js:217
    mkdirSync(scriptsRoot(), { recursive: true });
  • 写入本地文件
    index.js:218
    mkdirSync(join(scriptsRoot(), 'server_scripts'), { recursive: true });
  • 写入本地文件
    index.js:219
    mkdirSync(join(scriptsRoot(), 'client_scripts'), { recursive: true });
  • 发起网络请求
    client.js:62
    const res = await fetch(ROUTE)
  • 发起网络请求
    client.js:76
    const res = await fetch(ROUTE)
  • 发起网络请求
    client.js:204
    const next = await (await fetch(ROUTE)).json()
  • 发起网络请求
    client.js:216
    const res = await fetch(ROUTE, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) })
  • 读取本地文件
    host.js:348
    const stamp = statSync(scriptPath).mtimeMs;
  • 读取本地文件
    host.js:404
    files.push({ file: script.file, code: readFileSync(join(pkg.dir, script.file), 'utf8') });
  • 读取本地文件
    lib/resolve.js:27
    const json = JSON.parse(readFileSync(manifestPath, 'utf8'));
  • 读取本地文件
    lib/resolve.js:36
    const json = JSON.parse(readFileSync(manifestPath, 'utf8'));
  • 读取本地文件
    lib/resolve.js:47
    if (existsSync(dir) && statSync(dir).isFile()) dir = dirname(dir);
  • 读取本地文件
    lib/resolve.js:47
    if (existsSync(dir) && statSync(dir).isFile()) dir = dirname(dir);
  • 读取本地文件
    lib/resolve.js:53
    if (existsSync(candidate)) {
  • 读取本地文件
    lib/resolve.js:119
    patchText = existsSync(patchPath) ? readFileSync(patchPath, 'utf8') : '';
  • 写入本地文件
    lib/patch-ledger.js:249
    mkdirSync(pathDirname(patchPath), { recursive: true });
  • 读取本地文件
    lib/patch-ledger.js:251
    if (existsSync(patchPath)) {
  • 读取本地文件
    lib/patch-ledger.js:252
    text = readFileSync(patchPath, 'utf8');
  • 写入本地文件
    lib/patch-ledger.js:326
    writeFileSync(patchPath, text, 'utf8');
  • 读取本地文件
    lib/patch-ledger.js:332
    if (!existsSync(patchPath)) return [];
  • 读取本地文件
    lib/patch-ledger.js:333
    const text = readFileSync(patchPath, 'utf8');
  • 读取本地文件
    lib/scanner.js:43
    if (!existsSync(root)) {
  • 读取本地文件
    lib/scanner.js:49
    if (!existsSync(planeDir)) continue;
  • 读取本地文件
    lib/scanner.js:52
    entries = readdirSync(planeDir, { withFileTypes: true });
  • 读取本地文件
    lib/scanner.js:87
    if (!existsSync(manifestPath)) {
  • 读取本地文件
    lib/scanner.js:94
    manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
  • 读取本地文件
    lib/scanner.js:155
    return readdirSync(pkgDir, { withFileTypes: true })
  • 写入本地文件
    lib/tools.js:51
    writeFileSync(file, code, 'utf8');
  • 删除本地文件递归
    lib/tools.js:57
    rmSync(dir, { recursive: true, force: true });
  • 写入本地文件
    lib/tools.js:158
    mkdirSync(pkgDir, { recursive: true });
  • 写入本地文件
    lib/tools.js:159
    writeFileSync(filePath, args.content, 'utf8');
  • 读取本地文件
    lib/tools.js:209
    if (!existsSync(p)) orphans.push(script);
扫描于 2026/11/4(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

社区实装还没有人打卡 · 装过的第一个就是你
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 独立脚本包定制插件
  • 不改插件源码

README

由 DeepSeek 最新模型翻译生成
dsh-kubejs DSH 插件修改者 —— 用独立脚本包定制其他已安装插件,不改任何插件源码(KubeJS 模式)。 灵感来自 Minecraft 的 KubeJS:你想改别的 mod,不是去改它的源码,而是写一个脚本项目丢进 kubejs/ 目录。dsh-kubejs 把这套模式带进 DSH: - 插件(修改者)与脚本(修改)分离——dsh-kubejs 自身是普通 DSH 插件,只负责加载修改脚本; - 脚本集中放在独立目录,与插件代码完全隔离,插件更新、重装都不会丢修改; - 脚本按「目标插件」分包声明依赖,目标插件未安装或版本失配时整包禁用并报警,绝不静默失效。 解决什么问题 让 AI(或你自己)直接修改已安装插件是很脆弱的: | 直接改插件文件 | 用 dsh-kubejs 脚本 | | 插件一更新修改即丢失 | 修改独立存放,更新不丢 | | 改坏会拖垮 DSH 启动 | 脚本异常只禁用自己 + 通知报警 | | 修改散落各处无法 review | 集中目录 + 统一管理面板 | | 无法精确回滚配置覆写 | 配置覆写有账本,可精确摘除 | 五原语 脚本通过 activate(api) 获得五个原语,全部作用于已存在的东西: | 原语 | 作用 | 平面 |
展开完整文档(README 剩余 1 节)
dsh-kubejs

DSH 插件修改者 —— 用独立脚本包定制其他已安装插件,不改任何插件源码(KubeJS 模式)。

灵感来自 Minecraft 的 KubeJS:你想改别的 mod,不是去改它的源码,而是写一个脚本项目丢进 kubejs/ 目录。dsh-kubejs 把这套模式带进 DSH:

- 插件(修改者)与脚本(修改)分离——dsh-kubejs 自身是普通 DSH 插件,只负责加载修改脚本;
- 脚本集中放在独立目录,与插件代码完全隔离,插件更新、重装都不会丢修改;
- 脚本按「目标插件」分包声明依赖,目标插件未安装或版本失配时整包禁用并报警,绝不静默失效。

解决什么问题

让 AI(或你自己)直接修改已安装插件是很脆弱的:

| 直接改插件文件 | 用 dsh-kubejs 脚本 |
| 插件一更新修改即丢失 | 修改独立存放,更新不丢 |
| 改坏会拖垮 DSH 启动 | 脚本异常只禁用自己 + 通知报警 |
| 修改散落各处无法 review | 集中目录 + 统一管理面板 |
| 无法精确回滚配置覆写 | 配置覆写有账本,可精确摘除 |

五原语

脚本通过 activate(api) 获得五个原语,全部作用于已存在的东西:

| 原语 | 作用 | 平面 |
| api.on(event, handler) | 事件钩子(真实挂在宿主 cordis 事件上;waterfall 语义见下) | server |
| api.slot(id, props, Component) | 向 client 槽位注册 UI 组件 | client |
| api.config.override(path, value) | 配置覆写(写入 profile cordis.patch.yml 的托管区块,带账本可精确摘除) | server |
| api.service.wrap(name, wrapper) | 服务包装(洋葱式,可拦截/增强任意已注册服务) | server |
| api.fetch.wrap(matcher, handler) | 全局 fetch 拦截(洋葱式,运行时即时生效,无需重启) | server |

事件钩子怎么用

export function activate(api) {
// 只看不动:返回 undefined 就是完全透传,绝不干扰 DSH
api.on('agent/pre-step', (payload) => {
api.log.debug('当前步:', payload.step);
});

// 改决策:一定要先 await next() 拿到宿主的内建决策,再 spread 它
api.on('agent/pre-step', async (payload, next) => {
const decision = await next(); // { kind: 'enter', messages: [...] }
return { ...decision, delayHint: 800 };
});

// 拦截:不调 next(),直接返回自己的决策(宿主内建逻辑不再执行)
api.on('tools/pre-execute', (exec) => exec.name === 'kubejs_write_script'
? { kind: 'cancel', reason: '本会话禁止脚本自我改写' }
: undefined);
}

三条纪律:

1. 不拥有决策就返回 undefined(透传),别自造对象——自造会顶掉宿主的内建决策字段。
2. 想改就先 await next(),再 spread 它的返回值。
3. 钩子是异步的,handler 可以是 async;慢 handler 会拖慢事件(这本身就是节奏控制能力)。

逃生舱:api.ctx 全量直通(本机信任模型,读状态、钩冷门事件等「读或改」场景用它;想「造」新东西时请写成独立插件——脚本的产出是行为差异,插件的产出是可依赖、可分发的东西)。

fetch 拦截怎么用

export function activate(api) {
api.fetch.wrap({ urlIncludes: '/v1/chat/completions' }, async (request, next, helpers) => {
// 把「整条请求一刀切超时」换成「没字节流动才超时」的看门狗 const idle = helpers.createIdleSignal({ firstByteMs: 60_000, idleMs: 300_000, upstream: request.signal });
request.init.signal = idle.signal;

const res = await next(); // 返回 undefined 则由框架代发 return helpers.wrapResponseBody(res, idle.pulse, idle.dispose); }); }

要点:

- matcher 三种形态:函数 (info) => boolean、声明式 { urlIncludes, urlRegex, method, headers }、省略或 '*' 匹配全部(慎用)。
- request.init 是可改写的浅拷贝:mutate 它的 signal / method / headers 即可改请求,不会污染调用方对象。
- handler 返回 undefined = 你没代发,框架按 request 当前状态代发;返回 Response = 你已代发或自构造。
- helpers.createIdleSignal(opts) 内置空闲看门狗:首字节阈值 firstByteMs(默认 60s)+ 空闲阈值 idleMs(默认 300s,每来一个 chunk 重新上弦),upstream 可挂外部取消信号联动。解决「持续吐 token 的长思考流被墙钟总超时误杀」。
- 首次注册时装 globalThis.fetch 补丁,最后一条规则移除即还原,卸载即净;规则按注册顺序派发。

安装

方式一:DSH 官方插件管理器(推荐)

在 DSH Desktop 的「插件」面板中输入以下任一地址安装:

github:YuMo-233/dsh-kubejs
或直接填仓库地址 https://github.com/YuMo-233/dsh-kubejs。官方安装器会自动完成拉包、bundles 登记、cordis.patch.yml 注册,装完重启即可。

方式二:开发安装(link,改代码即时生效)

1. clone 到任意位置
git clone https://github.com/YuMo-233/dsh-kubejs.git

2. junction(或复制)进 DSH profile 的 node_modules Windows: mklink /J "%DSH_HOME%\profiles\desktop\node_modules\dsh-kubejs" "<clone 路径>"

3. 在 profile 的 package.json 里登记 dependencies 加: "dsh-kubejs": "link:<clone 路径>" dsh.profile.bundles 数组加:"dsh-kubejs"

4. 在 profile 的 cordis.patch.yml 里注册插件
- insert:
- id: dsh-kubejs
name: 'dsh-kubejs'

5. 重启 DSH Desktop

脚本目录

脚本不放在插件里,而是放在 DSH 数据目录(默认 ~/.dsh/):

~/.dsh/dsh-kubejs/
├── server_scripts/ # server 平面:事件钩子 / 配置覆写 / 服务包装(Node 侧,ESM) │ └── snowluma-humanize/ │ ├── manifest.json # 声明目标插件 │ └── humanize.js └── client_scripts/ # client 平面:槽位 UI(浏览器执行,纯脚本体) └── cachebilling-stats/ ├── manifest.json └── stats.js

manifest.json(包的唯一声明入口):

{
"target": "qq-bridge",
"targetRange": ">=1.0.0 <2.0.0",
"description": "让 snowluma 的回复更有人味", "author": "YuMo233", "disabled": false }

- target(必填):目标插件包名;targetRange(可选):semver 范围(支持 ^ ~ >= > < <= = 及空格 AND 组合)。
- author(可选):作者署名,写真正作者的名字(不是工具名——dsh-kubejs 是工具不是作者,也不是来源插件名;沿用别人的脚本保留原作者)。面板包卡片右上角会以灰色标签显示。
- 包内 .js 自动扫描发现;包内脚本禁止互相 import(脚本是叶子,不是构建块)。
- 失配以包为单位:目标未安装或版本不满足 → 整包禁用 + 日志 + notify 报警。
- 包内可放可选的 profiles 白名单,限制脚本只作用于特定 profile。

DshKubeJS 模式(AI 会话模式)

dsh-kubejs 会向 DSH 声明一个会话级 agent preset「dsh-kubejs 模式」(模仿创造模式)。选中该模式的会话会获得:

- 完整标准工具套(read / glob / grep / edit / write / pwsh / web / todo / subagent …);
- 四个专用工具:

| 工具 | 作用 |
| kubejs_inspect | 列出脚本包、脚本、失配/失败状态与配置账本(只读,写前先看) |
| kubejs_write_script | 写/改脚本文件,落盘前自动校验(JSON / ESM 语法 / client 禁 import-export / 路径逃逸) |
| kubejs_reload | 热重载全部脚本包(含 fetch 规则摘除重建) |
| kubejs_check | 健康报告:失配包、被隔离脚本、账本孤儿条目 |

- persona 纪律:禁止修改 plugins/、node_modules/ 下任何文件,一切修改走 dsh-kubejs 脚本。

管理面板

client 平面在左侧边栏注册「脚本」入口(位于「插件」按钮旁),点击后在主面板区打开管理页:浏览全部脚本包(状态徽章 / 失配红标)、client 脚本执行状态、配置覆写账本、热重载、调试开关。数据走同源路由 /dsh-kubejs/panel。

配置覆写账本

api.config.override 不直接改内存配置,而是把覆写写进 profile 的 cordis.patch.yml 托管区块:

--- dsh-kubejs managed BEGIN ---
script: snowluma-humanize/humanize.js
- id: qq-bridge
config:
reply:
humanize: true
--- dsh-kubejs managed END ---

每个条目带 # script: 归属注释;脚本删除或失配时精确摘除自己的条目,手工写的其他配置不受影响。

同一插件实例(同 - id:)分多次写不同路径是累加而非覆盖:账本按 (包 id + 配置路径) 深合并,写第二次不会抹掉第一次的键。

托管区块的标记行必须以 # 开头,写成裸 --- ... managed BEGIN --- 会让 DSH 起不来 —— 详见下面「已知坑与硬约束」。

故障隔离

- 脚本抛异常只禁用脚本自己 + notify 报警,绝不拖垮 DSH 启动;
- 运行期钩子异常只记日志;
- dsh-kubejs.debug 配置(或面板开关)打开后输出 api.log.debug 调试日志。

热重载(尽力)

| 修改内容 | 生效方式 |
| 事件钩子 / 配置覆写 / fetch 拦截 | kubejs_reload 即时生效(无需重启) |
| 服务包装 | 建议重启 DSH |
| client 槽位 | 刷新页面 |

能力边界

脚本是「修改者」不是「插件」:能钩既有事件、覆既有配置、包既有服务、填既有槽位、拦既有 fetch;不能造新接入点(provide 新服务 / 注册新工具 / 新路由)、不能引入新依赖(client 侧只能 require 页面已打包的模块)。需要这些时,请把脚本「毕业」成独立插件。

开发

node --test # 跑全部测试(推荐)
node test/tools.test.mjs # 四工具(参数校验 / 语法校验 / 落盘)
node test/host.test.mjs # host:扫描 / 加载 / 故障隔离
node test/ledger.test.mjs # 配置覆写账本:写入 / 摘除 / 幂等
node test/preset.test.mjs # agent preset 构建与工具行装配
node test/fetch-wrap.test.mjs # fetch 拦截:匹配 / 洋葱 / 卸载还原 + 空闲看门狗

已知坑与硬约束

这些是用真实故障换来的,改 dsh-kubejs 或写脚本前请先读一遍。前两条已有工程护栏兜底,剩下的靠纪律。

1. 绝不能往 cordis.patch.yml 写裸 ---(已有硬护栏)

症状:DSH Desktop 起不来,报 YAMLException: end of the stream or a document separator is expected。

原因:YAML 里 --- 是文档分隔符。托管区块的标记行如果写成裸 --- dsh-kubejs managed BEGIN ---,会把整个 patch.yml 劈成三个文档,解析直接失败。这个坑的真实来历只是「当初觉得 --- xxx --- 看起来像条醒目分隔线」——它没有任何功能必要性。

正确写法:标记行必须是注释,以 # 开头:

--- dsh-kubejs managed BEGIN ---
script: my-script
- id: some-plugin
config:
key: value
--- dsh-kubejs managed END ---

护栏:写入统一走 markerLine()(自动加 # ),定位走 findMarkerLine()(裸行和注释行都认,所以历史遗留的坏文件下次写入会自动痊愈)。此外 writeScriptOverrides() 在写盘前会用 findBareYamlSeparator() 扫全文,命中裸分隔符就抛错、一个字节都不写。所以 dsh-kubejs 不可能再写出一个会让 DSH 起不来的 patch.yml。

2. manifest.json 不能带 BOM

症状:包状态 invalid,日志 manifest.json 解析失败: Unexpected token,但文件肉眼看完全正常。

原因:用 Set-Content(PowerShell 默认)写文件会加 UTF-8 BOM(ef bb bf),JSON.parse 认不了首字节。

正确做法:写 manifest.json 用无 BOM 的 UTF-8(推荐 kubejs_write_script 工具,它走 writeFileSync(path, content, 'utf8'));手工写的话 PowerShell 用 -Encoding utf8NoBOM。

3. 改脚本 reload 即可,新增/删除脚本包也是

ESM 按 URL 缓存,kubejs_reload 会击穿缓存所以改内容即时生效;新增/删除脚本包同样只需 reload —— loadAll 每次都重扫脚本目录(实测新增包 reload 后立刻 status: ok 并加载),不需要重启 DSH。

真正需要重启的是改了脚本的 name(文件名):同一份代码换了文件名,宿主里会残留下旧 entry 的事件绑定。

4. 脚本必须用 ESM 导出 activate

服务端脚本只认 export function activate(api)。写成 CJS 的 module.exports = { activate } 不会报任何错,包状态仍是 ok、reload 也报成功,但 activate 永远不会被调用——脚本静默失效,最难查。

5. client 脚本不能 import/export

client 平面由 new Function 执行,只能用 require() 取页面已打包的模块(react、@deepseek-ai/dsh-client-ui-primitives 等)。写了 import 会在落盘校验阶段就被拒。

6. 脚本之间禁止互相 import

脚本是叶子不是构建块 —— 共享代码请写在同一个脚本里,或「毕业」成独立插件。

7. 事件钩子别自造决策对象

不拥有决策就 return undefined(透传);要改就先 await next() 拿宿主内建决策再 spread 它。直接返回自造对象会顶掉宿主的 kind/messages 等字段。

8. 绝不要手工 ctx.emit 别人的水面事件(会崩 DSH)

症状:DSH 整个挂掉,Host 子进程 exitCode: 1,日志末尾 dsh-plugin-desktop: fatal load failure: TypeError: next is not a function。

原因:水面(waterfall)事件的监听器签名是 (payload, next),别人实现的监听器会无条件调用 next()。脚本里图省事写 await ctx.emit('agent/request', { agent }) 只传了 payload、没传 next,任何第三方 listener(实测 @linxin666/dsh-liangshen 的 presets/liangshen/guard.mjs:374)一执行就抛 TypeError,冒泡成 host 的 fatal load failure,Host 进程被直接 kill。

正确做法:不要自己 emit 事件。要观察就用 api.on(事件, () => { ...; return undefined })(dsh-kubejs 的 bridge 会负责造 next);要自测就别走事件,直接调用自己的内部函数。

9. 宿主 webServer 的 POST body 读不可靠,状态走 query 兜底

症状:POST 带 JSON body 恒被拒(如 {"success":false,"error":"enabled must be boolean"}),但把同样的值放进 URL query 就成功。

原因:宿主 webServer 的 req 在这条链路上拿不到可靠的 body(实测连宿主自己的 /dsh-kubejs/panel 路由用 curl -d 都会 JSON body 解析失败),非脚本自身 bug。

正确做法:状态类接口同时支持 query 与 body 两个通道,且关键参数(如 workspaceId)走 query,body 只作为可选补充。写 readJsonBody 时用 chunks.push(chunk) + Buffer.concat(chunks).toString('utf8'),逐块字符串拼接不可靠。

License

MIT
预览分享卡片长什么样 ↓YuMo-233/dsh-kubejs
分享 · DeepSeek Harness Hub
YuMo-233/dsh-kubejs

DSH 插件修改者:以独立脚本包定制其他已安装插件,不改插件源码KubeJS 模式

这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。
认领条目 →
回到顶部