dsh-flow
跨会话流程编排:让四个角色(统筹 / 预研 / 开发 / 测试)在 DSH 里配合走完一条活——派单、实现、独立验收、需求比对、终审把关、归档,然后回到起点开下一条。
和剧场插件(dsh-theater)无关:不 import、不依赖、不共用数据。投递是自己实现的(agent.steer + 等 turn/end),插件里也没有任何剧本逻辑。
解决什么
多会话协作时,两件事最容易坏:
- 「自己验自己」:开发写完自测通过,就等于「做完了」。没有第二双眼睛,边界没人报。
- 流程全靠人记:派了谁、到哪一步、卡在哪,散在各个会话的脑子里。换一天就接不上。
dsh-flow 把「一条活怎么走完」固成工具:任务卡是文档(正本),状态写在 YAML 头里,每一步都有工具管。人和会话都不用记流程,照卡走就行。
角色与流程
┌─────────── 回到起点(下一轮)────────────┐
▼ │
预研 ─提需求/选型─► 统筹 ─建卡、拆解、派单─► 开发 ─实现、自测、push、回执─┤
│ │
└──派验收──► 测试 ─独立验收、出报告、报边界──► 统筹 ─需求比对、终审、归档
| 角色 | 干什么 | 不干什么 |
|---|---|---|
| 统筹 | 拆解、建卡、派单、派验收、需求比对、终审把关、汇总、归档 | 不自己动手实现;不替别人拍板 |
| 预研 | 提需求、写预研说明、选型 | 不直接改实现 |
| 开发 | 认领、实现、自测、push、回执 | 不自验——自测通过不等于验收通过 |
| 测试 | 独立验收、出报告、报边界 | 不替开发改代码;不接「边改边验」 |
闭环一句话:预研 → 统筹(建卡/拆解)→ 开发(做)/ 测试(验)→ 统筹(需求比对、终审把关,不过就再走一轮开发)→ 统筹(汇总/归档)→ 回到起点。
五个坎:派单 → 认领 → 回执 → 独立验收 → 终审。少一个,这条活就不算闭环。
流程原理(平台无关)
这套流程不绑 DSH。拆开看只有五个抽象——角色 / 任务卡 / 状态机 / 投递 / 归档,换到飞书、GitHub Issues、Notion 或者自建系统,照着都能复刻。
三条不变量换平台也不能改:
- 卡是正本:状态、派单记录、验收结论都在卡上;聊天和看板只是视图。
- 开发不自验:做的人不判自己。这条松了,整个流程就没有意义了。
- 关闭要有通过的验收:没有验收的
done是耍赖。
完整原理——八拍时序(每拍的输入/产出/判据/失败怎么办)、状态转移表、换平台映射表、最小实现清单、复刻成功的判据——见 docs/流程原理.md。
工具
| 工具 | 干什么 |
|---|---|
flow_roles |
登记 / 查看四个角色对应的会话 id;action=set 用逗号分隔可一次登记多个会话(多实例,例如三个测试) |
flow_start |
建任务卡:写 YAML 头(from/to/type/ticket/status/created),status=pending,投给统筹 |
flow_dispatch |
统筹派单:卡状态 pending → processing,记一笔派单,投给开发 / 测试;写 toSession(完整会话 id)优先于 to 角色名;顺手把下一站写进卡头 downstream(不填按 to 推导:开发→测试、测试→统筹) |
flow_submit |
开发交卡:processing → waiting_verify,追加「## 交付记录」,自动挑一个空闲测试(名下没有在途卡)投过去;都忙就停在 waiting_verify 报「排队」 |
flow_ask |
开发遇需求对不上、不确定时不许猜:卡 processing → blocked,追加「## 疑问」,投统筹决策 |
flow_answer |
统筹答复疑问:blocked → processing,追加「## 决策」,投回开发;决策留卡里供追溯 |
flow_verify |
测试回执:把「通过 / 不通过 + 报告路径」写进卡;不通过投回开发(同一张卡最多自动退 2 次,第 3 次转 waiting_human 人工裁定;此状态下再调会直接报错,要先由统筹 flow_dispatch 放行)。先投递再记账,投递结果分三档见下。dryRun:true 只算不改:不改卡、不投递、不写登记单、不占分配时间,返回 wouldBeStatus / wouldDeliverTo / rejectsLeft,给测试验流程用 |
flow_close |
归档:状态改 done、移到【归档区】<类型>\<YYYY-MM>\、移动前后核对 SHA-256 |
flow_run |
一键封顶:读一张卡按状态自动推进,失败停下报原因 |
flow_status |
读全局会话状态表,汇总「活跃 N / 待机 M」+ 明细;only 可只看活跃或待机 |
状态机(0.2.7)
pending → processing → blocked → waiting_verify → waiting_final → done
▲ │ │
└─ answer ───┘ │
└────── 不通过返工 ────────┘
waiting_verify ──连退满 3 次──> waiting_human ──统筹 flow_dispatch 放行──> processing
| 状态 | 谁在手上 | 怎么离开 |
|---|---|---|
pending |
等派单(统筹) | flow_dispatch → processing |
processing |
开发在做 | flow_submit → waiting_verify;flow_ask → blocked |
blocked |
等统筹答复疑问 | flow_answer → processing |
waiting_verify |
测试在验(或排队中) | flow_verify 通过 → waiting_final;不通过 → processing(回开发;连退满 3 次 → waiting_human) |
waiting_human |
连退超限,等人工裁定 | 统筹 flow_dispatch 派单 → 转回 processing(等于放行);此状态下不能再 flow_verify |
waiting_final |
等统筹终审 | flow_close → done(归档) |
done |
归档区 | 终态 |
卡头的 to_session 记的是当前持有人,挑空闲测试就靠它判断「谁手里有在途卡」。
数据在哪
~/.dsh/plugins/dsh-flow/config.json— 工作区路径、任务卡目录、归档目录、四个角色的会话 id。- 任务卡 — 在
config.workspace+config.tasksDir下(workspace默认为空,首次使用前必须配成本机工作区路径;tasksDir默认【文档区】\计划与任务)。 - 归档 — 在
config.workspace+config.archiveDir下,按<类型>\<YYYY-MM>分。 - 派活登记单 —
【文档区】\进度\派活登记.md(即tasksDir的同级进度/下),一行一条:时间 | 谁 | 任务 | 状态 | 摘要。flow_dispatch/flow_submit(含排队)/flow_verify/flow_close各自追加;只追加不重写,写失败不影响主流程——但不再静默:会console.error并往插件目录_dispatch-error.log落一条完整堆栈,排查「登记单没写」先看它。登记单的「谁」列默认取卡头to_session;手工建的卡没有这一栏,交卡时传flow_submit({ "session": "session-…" })补上。 - 投递结果分三档(
flow_verify/flow_submit都按这套):delivered(reply正常返回)、timeout(超时,不确定)、failed(reply里是not-found/不存在这类,明确没送到)。超时按「当它送到了」处理——照常touchAssigned、卡头写 target、登记单记「…(投递超时,未确认)」;因为漏记分配时间的伤害比多记一次大。只有failed才回滚卡头、不占分配时间、记「投递失败(未送达)」。返回体里有outcome字段,三档一眼可辨。 - 为什么超时不等于没送到:
theater_say超时的消息通常已经送达,只是对方没在timeoutMs内回。2026-10-25 实测:往开发投「请重做」,对端真收到了,但当时按「未送达」记账,对端和白纸黑字的登记单对不上。 - 假派活:明确失败(会话不存在)时不会有「开发收到请重做、去看卡已没了」这种事。这种卡头
to_session会回滚成原持有人,不留指向死会话的指针;lastAssigned也不动,轮转挑人不受影响。要复现:把config.json里某个角色临时指到不存在的会话,跑flow_verify/flow_submit,看登记单记的是不是「投递失败(未送达)」,跑完记得还原。 ~/.dsh/会话状态.md— 全局会话状态表,所有会话共用一张,不由本插件维护也不归本插件。flow_status只读它,汇总「活跃 / 待机」;这张表是判断「现在能不能重启宿主」的依据。
安装
plugin 目录已有(
~/.dsh/plugins/dsh-flow/)。在 profile 的
package.json里挂上:
// ~/.dsh/profiles/<profile>/package.json
"dependencies": { "dsh-flow": "link:<home>/.dsh/plugins/dsh-flow" },
"dsh": { "profile": { "bundles": ["...", "dsh-flow"] } }
- 建 junction(PowerShell):
New-Item -ItemType Junction -Path "$env:USERPROFILE\.dsh\profiles\<profile>\node_modules\dsh-flow" `
-Target "$env:USERPROFILE\.dsh\plugins\dsh-flow"
重启 DSH(Host 只在启动时加载插件一次)。改了
lib/*.js必须重启才生效,改config.json不用。登记四个角色:
flow_roles({ "action": "set", "role": "统筹", "sessionId": "session-abcdefgh-…" })
flow_roles({ "action": "set", "role": "开发", "sessionId": "session-abcdefgh-…" })
// 预研、测试同理
flow_roles({ "action": "get" }) // 看现状
sessionId必须带session-前缀。只传 uuid 会报session/not-found。
一键 vs 分步
| 场景 | 用哪个 |
|---|---|
| 想盯着每一步、每一步都能改主意 | 分步:flow_start → flow_dispatch → flow_verify → flow_close |
| 卡已经写好,只想让它往前走 | 一键:flow_run |
| 卡在哪一步说不清 | flow_run 先用 maxSteps: 1 探一步,看返回的 steps |
flow_run 的推进逻辑:
pending→ 派给开发processing、卡里没有「## 验收」段 → 请测试验;测试把结论写进卡后,下一圈才继续processing、卡里有验收段 → 关闭归档done→ 结束
中途失败就停,返回 steps 和 stopped 说明卡在哪。它不会替你改实现、也不会替你编验收结论。
完整案例:从预研提需求到归档
场景:预研发现缺一个「视频录制」能力,提给统筹;统筹拆成两单(开发 + 测试);开发做出 0.1.0;测试独立验收;统筹终审、比对需求,收口归档。
第 1 步 · 预研提需求
预研会话里:
flow_start({
"title": "视频录制插件",
"summary": "录屏录制",
"from": "预研",
"to": "统筹",
"requirement": "需要一个能把屏幕/会话录成 mp4 的插件:开始、停止、存到交付区。依赖尽量少。",
"criteria": "1) flow 工具能起录、停录;2) 产物是能播的 mp4;3) 空 PATH 能跑或写清依赖。"
})
结果:
- 卡落地:
【文档区】\计划与任务\任务卡_视频录制插件_录屏录制_NA_20261025.md - YAML 头:
from: 预研、to: 统筹、type: 任务卡、ticket: NA、status: pending - 统筹被投递叫醒(投递正文包在代码块里,语音不会念)
第 2 步 · 统筹建卡、拆解、派单
统筹看卡后拆成两条:
- 实现单 → 开发
- 验收单 → 测试
// 派给开发
flow_dispatch({ "card": "任务卡_视频录制插件_录屏录制_NA_20261025.md", "to": "开发", "toSession": "session-1d314539-…",
"note": "按卡实现到 0.1.0,自测后 push,回执写在卡末" })
- 卡状态:
pending → processing - 卡里多一行:
> 派单 → 开发(2026-10-25 14:20):按卡实现到 0.1.0,… - 开发被叫醒
第 3 步 · 开发实现、回执
开发照卡做,做完在卡末尾写 ## 回执(改了什么 / 验了什么 / 版本 / push SHA),然后:
flow_dispatch({ "card": "任务卡_视频录制插件_录屏录制_NA_20261025.md", "to": "测试",
"note": "实现完了,请独立验收" })
第 4 步 · 测试独立验收
测试只看任务卡的验收标准,不看开发写的回执结论:
flow_verify({ "card": "任务卡_视频录制插件_录屏录制_NA_20261025.md",
"pass": true,
"report": "【文档区】\\测试文档\\测试报告-视频录制插件-20261025.md",
"note": "三条判据全过;边界:没测长录制(>30 分钟)" })
- 卡里追加
## 验收(2026-10-25 15:02),结论「通过」,报告路径,备注 - 不通过时
pass: false,卡会投回开发重做
第 5 步 · 统筹汇总、归档
统筹先做需求比对与终审:功能跟需求对不对得上、有没有 BUG、有没有小改大收益的优化点——不过就出方案再走一轮开发;通过了才汇总给主人,然后:
flow_close({ "card": "任务卡_视频录制插件_录屏录制_NA_20261025.md" })
- 状态:
processing → done - 文件移到:
【归档区】\任务卡\2026-10\任务卡_视频录制插件_录屏录制_NA_20261025.md - 移动前后 SHA-256 一致才返回成功
一键版
上面五步如果不想一步步点:
flow_run({ "card": "任务卡_视频录制插件_录屏录制_NA_20261025.md" })
它会:派开发 → 请测试验(等测试把结论写进卡)→ 关闭归档,每步都记在返回的 steps 里;哪一步没成,stopped 会说明。
验证边界
单测
- 分步闭环、拒绝路径、
flow_run分步推进:历史自测脚本 21 项全过(模板工作区,跑完还原 config)。 - 段落拼接:
_selftest_section.mjs10 项全过(0.2.5 起)。 - 宿主内实际加载:2026-10-25 重启后工具出现在真实工具列表。前提是每个工具的 schema 必须带
additionalProperties: false和output——缺了宿主会把整行静默判成「启动失败」,工具不出现但也不报错。
真实会话闭环
- 0.2.0(2026-10-25,开发 3 / 测试 3 六条独立会话跑四张卡):
flow_roles get多实例显示正常;flow_submit四次交卡都由工具挑人,两个不同测试先后接到卡;全链路四遍走通无人工补位;旧单值配置回归正常。 - 0.2.5:
waiting_human用真卡验——连退第 1、2 次回processing,第 3 次转waiting_human,第 4 次flow_verify直接被拒;flow_dispatch放行后回processing,再验通过进waiting_final。验收段四段都两换行、段内不粘连。另有独立测试会话按四条判据复核通过。 - 0.2.6:
dryRun硬条件——同一张卡跑前跑后 SHA-256 不变、登记单不增行、lastAssigned一字不差;wouldBeStatus/wouldDeliverTo/rejectsLeft与真跑一致;waiting_human下dryRun同样被拒(闸门与真跑同源);flow_submit投递失败回滚卡头、不占分配时间;resolveCard找不到卡时带dryRun提示。 - 0.2.7:三档复查——
timeout档outcome=timeout、卡头写 target 不回滚、lastAssigned确实变动、登记单记「投递超时(未确认)」;failed档(死号)卡头回滚、死号不进lastAssigned、记「投递失败(未送达)」。判据 6 的连退 1/2/3 递增不跳号、通过进waiting_final一并复查。 - 现场共造 9 张一次性假卡(dryRun / submit / waiting_human / 连退回归 / 通过 / 判据 CD 等),验完全部清掉,config 临时改动均已还原。
没验的(先当不能)
flow_submit的timeout档:跟flow_verify同一套判定,但没单独实证。failed档的字面量匹配:靠/not-found|不存在/i认,宿主换措辞会漏判成timeout,届时同步改正则。- 排队路径:交卡时刻一直错开,
pickFree返回 null 那条没在真实会话跑过。 - 疑问回传:
flow_ask/flow_answer一次都没走到。 - 挑人轮转:只验了「不是固定第一个」,没验连续多轮是否真按最久未派轮转。
- 跨工作区(
config.workspace指到别处)、同名卡片、卡被归档后再被flow_run指到。
样本:单机、Windows 宿主、9 张假卡 + 6 条真实会话;不是端到端演练。0.2.6 的 timeout 档是拿生产 config 直接投到真开发身上验的——方式不合规,结论可采信。
No comments yet. Be the first to write one.