现象
打开 /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
现象
打开
/dsh-flow/后抛错,整块渲染失败:只要工作区里存在一张没有任何投影消息的轮次卡就必现。这不限于新建的空白会话 —— 见下面「实际触发场景」。
根因
conversationCards()构造轮次卡有两处,形状不一致。正常路径
src/canvas/session.js:259带了agentEvent:兜底路径
src/canvas/session.js:276-286(if (turns.length === 0),turns.push在 281)漏了这个键:于是该卡的
card.agentEvent是undefined,而渲染侧用的是!== null守卫 ——undefined能通过这个守卫:src/canvas/view.js:135-136——card.agentEvent.from,崩溃点src/canvas/view.js:146——card.agentEvent.fromsrc/canvas/teams.js:99-100——card.agentEvent.from,有团队时同样会崩src/canvas/view.js:121—— 不崩,但该卡会被误标为「智能体消息」实际触发场景:历史会话被渲染成空卡
本机命中的不是新建的空会话,而是 4 个已持久化的历史会话:
session-f319c4ff「修复图片放大bug」user/message、398 条assistant/message、414 次tool/call,日志 2.8 MBsession-15eb7b0e(浏览器里开着的那个)宿主侧,
list()只给活着的会话:插件侧,建卡与回放分工如下:
所以
syncSessions会给全部会话建好带标题的空壳,但历史只有 live 会话会被回放。插件刚装好、宿主重启的那一刻,活着的只有浏览器里开着的那个,其余历史会话就停在「有标题、0 条消息」—— 然后被上面那张缺agentEvent的卡炸掉。影响面:任何已持久化、当前进程内未激活的会话,在画布上都是一张空卡,并触发崩溃。
复现
最小复现:让自动投影收录一个 0 条消息的会话(新建会话但不发言即可),打开
/dsh-flow/,渲染到该卡时抛错。完整复现:装好插件后重启宿主,只保持一个会话在浏览器中打开,其余历史会话即成为空卡。
建议修复
给兜底卡补上
agentEvent: null即可。session.js只有这两处构造轮次卡,补上后不变量重新成立:另外建议把上述 4 处的
!== null/=== null收紧成!= null:这个不变量已经被静默破坏过一次,而破坏它的代价是整页崩溃,不是少一个字段。附:一个想确认的点
历史会话先显示为空卡,是否属于有意的懒加载?卡片点击走
flow:activate-session→sessions.open(),而 DSH 侧是 "Emitsession/createdexactly once for an entered session",一进入就会触发session/created,回放跟着跑,历史应该就出来了。如果是,那这里不算独立缺陷,只是建议把空状态分开:用「等待助手回复」表示一个已经有 398 条助手消息的会话是有误导性的,「尚未加载」和「等待回复」或许应该区分。
如果不是(点击之后历史不会补齐),请告诉我,我再补复现。
验证
用 DOM 桩加载真实模块(含真实
engine.js),拿一个messages: []的线程走真实的conversationCards()。修复前,报错与线上逐字一致:
修复后:
本地已用
pnpm patch应用上述修复,画布恢复正常。截至 2026-09-16,main的src/canvas/session.js:284仍未带agentEvent,本 issue 对当前 HEAD 依然有效。环境
github:rootkiller6788/dsh-flowwebprofile)