Skip to content
dsh.fish
Bundle

dsh-decision-map

决策地图 (Decision Map) — a DeepSeek Harness bundle plugin that renders the current session's execution trace as a readable timeline plus stat cards.

Source
Scitiger-AI
stars
1 stars
License
MIT
Updated
Updated 5 days ago

Readme

# dsh-decision-map · 决策地图

把当前会话的 **执行轨迹** 画成一张普通人一眼能看懂的图。它是 DeepSeek Harness 的一个**正式 bundle 插件**(双面:Node 半边 + 浏览器半边),在会话视图里新增一个「决策地图」标签页,展示两块内容:

1. **一条卡片式时间线** —— 从用户开口到任务完成,每个动作是一张卡片,卡片左侧有一颗 emoji 节点图标坐在时间线脊柱上:
   - `💭 思考`(琥珀色)—— 该步产生了推理(reasoning)
   - `🔧 工具`(蓝色)—— 一次工具调用
   - `✍️ 写文件`(绿色)—— 对 `write` / `edit` 这类会改动文件的工具的调用

   轮次之间用「第 N 轮 · N 个动作 · 跨度 X」分隔;每张卡片标注类型、工具名、耗时、`第N轮·第S步`,下方缩进显示详情(思考显示推理片段、工具显示参数)。**点击任一卡片**,右侧会展开一个详情面板,显示该动作的类型、耗时、时间与完整详情(选中卡片有高亮描边)——与内置「轨迹」的点击查看一致。与「轨迹」账本式视图的区别:决策地图把每一步做成带图标的卡片、按时间顺序铺开,一眼能看懂每一步「做了什么、花了多久」。

2. **一排统计卡** —— 总 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 身份字段

```json
"dsh": {
  "bundle": { "patch": "./cordis.patch.yml" }
}
```

这是 profile 组合器解析 bundle 的契约:`dsh.bundle.patch` 指向包内的一份 patch 文件。本包同时声明了浏览器半边:

```json
"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` 等):

```yaml
- 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/projection` push 帧送达浏览器。这与 `@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 这样注册:

```js
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`**。

```bash
# 方式 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 记账口径一致。

Install

dsh plugin --profile web add github:Scitiger-AI/dsh-decision-map

Profile: web

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source