Skip to content

observationTabOpen 未判空 state.splits / state.bottomSplits,会话切换必抛 Cannot read properties of undefined #229

Description

@sy121718

[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)

复现

  1. 使用 dsh-better-sidebar@0.19.0(peer 地板为 @deepseek-ai/dsh-* ^0.1.5-rc.1);
  2. 启动 DSH,新建会话或切换会话
  3. 右侧栏整体被宿主错误边界接管,显示:
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.jsondsh.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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions