Skip to content

空会话的兜底轮次卡缺少 agentEvent,画布渲染抛 TypeError 整体崩溃 #1

Description

@heimixieb

现象

打开 /dsh-flow/ 后抛错,整块渲染失败:

TypeError: Cannot read properties of undefined (reading 'from')

只要工作区里存在一张没有任何投影消息的轮次卡就必现。这不限于新建的空白会话 —— 见下面「实际触发场景」。

根因

conversationCards() 构造轮次卡有两处,形状不一致。

正常路径 src/canvas/session.js:259 带了 agentEvent:

agentEvent: questionAgent === null ? null : { kind: questionAgent.kind, from: questionAgent.from, to: questionAgent.to, label: questionAgent.title },

兜底路径 src/canvas/session.js:276-286(if (turns.length === 0),turns.push 在 281)漏了这个键:

turns.push({
  id, positionKey, kind: 'turn', dshThreadId: thread.id, sourceParentId: thread.parentId, parentId: null,
  sourceSeq: undefined, turnIndex: 0, naturalPosition, position: savedPosition ?? naturalPosition, positionLocked: savedPosition !== undefined,
  question: thread.dshSessionTitle ?? thread.title, answer: null, error: null, processCount: 0,
})

于是该卡的 card.agentEvent 是 undefined,而渲染侧用的是 !== null 守卫 —— undefined 能通过这个守卫:

  • src/canvas/view.js:135-136 —— card.agentEvent.from,崩溃点
  • src/canvas/view.js:146 —— card.agentEvent.from
  • src/canvas/teams.js:99-100 —— card.agentEvent.from,有团队时同样会崩
  • src/canvas/view.js:121 —— 不崩,但该卡会被误标为「智能体消息」

实际触发场景:历史会话被渲染成空卡

本机命中的不是新建的空会话,而是 4 个已持久化的历史会话:

会话 画布显示 磁盘上的真实内容
session-f319c4ff「修复图片放大bug」 0 条消息 6032 个事件:14 条 user/message、398 条 assistant/message、414 次 tool/call,日志 2.8 MB
session-15eb7b0e(浏览器里开着的那个) 60 条消息 正常

宿主侧,list() 只给活着的会话:

// dsh-session/lib/index.js:1559
/** All live sessions, in creation order. */
list() { return [...this.store.values()].map((entry) => entry.session) }

插件侧,建卡与回放分工如下:

// index.js:1267 —— 启动时的一次性历史回填,扫的就是上面那份 live 列表
for (const session of ctx.sessions.list()) replaySession(session)
// index.js:1265 —— 之后只在 session/created 时回放
ctx.on('session/created', replaySession)
// index.js:297-310 —— syncSessions 拿浏览器给的完整会话列表建卡片外壳(events: [])

所以 syncSessions 会给全部会话建好带标题的空壳,但历史只有 live 会话会被回放。插件刚装好、宿主重启的那一刻,活着的只有浏览器里开着的那个,其余历史会话就停在「有标题、0 条消息」—— 然后被上面那张缺 agentEvent 的卡炸掉。

影响面:任何已持久化、当前进程内未激活的会话,在画布上都是一张空卡,并触发崩溃。

复现

最小复现:让自动投影收录一个 0 条消息的会话(新建会话但不发言即可),打开 /dsh-flow/,渲染到该卡时抛错。

完整复现:装好插件后重启宿主,只保持一个会话在浏览器中打开,其余历史会话即成为空卡。

建议修复

给兜底卡补上 agentEvent: null 即可。session.js 只有这两处构造轮次卡,补上后不变量重新成立:

--- a/src/canvas/session.js
+++ b/src/canvas/session.js
@@ -281,7 +281,7 @@ function conversationCards(threads) {
       turns.push({
         id, positionKey, kind: 'turn', dshThreadId: thread.id, sourceParentId: thread.parentId, parentId: null,
         sourceSeq: undefined, turnIndex: 0, naturalPosition, position: savedPosition ?? naturalPosition, positionLocked: savedPosition !== undefined,
-        question: thread.dshSessionTitle ?? thread.title, answer: null, error: null, processCount: 0,
+        agentEvent: null, question: thread.dshSessionTitle ?? thread.title, answer: null, error: null, processCount: 0,
       })
     }

另外建议把上述 4 处的 !== null / === null 收紧成 != null:这个不变量已经被静默破坏过一次,而破坏它的代价是整页崩溃,不是少一个字段。

附:一个想确认的点

历史会话先显示为空卡,是否属于有意的懒加载?卡片点击走 flow:activate-session → sessions.open(),而 DSH 侧是 "Emit session/created exactly once for an entered session",一进入就会触发 session/created,回放跟着跑,历史应该就出来了。

如果是,那这里不算独立缺陷,只是建议把空状态分开:用「等待助手回复」表示一个已经有 398 条助手消息的会话是有误导性的,「尚未加载」和「等待回复」或许应该区分。

如果不是(点击之后历史不会补齐),请告诉我,我再补复现。

验证

用 DOM 桩加载真实模块(含真实 engine.js),拿一个 messages: [] 的线程走真实的 conversationCards()。

修复前,报错与线上逐字一致:

'agentEvent' in card : false
card.agentEvent      : undefined
RESULT               : TypeError: Cannot read properties of undefined (reading 'from')
teams.js:99          : TypeError: Cannot read properties of undefined (reading 'from')

修复后:

'agentEvent' in card : true
card.agentEvent      : null
RESULT               : rendered OK, speaker = null dot = null
teams.js:99          : OK, bucket = main

本地已用 pnpm patch 应用上述修复,画布恢复正常。截至 2026-09-16,main 的 src/canvas/session.js:284 仍未带 agentEvent,本 issue 对当前 HEAD 依然有效。

环境

  • dsh-flow 0.3.0(commit 3abeaae),github:rootkiller6788/dsh-flow
  • 宿主:DeepSeek Harness 0.1.5-rc.1(web profile)
  • Node.js v24.19.0 / Windows

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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions