[dsh-plugin-browserskill] observationTabOpen 未校验 state.splits / state.bottomSplits,会话切换必抛 TypeError
建议标题:observationTabOpen 未判空 state.splits / state.bottomSplits,在新版 dsh-better-sidebar 下抛 Cannot read properties of undefined (reading 'kind')
环境
| 组件 |
版本 |
@wxg-prc-cpg/browser-skill-dsh-plugin |
0.2.1(npm latest) |
dsh-better-sidebar |
0.19.0(把右列交给 DSH 原生右侧栏的那一版) |
| DSH / dsh CLI |
0.1.5-rc.1 |
| 平台 |
Debian 13 + DSH Desktop(Linux) |
复现
- 使用
dsh-better-sidebar@0.19.0(peer 地板为 @deepseek-ai/dsh-* ^0.1.5-rc.1);
- 启动 DSH,新建会话或切换会话;
- 右侧栏整体被宿主错误边界接管,显示:
dsh-better-sidebar: TypeError: Cannot read properties of undefined (reading 'kind')
[重试]
点「重试」后恢复;每个新会话都会复现。
注意报错前缀是 dsh-better-sidebar: —— 那是宿主错误边界的署名,实际抛错方是本插件。
调用栈(浏览器实际堆栈)
at leafNodes (.../client.js:4463:13)
at leafNodes.next (<anonymous>)
at observationTabOpen (.../client.js:4472:70)
at evaluate (.../client.js:4507:19)
at SidebarStore.notify (.../client.js:74506:49)
at SidebarStore.setSession (.../client.js:74406:10)
根因
lib/client.cjs:
function* leafNodes(node) {
if (node.kind === "leaf") { // ← node 为 undefined 时抛错
yield node;
return;
}
for (const child of node.children) yield* leafNodes(child);
}
function observationTabOpen(state) {
if (state === void 0) return false; // 只判了 state 本身
for (const root of [state.splits, state.bottomSplits]) // ← 这两个字段可能为 undefined
for (const leaf of leafNodes(root)) // ← 于是把 undefined 交给 leafNodes
if (leaf.tabs.some((tab) => tab.type === "browserskill:observation")) return true;
return false;
}
state 来自 service.getSnapshot()(dsh-better-sidebar 的侧栏 store 快照),
state.splits / state.bottomSplits 是它的分栏工作台布局树。
- 在
dsh-better-sidebar@0.18.x 上,这两个字段即使是空树也存在(形如 { kind: "leaf", tabs: [] }),缺陷被掩盖;
0.19.0 把右列移交给 DSH 0.1.5 的内置右侧栏后,会话挂载瞬间快照中的布局树字段尚未就绪(undefined),
于是 leafNodes(undefined) 必然抛 TypeError。
结论:本插件对宿主 store 的内部形状做了过强假设,是潜在缺陷被上游契约变更暴露,
并非 DSH 内核 API 的直接破坏性变更(本插件用的是 better-sidebar 的服务,不是内核 sidebar 服务)。
建议修复(本地已验证)
function* leafNodes(node) {
if (node === void 0) return; // ① 入口判空
if (node.kind === "leaf") {
yield node;
return;
}
for (const child of node.children ?? []) yield* leafNodes(child); // ② children 判空
}
function observationTabOpen(state) {
if (state === void 0) return false;
for (const root of [state.splits, state.bottomSplits]) {
if (root === void 0) continue; // ③ 跳过尚未就绪的根节点
for (const leaf of leafNodes(root))
if (leaf.tabs?.some((tab) => tab.type === "browserskill:observation")) return true; // ④ tabs 判空
}
return false;
}
更稳妥的做法:把 service.getSnapshot().state 统一经过一个 normalizeLayout(state) 收敛
(缺字段时回退为空树),这样宿主 store 继续演进也不会再次踩空。
补充证据:本插件在 package.json 的 dsh.client 中并未声明 dsh-better-sidebar 依赖
(inject: ["@deepseek-ai/dsh-client-ui-tool", "@deepseek-ai/dsh-client-ui-layout"]),
而是在运行时探测侧栏服务(源码注释:the disposer the cordis fiber invokes when the sidebar service goes away)。
也就是说,侧栏服务对本插件是一个「可能缺席、版本可漂移」的可选宿主 —— 既然如此,
其快照的内部形状就更不应该被硬假定,这也是建议引入 normalizeLayout() 的核心理由。
验证
本地按上述四处修改后:
- 重启 DSH,新建会话 / 切换会话均不再出现该报错;
- 「观察 tab」在布局树未就绪时安静跳过(不会被误判成“已打开”),待 store 再次 notify 后能正常自动打开。
English summary
observationTabOpen(state) only guards state === undefined, then passes state.splits /
state.bottomSplits straight into leafNodes(node). With dsh-better-sidebar@0.19.0 (the release
that hands the right column over to DSH 0.1.5's native right sidebar) those fields are undefined
right after SidebarStore.setSession, so node.kind throws
TypeError: Cannot read properties of undefined (reading 'kind') on every session switch and the
whole sidebar gets replaced by the host error boundary. Suggested fix: guard node / root /
children / tabs as above (or normalize the layout state). Previously masked by 0.18.x, where
the layout fields were always present.
[dsh-plugin-browserskill] observationTabOpen 未校验 state.splits / state.bottomSplits,会话切换必抛 TypeError
建议标题:
observationTabOpen未判空state.splits/state.bottomSplits,在新版dsh-better-sidebar下抛Cannot read properties of undefined (reading 'kind')环境
@wxg-prc-cpg/browser-skill-dsh-pluginlatest)dsh-better-sidebar复现
dsh-better-sidebar@0.19.0(peer 地板为@deepseek-ai/dsh-* ^0.1.5-rc.1);点「重试」后恢复;每个新会话都会复现。
注意报错前缀是
dsh-better-sidebar:—— 那是宿主错误边界的署名,实际抛错方是本插件。调用栈(浏览器实际堆栈)
根因
lib/client.cjs:state来自service.getSnapshot()(dsh-better-sidebar的侧栏 store 快照),state.splits/state.bottomSplits是它的分栏工作台布局树。dsh-better-sidebar@0.18.x上,这两个字段即使是空树也存在(形如{ kind: "leaf", tabs: [] }),缺陷被掩盖;0.19.0把右列移交给 DSH 0.1.5 的内置右侧栏后,会话挂载瞬间快照中的布局树字段尚未就绪(undefined),于是
leafNodes(undefined)必然抛 TypeError。结论:本插件对宿主 store 的内部形状做了过强假设,是潜在缺陷被上游契约变更暴露,
并非 DSH 内核 API 的直接破坏性变更(本插件用的是 better-sidebar 的服务,不是内核 sidebar 服务)。
建议修复(本地已验证)
更稳妥的做法:把
service.getSnapshot().state统一经过一个normalizeLayout(state)收敛(缺字段时回退为空树),这样宿主 store 继续演进也不会再次踩空。
补充证据:本插件在
package.json的dsh.client中并未声明dsh-better-sidebar依赖(
inject: ["@deepseek-ai/dsh-client-ui-tool", "@deepseek-ai/dsh-client-ui-layout"]),而是在运行时探测侧栏服务(源码注释:the disposer the cordis fiber invokes when the sidebar service goes away)。
也就是说,侧栏服务对本插件是一个「可能缺席、版本可漂移」的可选宿主 —— 既然如此,
其快照的内部形状就更不应该被硬假定,这也是建议引入
normalizeLayout()的核心理由。验证
本地按上述四处修改后:
English summary
observationTabOpen(state)only guardsstate === undefined, then passesstate.splits/state.bottomSplitsstraight intoleafNodes(node). Withdsh-better-sidebar@0.19.0(the releasethat hands the right column over to DSH 0.1.5's native right sidebar) those fields are
undefinedright after
SidebarStore.setSession, sonode.kindthrowsTypeError: Cannot read properties of undefined (reading 'kind')on every session switch and thewhole sidebar gets replaced by the host error boundary. Suggested fix: guard
node/root/children/tabsas above (or normalize the layout state). Previously masked by 0.18.x, wherethe layout fields were always present.