dsh-workflow-map
DeepSeek Harness 分步工作流地图:把会话折叠成一条分步列表,给每个进度百分比标注来源而不是只报一个数,并支持把提示词排入下一步队列、被取用前可撤回。
安装
dsh plugin --profile web add dsh-workflow-map
沙箱测试结果
| 阶段 | 结果 |
|---|---|
| 1 安装 | ✓ |
| 2 列装 | ✓ |
| 3 挂载 | ✓ |
| 4 启动 | — |
四阶段在隔离沙箱里跑:安装(有网)→ 列装 → 挂载 → 启动(后三步断网)。
README
dsh-workflow-map
[English](README.en.md) · 简体中文
DeepSeek Harness 插件:把当前会话渲染为一张可交互的分步工作流地图。
https://github.com/deepseek-ai/deepseek-harness/discussions/11660
宿主适配要求(Host requirement)
- DSH 内核:engines.dsh=>=0.2.1-rc.1 <0.3.0-0 || >=0.2.2-rc.1 <0.3.0-0,覆盖0.2.1全系列(0.2.1-rc.1至0.2.1-rc.5)与0.2.1正式版,并显式覆盖0.2.2预发布链(本机0.2.2-rc.1已实测)。
- Node.js:>=20(engines.node)。
- 宿主包以peerDependencies声明(均为可选 peer,区间与engines.dsh一致),不打包宿主;低于0.2.1-rc.1或0.3.0及以上的宿主不在声明范围内。
- 区间写法:两段并集。第一段>=0.2.1-rc.1 <0.3.0-0在0.2.1元组上带显式预发布标签,用于匹配0.2.1-rc.x;第二段>=0.2.2-rc.1 <0.3.0-0让 npm / pnpm 默认的 engines 判据也接受0.2.2-rc.x(单段>=0.2.1-rc.1 <0.3.0-0在默认判据下会静默排除其它元组的预发布版本,DSH 自身闸门带includePrerelease则不受影响)。上界<0.3.0-0小于0.3.0的任何预发布版本,因此整条0.3线(含0.3.0-rc.1)都在区间外。
逐版本核验方式与结论:
宿主版本 · 核验方式 · 结论
0.2.1-rc.3 · 实机运行:插件加载、面板注入、视图路由 · 基线版本
0.2.1-rc.1 / 0.2.1-rc.2 / 0.2.1-rc.4 / 0.2.1-rc.5 · 跨版本契约核验:闭包元数据、逐文件 SHA-256、契约锚点窗口 · 九个契约点未变
0.2.2-rc.1 · 新内核实机核验:插件启用态、宿主半 ESM 链接、九个契约点、活体 workflowView 投影写入 · 适配,加载期无硬失败
契约核验不等于实机运行:0.2.1 那四个版本只处理已发布的产物与契约锚点,未启动宿主。0.2.2-rc.1 是 2026-11-06 在升级后的本机内核上做的实机核验(声明区间、加载链、运行投影三层),方法与证据见 [docs/host-compatibility.md](docs/host-compatibility.md)。
三个入口共用同一份数据与同一套组件:侧边栏面板项(sidebar.panellist)、主内容区(main)、
会话标签栏(conversation.view)。面板将会话事件折叠为分步序列,展示每步的状态、进度、
工具调用与文件操作,并支持在会话运行中向其下一步注入提示词。
*主界面(深色):左为侧边栏入口选中态,中为串行主流程(每步一张单元卡,两侧圆形接口为工具/文件
类型,运行中的单元高亮并带流动外框),右为分步详情页,底部为常驻注入条。*
界面截图(完整预览)
 · 
主界面 · 浅色主题:与深色同一布局,配色随宿主主题切换 · 运行中的单元:流动渐变外框标出「此刻在调用」
 · 
分步详情:右页列出该步的类型清单、调用参数与结果摘要 · 热注入:提示词排入下一步执行队列,被取用前可撤回
 ·
以上六张为离屏渲染的界面预览,不含任何真实会话内容。
1. 它解决什么问题
长任务的会话日志会增长到上千条事件。定位「当前处于第几步」「这一步做了什么」「已经完成多少」
需要在日志中回溯。本插件把这三类信息呈现为一屏可读的视图,数据来源与判定规则全部可追溯。
界面结构
┌──────────────────────────────┬───────────────────┐ │ 当前会话 4/10 · 46% ⚙ │ ├──────────────────────────────┼───────────────────┤ │ ◯─┐ 01/10 骨架与最小注册 │ 第 5 步详情 │ │ │ ▓▓▓▓▓▓▓▓▓▓ 100% 自报 │ ──────────────── │ │ │ │ 这一步骤调用的 │ │ ◯─┘ 02/10 宿主投影与折叠 │ ● md 类型 4 处 │ │ │ ▓▓▓▓▓▓▓▓▓▓ 100% 自报 │ ● js 类型 2 处 │ │ │ │ ● pwsh类型 2 处 │ │ ◯─┐ 05/10 浏览器壳与挂载点 ← 运行中(高亮) │ │ │ ▓▓▓▓▓░░░░░ ~58% 估算 │ 第 5 步详情 │ │ │ 3 分钟前估计 │ 这一步骤交了什么 │ │ │ │ ● 已完成 │ │ ◯─┘ 06/10 分步卡片与进度条 │ 12 工具 12 文件 │ │ ▓▓▓░░░░░░░ ~36% 估算 │ ──────────────── │ │ │ 热注入 [.......] │ └──────────────────────────────┴───────────────────┘ 主流程(每步一个单元,两侧圆点为类型接口) 右页(点击后打开)
示意图,非截图;实际界面见上方截图。
2. 核心能力
以下四块能力对应界面上的四个区域:注入(页头按钮与右页输入条)、进度(卡片下方轨道)、
布局(主流程与两侧接口)、状态编码(动画与配色)。
2.1 热注入(steer / unsteer)
写入语义:POST /dsh-workflow/steer 调用宿主的 agent.steer(message),消息进入会话收件箱的
next-step 队列,在当前轮次结束后由下一步取用。调用不触发 cancel,因此不中断正在执行的轮次。
端点 · 方法 · 语义
/dsh-workflow/steer · POST · 写入一条提示词到 next-step;响应返回本次生成的 messageId
/dsh-workflow/unsteer · POST · 按 messageId 调用 agent.inbox.remove(),把尚未被取用的消息移出收件箱
状态判定(三者互斥,均由日志派生,不由前端推测):
状态 · 判定依据
待拾取 · 存在 agent/inbox/spliced 且无同 id 的 user/message
已接下 · 存在同 id 的 user/message(轮次已 claim)
已撤销 · 存在 agent/inbox/spliced { outcome: "canceled" } 且该 id 未被 claim
HTTP 200 仅表示消息已进入收件箱,不等价于会话已读取;「待拾取」与「已接下」的区别由上述日志
事件区分。撤回的持久记录是 splice 的位置而非消息 id,因此折叠侧维护一份收件箱台账
(按宿主 inboxProjectionDefinition 的 splice 语义逐条重放)才能定位被移除的具体行。
失败语义:文档型错误一律返回 JSON,不向 HTTP 层抛出异常。常见状态码:400 缺字段 /
body 非 JSON,404 会话不存在,409 会话未在运行或消息已被取用,413 文本超 2000 字或
body 超 64 KiB,503 宿主未暴露相应能力。
2.2 进度估算
每步的百分比按以下优先级取值(src/fold.mjs 的 percentFor):
优先级 · 来源 · 计算规则 · 上限
1 · 模型自报 model · 取该步最近一次 stepProgress/report 的百分比 · 99
2 · 时间基线 baseline · elapsed / median(本会话已完步耗时) × 100 · 90
3 · 计划步序 baseline:steps · 尚无已完步耗时时,用 当前步序 / 总步数 × 100 冷启动(计划 ≥ 2 步且不是首步) · 90
— · none · 三条来源均不可用 · 不显示数字
约束:未结束的步不会显示 100%;已结束(完成或失败)的步固定为 100%;回合已结束、仍在等待下一回合的步不显示数字,
而不是填 100%。
待办表的全局完成率不参与步级百分比。 它是「已完成行 / 全部行」的会话级事实,不是这一步的测量值:把它填进某一步,
等于把整体数字交给局部,同一份半完成的清单里每一步都读到同一个值,且只在该表被重写时才变。在 167 份会话日志上实测,
38 个运行中的步里有 23 个显示的正是整场会话的完成率(按现行 fold 重算为 39 个 / 25 个——两个抽样时点的会话数不同,方向与量级一致)。模型自报与时间基线都是关于该步的量,因此只保留这两条来源;
高水位记录 step.maxPercent 仍写入,只作为两条推算来源的下限,不抬高自报值,也不参与已结束步的计算。
foldTodos 将已结束行改回 in_progress、或降级为 pending 时,清空 percent 与 maxPercent 并重置
startedAt:已结束记录的数字属于那一次结束,不属于重新打开它的行。
awaitingNextTurn:待办表仍把该行写成 in_progress,但它所在的回合已由 turn/end 结束、且没有 turn/start
开启下一回合(PAE 会在下一回合继续这一步)。此时面板标「等待下一回合」,进度条不出数字。它是步级字段,
与会话级的 view.running(此刻有没有开着回合)是两个问题,视图同时发布两者。
进度条下方另有一行元信息:N 分钟前估计,即 view.asOf − step.lastEstimateAt。
该值描述数字的陈旧程度,用于区分「正在更新」与「长时间未更新」。字段缺失时整行不渲染。
2.3 单页布局
一条纵向主流程,每步一个单元;单元左右两侧各挂若干圆形接口,每个接口对应一类工具或文件类型。
维度 · 规则
分侧依据 · 读取类(read / grep / glob / 路径查询)在左;写入与执行类(edit / write / pwsh)在右
每侧容量 · 2 个接口
溢出处理 · 超出部分不渲染,单元上标注「另有 N 类未画在两侧」
连接 · 接口与单元之间为直线
详情 · 点接口 → 右页展示该类调用清单;点单元 → 右页展示该步记录
选择单页而非多列的理由是上下文连续性:两列布局在切换分步时会同时改变两个滚动区的位置,
单页布局下主流程位置不随右页开合而变动。
2.4 视觉状态编码
运行中的单元及其唯一一条指向它的连接线使用流动渐变;其余单元即使状态为 running(例如已失焦)
也只做静态填充。动画时长与阈值定义在 src/client/theme.js:
…(truncated)
以上内容来自 npm 上的 README,我们只做格式化,不改内容。
版本
npm 上共发布:10,最后更新 2026-11-06