Repository navigation
Replies: 1 comment
|
补充:用户侧预防工具(不替代上游修复) 上游修好之前,用户侧唯一的规避是「给被挂载的插件目录补一个含 name + version 的 package.json」。 https://github.com/Linicc/dsh-plugin-guard 它以 DSH 组合包形式发布(声明 dsh.bundle.patch,含编译产物),可直接安装: dsh plugin --profile add github:Linicc/dsh-plugin-guard 它在 host 启动时(早于任何模型请求)扫描所有 profile 的 patch 层,复刻本贴所述的判定逻辑, 需要说明的是:
|
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
摘要
@deepseek-ai/dsh-plugin-package-inventory-deepseek在请求期解析每个活跃 Loader 行的包身份。当该行
name是路径、且最近的package.json有name但无version时,它throw;异常被
dsh-llm-deepseek包成REQUEST_EXTENSION,每一轮 DeepSeek 请求全部失败。被命中的 manifest 是 DSH 自己生成的:
profiles/<name>/package.json内容为{ "name": "dsh-profile-desktop" }——默认就没有version。因此任何在 profile 中挂载、而自身目录没有
package.json的本地插件都会触发。环境
channel: nightly,Windows 10.0.26200);dsh0.1.0-rc.6app.asar提取核对);cordis4.0.1deepseek-official/deepseek-flashprepareRequestExtensions阶段)master分支同源代码仍存在此问题(已核对
packages/llm/plugin-package-inventory-deepseek/src/index.ts,identityFromManifest与另一处cannot resolve active package的抛出逻辑均未加降级)——即这不是已修复的旧版残留
最小复现
重启后任意一次 DeepSeek 请求即失败,且此后每一轮都失败,与 prompt 内容无关。
在该插件目录补上
{ "name": "x", "version": "0.0.0" }立刻恢复(注意这是用户侧临时规避,不是修复;且
PackageIdentityResolver带进程内缓存,运行期补完 manifest 需重启才生效)。
根因
identityFromManifest()(已编译产物等价代码):路径型行使
packageName === undefined,于是allowAnonymous = true;但该逃逸口只检查
name——「有 name、无 version」的 manifest 逃不掉,直落throw。这是缺陷,不是配置错误
调用方本来就允许解析失败:
resolve()的返回类型也声明为… | undefined。抛出与调用契约自相矛盾。该扩展的产物是尽力而为的诊断性元数据(README:对模型 token 影响为 0),
同一函数内序列化失败路径已做优雅降级——唯独身份解析 fail-closed 并具备请求级否决权,
属错误分级失当。架构笔记
2026-09-10-public-package-manifest.zh.md亦写明「App-boot 通过
Partial适配无需发布身份的本地 profile」。建议修复
改动 1(根因,必需):身份不可解析时按调用方契约降级
当前
identityFromManifest()把「manifest 不完整」与「manifest 损坏」混为一谈,两者一律
throw。但如果 manifest 能被JSON.parse出来,它就不是损坏,只是不足以构成发布身份——这类条目本来就该被跳过,而调用方也确实准备好了出口。
function identityFromManifest(path: string, allowAnonymous: boolean) : DeepSeekPluginPackageIdentity | undefined { const manifest = JSON.parse(readFileSync(path, 'utf8')) as PackageManifest - if (allowAnonymous && manifest.name === undefined) return undefined - if (typeof manifest.name !== 'string' || manifest.name.length === 0 - || typeof manifest.version !== 'string' || manifest.version.length === 0) { - throw new Error( - `plugin-package-inventory-deepseek: ${path} must declare non-empty name and version`) - } + const hasName = typeof manifest.name === 'string' && manifest.name.length > 0 + const hasVersion = typeof manifest.version === 'string' && manifest.version.length > 0 + if (!hasName || !hasVersion) { + // 身份不可解析属于「元数据缺失」,不是请求级错误。 + // allowAnonymous 的语义是「这是没有发布身份的松散模块」,本应同时覆盖缺 name 与缺 version。 + if (allowAnonymous) return undefined + throw new Error( + `plugin-package-inventory-deepseek: ${path} must declare non-empty name and version`) + } return { name: manifest.name, version: manifest.version } }为什么只改这一处就够:
allowAnonymous由packageName === undefined决定,即「该行是路径而非裸包名」——正是「本地挂载的松散模块」这一情形。
把判据从「只容忍缺
name」放宽为「缺name或version任一即容忍」,与调用方undefined → continue的契约完全对齐,profiles/<name>/package.json自然被跳过。行为变化:此前打死整轮请求的情况,变为「该条目不出现在
dsh_plugin_packages里」。这是可观测的降级,不是静默伪造——下游看到的是「目录里没有它」,
而不是「一个看起来完整、实则错误的目录」。这一点很重要:
如果为了止血而返回一个空数组冒充完整目录,就把「元数据缺失」伪装成了「目录为空」。
改动 2(同类缺陷,建议一并修)
resolve()内还有第二处没有undefined出口的抛出,成因相同:manifest = barePackageManifest(packageName, anchors, this.packages) if (manifest === undefined) { - throw new Error( - `plugin-package-inventory-deepseek: cannot resolve active package ${JSON.stringify(packageName)}`) + // 与 caller 契约一致:裸包名解析不到 = 该条目不可诊断,跳过而非中断请求 + return undefined }理由同上:调用方已经能处理
undefined,这里抛出同样属于错误分级失当。若维护者认为裸包名解析失败必须保持可见,可退一步——写入 debug 日志后仍
return undefined,但不要让它继续上升为请求级失败。
改动 3(防御纵深,建议;可与改动 1、2 解耦)
即使 1、2 落地,未来任何第三方清单扩展出问题仍可能打死请求。
建议在
dsh-llm-deepseek的prepareRequestExtensions()边界,把「可选扩展准备失败」统一降级为:
error.cause一并带出这与该函数内已有的序列化降级路径(payload 退回 base request、
onOmitted上报)语义完全一致,属于把同一条策略补齐到身份解析路径,而不是新增机制。
不应采用的做法
package.jsoncollectActivePluginPackages外层包一个大 try/catch测试覆盖建议
version(本例)namedsh_plugin_packages中且字段正确PackageIdentityResolver.cache会记忆undefined)一个相关的小观察
PackageIdentityResolver的cache会把undefined一并记住(源码中已有 TODO:Invalidate manifest identities if in-process package-version replacement
becomes a supported upgrade path)。这意味着运行期补上
package.json后需重启才生效。不是本 issue 的根因,但会影响用户按「补 manifest」自救时的预期,值得在文档里提一句。
排障提示
遇到
REQUEST_EXTENSION先看error.cause——UI 上的「DeepSeek request extension preparation failed」是被 adapter 覆盖过的文案,
原始错误藏在 cause 里,直接看 UI 会误判为模型或账号问题。
All reactions