Source launch duplicates dsh-typert-protocol for out-of-tree plugins, silently disabling their Remote layer #1993
Replies: 2 comments
|
Confirmed at source level and implemented as a cherry-pick-ready branch - this is the same module-identity failure class as #1697, and it gets the same fix mechanism. Source audit (master 47f9438)
Patch: fix/typert-remote-markers-shared-registryhttps://github.com/zoahdev/deepseek-harness/tree/fix/typert-remote-markers-shared-registry
Verification
Thanks for the exact repro (source-launch + out-of-tree plugin + 404) - this one closes a whole class of "my Remote layer silently doesn't exist" reports. |
|
@zoahdev 的 第三个实例:
|
| # | 包 | 模块私有状态 | 状态 |
|---|---|---|---|
| #1697 | (@zoahdev 的第一例) | 装饰器/身份 | 已修(Symbol.for) |
| 本帖 #1993 | dsh-typert-protocol |
WeakMap 装饰器标记 |
已修(Symbol.for 分支就绪) |
| #3751 | dsh-tools |
unique symbol 调度器键 |
未修 |
三例、三个包、同一个机制、同一个修法。@zoahdev 你那个分支的形状(Symbol.for('<包名>/<用途>') 键的 globalThis registry、语义不变)在 #3751 上应该能原样套——那边只是一个符号常量,比 WeakMap 那例还简单。如果要把这类一次性收掉,三个一起提比分三次提有力得多。
楼主的"方向 1"我可以给一条独立佐证
Make duplication detectable. Have the protocol module register its instance identity on a
globalThissymbol at load and warn when a second instance appears. One log line would have replaced this entire investigation.
这句话我完全同意,而且我们(做 DSH 上的外部插件)已经在自己的回归里被迫实现了它的粗糙版——因为同一个坑对仓库外插件更致命,我们盯的是包在依赖树里出现几次:
// profile 不许解析出自己的 core 拷贝——它会遮住 CLI 那份
const profileCore = sh(`find ${profileRoot}/node_modules -maxdepth 4 -path '*/@deepseek-ai/dsh-agent' -print`)
if (profileCore !== '') throw new Error(`the profile resolved its own core copy at ${profileCore}`)也就是说:"同一个包被加载两次"在 DSH 的依赖布局下不是理论风险,而是外部插件作者必须每次发版主动检查的东西。 我们只能从文件系统层面猜,因为运行时没有任何东西会告诉我们。楼主提的那条(模块自己在 globalThis 上登记身份、发现第二份就 warn)在运行时层面做,比我们这种从外面数目录的办法准确一个量级,而且对所有消费者一次生效。
顺带说,这三例合起来正好说明为什么"方向 1"值得独立做:Symbol.for 修的是"重复了也能正常工作",检测修的是"重复了至少有人知道"。 前者要一个包一个包地改(现在三个,以后还会有第四个),后者一次装上就覆盖所有还没被发现的实例——包括那些症状不是 404、不是 undefined.prepare,而是别的什么更难认的东西。
边界与利益相关
我们不修 DSH 自家组件——typert、tools、agent-loop 都在 DSH 里。上面是跨帖串联和一条外部视角的佐证。
Symbol.for 能不能原样套到 #3751,我没有在 dsh-tools 上实跑验证过——那是我在那帖给出的建议,依据是机制相同,最终以实际改的人的判断为准。
利益相关:我维护 pi2dsh(Pi 生态兼容层)。这条不推销——模块重复是依赖布局层的问题,多装一个插件只会让树更复杂。
Uh oh!
There was an error while loading. Please reload this page.
Follow-up: source launch silently breaks the Remote layer of every out-of-tree plugin
Reporting a concrete instance of the extensibility gap discussed above, with a reproduction and a root cause.
Symptom
A third-party plugin installed under
$DSH_HOME/profiles/<profile>/node_modules/registers aTypertRemoteServicesubclass with@Remotemethods. The host half loads correctly and its model-facing tools work. Every one of its/api/<namespace>/<method>endpoints returns a bare404 not found.A 404 from
RpcHostmeansTypertGatewayService.claimsEndpoint()returned false — the request never reached argument validation, so there is no error, no log line, and nothing that points at the real layer. The plugin author reasonably concludes their namespace is unsupported.Root cause
remoteMethods()reads decorator markers from a module-privateWeakMap(packages/typert/protocol/src/index.ts:126).Under the documented source launch (
node --import tsx/esm apps/cli/src/bin.ts), tsx applies the repositorytsconfig.base.jsonpathsat runtime:The host therefore loads the protocol from
src/. A plugin installed under$DSH_HOMEis outside the tsconfig scope, so its identical bare import resolves the ordinary Node way tolib/index.js.Two module instances, two private
WeakMaps:collectSrcClaims()/resolveSrcDescriptor()read the src copy and find nothingThe SRC fallback is doing exactly what it should; it simply cannot see markers written into a different module instance.
Evidence
Measured under the source launcher, comparing the copy the plugin's compiled class extends against the copy the host uses:
Single-variable intervention — same profile, same plugin commit, only the launcher changed:
POST /api/pluginMarket/statusnode --import tsx/esm apps/cli/src/bin.ts web404 not foundnode apps/cli/lib/bin.js web{"ok":true,"value":{"totalPlugins":1164,...}}Switching my own host to the built launcher fixed the plugin's Settings UI with no change to the plugin.
Why this is worth addressing rather than documenting
The failure is silent on both sides and the observable symptom points at the wrong layer. It affects the exact population the plugin system is meant to serve: developers running DSH from a checkout while installing community plugins into
$DSH_HOME. It also generalizes beyond Typert — any cross-package registry keyed by module-private state (decorator metadata,instanceofchecks, Symbol registries, memoized singletons) has the same exposure.Possible directions
globalThissymbol at load and warn when a second instance appears. One log line would have replaced this entire investigation.claimsEndpoint()rejects an endpoint whose namespace matches a live Cordis service that carries atypertRemotebinding but exposes no markers, that specific combination is almost certainly module duplication and could say so instead of 404.Directions 1 and 2 look cheap and independent of whichever way 3 is resolved.
Environment
DSH
0.1.0-rc.5, Windows, plugin installed withdsh plugin --profile web add <github spec>.All reactions