dsh-decision-map
dsh-decision-map
- Stars
- 0
- Language
- JavaScript
- Created
- Aug 17, 2026
- Updated
- Aug 17, 2026
Introduction
dsh-decision-map · 决策地图
把当前会话的 执行轨迹 画成一张普通人一眼能看懂的图。它是 DeepSeek Harness 的一个正式 bundle 插件(双面:Node 半边 + 浏览器半边),在会话视图里新增一个「决策地图」标签页,展示两块内容:
-
一条卡片式时间线 —— 从用户开口到任务完成,每个动作是一张卡片,卡片左侧有一颗 emoji 节点图标坐在时间线脊柱上:
💭 思考(琥珀色)—— 该步产生了推理(reasoning)🔧 工具(蓝色)—— 一次工具调用✍️ 写文件(绿色)—— 对write/edit这类会改动文件的工具的调用
轮次之间用「第 N 轮 · N 个动作 · 跨度 X」分隔;每张卡片标注类型、工具名、耗时、
第N轮·第S步,下方缩进显示详情(思考显示推理片段、工具显示参数)。点击任一卡片,右侧会展开一个详情面板,显示该动作的类型、耗时、时间与完整详情(选中卡片有高亮描边)——与内置「轨迹」的点击查看一致。与「轨迹」账本式视图的区别:决策地图把每一步做成带图标的卡片、按时间顺序铺开,一眼能看懂每一步「做了什么、花了多久」。 -
一排统计卡 —— 总 Token(含输入/输出拆分)、工具调用次数、轮次数、最耗时的步骤。
纯前端绘制:内联样式 + 简单 div,不引用任何外部库、CDN、字体或图片(emoji 是系统自带字形)。配色使用 shell 自带的 --dsw-static-* 静态色与 --dsw-alias-* 主题别名,明暗主题自动适配。
目录结构
dsh-decision-map/
├── package.json # 双面包身份声明(dsh.bundle.patch + dsh.client)
├── cordis.patch.yml # bundle patch:insert 一行 { id: decision-map, name: dsh-decision-map }
├── lib/
│ ├── index.js # node half:注册 decisionMap 会话投影(fold 事件日志 → 时间线 + 统计)
│ └── client.js # browser half:注册 conversation.view 标签页,读投影并渲染
└── README.md
无构建、无 TypeScript、零运行时依赖:装下来开箱即用。node half 不 import 任何包(会话投影 seam 只调用 schema.parse(value),所以 schema 用一段手写的 .parse 校验器实现,不必依赖 zod——这也让 link: 本地安装不再需要物化任何依赖);browser half 是手写的 window.__ModuleLoader__.load({ id, factory }) bundle(与随发行版交付的客户端插件同一形态,但无需 tsdown 打包)。
身份声明(取证结论)
1. package.json 里的 bundle 身份字段
"dsh": {
"bundle": { "patch": "./cordis.patch.yml" }
}
这是 profile 组合器解析 bundle 的契约:dsh.bundle.patch 指向包内的一份 patch 文件。本包同时声明了浏览器半边:
"dsh": {
"bundle": { "patch": "./cordis.patch.yml" },
"client": { "platform": "web" }
}
dsh.client.platform: "web" 让 @deepseek-ai/dsh-client-modules 的 node half 把本包扫进 window.__DSH_BOOT__ 浏览器名录,并经由 exports["./client"] 提供 /plugins/dsh-decision-map/client.js。
2. cordis.patch.yml 的 insert 结构
顶层是一个数组,insert 下列出要插入的插件行,每行 { id, name }(可选 config / disabled 等):
- insert:
- id: decision-map
name: dsh-decision-map
name 是包名(由 loader 解析),id 是这行的稳定标识,供后续 patch 层按 id 覆盖。
3. 会话轨迹数据用哪个服务
- 原始事件日志由
ctx.sessions(SessionStore,来自@deepseek-ai/dsh-session)持有,session.events是 append-only 的{ type, seq, time, data }事件序列。 - 本项目不直接订阅事件,而是走 会话投影 seam(
ctx.sessionProjections,来自@deepseek-ai/dsh-session-projection):node half 注册一个纯函数 fold 单元,框架负责在每个提交事件上驱动它,并把成品值经 api-proxy 的 tail page 与session/projectionpush 帧送达浏览器。这与@deepseek-ai/dsh-session-stats的sessionStats是同一套机制,是「node 计算 → 浏览器读取」的无构建宿主→客户端通道。
折叠的事件:turn/start、step/start、assistant/chunk(reasoning/text/tool-call delta)、assistant/message(usage)、tool/call、tool/result、step/end。token 取 usage 的 inputTokens + cacheReadTokens + cacheWriteTokens + outputTokens;工具耗时按 callId 配对 tool/call → tool/result;「最耗时步骤」按 step/start → step/end;「思考」节点的耗时为该步首次 reasoning 到首个非 reasoning token 的时长(TTFT)。
4. 浏览器半挂标签页的真实 slot 接缝
conversation.view(kind: 'list'、scope: 'session')是会话视图标签环。browser half 这样注册:
ctx.slots.inject("conversation.view", () => ctx.slots.register({
name: "conversation.view",
id: "decision-map", // 标签 id,也作为 ViewTab.id
order: 20, // 标签顺序
label: "决策地图", // 标签显示名(plain string 或 () => string)
}, DecisionMapView));
slots.inject 把注册挂到本插件 fiber 上,插件卸载时自动移除标签。视图组件通过标准 session kit 的 useProjection("decisionMap") 读取 node half 算好的值。
安装与使用
用 dsh plugin 把本包装进某个 profile。因为本包声明了 dsh.bundle.patch,dsh plugin add 会自动把它并入该 profile 的 dsh.profile.bundles 层栈,随后 profile 组合器应用本包自带的 cordis.patch.yml(即 insert 那行 { id: decision-map, name: dsh-decision-map })——无需手工编辑 profile 的 cordis.patch.yml。
# 方式 A:在本包目录内(package.json 所在目录)用 `.`:
cd /path/to/dsh-decision-map
dsh plugin --profile web add .
# 方式 B:在本包目录的父级目录,用相对路径:
dsh plugin --profile web add ./dsh-decision-map
# 方式 C:发布到 npm 后按包名安装:
dsh plugin --profile web add dsh-decision-map
注意:dsh plugin add 会把相对路径锚定到「你执行命令时所在的目录」,所以别在包目录内写成 add ./decision-map(那会指向不存在的子目录)。重启(或触发配置热重载)后,进入任意会话,标题栏的视图标签里会出现「决策地图」。
说明:
sessionProjections注册表随 base bundle 挂载于宿主平面;sessionStats等既有投影键的送达链路(tail page / push 帧)会自动携带本包新增的decisionMap键,无需任何客户端侧注册。
数据流
会话事件日志 (session.events)
│ 每个提交事件
▼
ctx.sessionProjections.register({ key: "decisionMap", init/apply/view })
│ 折叠成 { timeline, stats },view 输出经手写 .parse 校验器校验
▼
api-proxy tail page + session/projection push 帧(宿主 → 浏览器)
│
▼
useProjection("decisionMap") ← 决策地图标签页组件
│
▼
时间线 + 统计卡(React.createElement + 内联样式)
生命周期与可撤销性
- node half:
ctx.sessionProjections.register(...)本身就是注册表在调用方 fiber 上登记的 effect,卸载即移除decisionMap键及其缓存单元(与dsh-session-stats一致)。 - browser half:
ctx.slots.inject("conversation.view", ...)把注册挂到本插件 fiber,卸载即移除标签页。
两半都不创建进程级/页面级副作用,均可被 Cordis 的 stop/update/unload 干净回收。
已知取舍
- 投影值随 tail page 全量携带 ——
decisionMap的timeline是完整动作列表,会话很长时投影值会变大(每个节点只含type/turn/step/seq/time/durationMs/label/detail,detail已截断至 120 字符)。这是该 seam 的既定代价,对常规会话可忽略。 - 「写文件」是启发式分类 —— 仅把工具名恰为
write/edit的调用标为绿色;bash等既能读也能写的工具归入「工具」,不臆测其是否落盘。 - 思考耗时是 TTFT 近似 —— 推理的墙钟时间用「该步首次 reasoning 到首个非 reasoning token」近似;无输出 token 的步其思考节点耗时为空。
- token 是 provider 上报值 —— 只有 adapter 上报了
usage的assistant/message才计入;未上报则为 0,与核心的 token 记账口径一致。