Repository navigation
Releases: MichaelShii/dsh-plugin-teamflow
Release list
v0.3.3
TeamFlow v0.3.3
发布日期:2026-11-01。变更清单按 release-npm-package 铁律 1 由
git log v0.3.2..HEAD生成
(28 笔提交 / 27 个文件 / +1930 −526;该区间另含依赖/CI 合并与本次发版提交),不凭会话记忆。
⚠️ 本版含一处行为变更(子代理续跑等待判据)与一处engines变更(宿主下限抬到0.2.1-rc.5)。
逐笔归属与真机取证见docs/devlog.md的 v0.3.3 批次。本文件面向使用者;内部流水不在正文里。
中文
⬆️ 升级要点
engines有变:宿主dsh下限由0.2.1-alpha.2抬到0.2.1-rc.5(上界仍<0.3.0);Node ≥ 22.18。
升完重启dsh --profile web(桌面版重启应用)——修复都在宿主侧,不重启不生效。- 一处行为变更:子代理续跑的等待由「固定 25 分钟墙钟」改为「有进展就续等」。此前一个正常跑长任务的
子代理(写测试 + 反复跑构建)会被旧逻辑在 25 分钟处误判失败,而它实际仍在推进、最终成功。 - 无
teamflow_*工具 schema / journal / 任务夹契约改动,不需要迁移。
① 长任务不再被墙钟误杀(修复)
现象:人工 resume 复活的子代理这一轮正常跑了很久,插件在固定 25 分钟到顶时判「没拿到正文」失败;卡片
还显示首次尝试的时长(几分钟),看起来像「早就失败的弃子」,实际它仍在正常干活。
根因:续跑等待是固定墙钟,到顶即放弃,不看是否还在推进;且判失败时不更新该阶段记录,结束时间停在首次运行。
修复:放弃判据改为「无新事件、无产物变更持续 10 分钟才放手」(天花板 2 小时);判失败时落真实结束时刻。
② 续跑与重试复用同一个子代理(省 token)
现象:每次重试都新起子代理,对上一轮一无所知,从零重读 PRD/技术方案/源码,单阶段可烧掉千万级 token。
修复:重试与中断后的 resume 都在原 child 里继续;指令投递成功后绝不回落新起——两个子代理会并发改同一批
文件,宁可本轮按失败收场转人工。
③ 切模型即时到达子代理
现象:界面切成别的模型后,子代理仍打旧(已不可用/被限流)的模型。
根因:只读父会话「上一次实际请求」的配置,而它只在父代理真正发请求后才刷新 ⇒ 切模型后是陈旧快照。
修复:优先读「待生效选择」;旧模型确属外部不可用且用户已切模型时,改道新起子代理吃新路由。
④ 开发阶段的边界守卫(修复)
越界发现落成「收口任务」;依赖成环改为串行化(读者不再与写者同波);技术方案钉死的数值常量被改动直接停
(不等验收);本轮有失败就不再起收口任务(失败态下 spawn 起不来,白烧两次尝试)。
⑤ QA 回环在 resume 后不再失效(修复)
QA 打回轮次改为跨 resume 累计(续跑后曾有界闭环整个失效);超限日志不再谎称「跳过产品验收」;长命令挂住时
给出可见提醒 + QA 命令纪律(一条命令只做一件事、带超时)。
⑥ 供应链加固 + 宿主下限锚定
CI 的 action 钉 commit SHA、引入 dependabot 分组、进程调用改 execFileSync(去掉 shell 字符串);
engines.dsh 下限抬到 0.2.1-rc.5。
English
⬆️ Upgrade notes
engineschanged: the hostdshfloor is raised from0.2.1-alpha.2to0.2.1-rc.5(upper bound
still<0.3.0); Node ≥ 22.18. Restartdsh --profile web(or the desktop app) after upgrading — all
fixes live host-side and do not take effect until restart.- One behavior change: the sub-agent continuation wait changes from a fixed 25-minute wall clock to
"keep waiting as long as there is progress". A sub-agent legitimately working on a long task (writing
tests, re-running builds) used to be marked failed at the 25-minute mark while it was still making
progress and would eventually succeed. - No
teamflow_*tool schema / journal / task-folder contract changes — no migration needed.
① Long-running sub-agents no longer killed by the wall clock (fix)
Symptom: A resumed sub-agent that legitimately ran long was declared failed at the fixed 25-minute mark
("no reply text"), and the card showed the first attempt's duration (a few minutes), looking like an
abandoned task — while it was still working and finally went green.
Root cause: The continuation wait was a fixed wall clock that gave up regardless of progress; and on
failure the stage record was not updated, leaving its end time at the first run.
Fix: Give up only after 10 minutes with no new event and no artifact change (cap raised to 2 hours);
on failure, record the real end time.
② Retry and resume reuse the same sub-agent (token savings)
Symptom: Every retry spawned a fresh sub-agent that knew nothing about the previous round and re-read
PRD / design / source from scratch — a single stage could burn millions of tokens.
Fix: Retries and post-interrupt resumes continue in the original child; once an instruction is
delivered we never fall back to a new child — two agents editing the same files is worse than failing,
so we stop and hand off to a human instead.
③ Model switch reaches sub-agents immediately
Symptom: After switching models in the UI, sub-agents still hit the old (unavailable / rate-limited) one.
Root cause: The route was read from the parent's "last actual request" config, which only refreshes after
the parent itself issues a request — a stale snapshot right after a switch.
Fix: Prefer the parent's pending selection; reroute to a fresh child (with the new route) only when
the old model is externally unavailable and the user has actually switched.
④ Boundary guards in the dev stage (fix)
Out-of-scope discoveries become a serial "cleanup task"; dependency cycles are serialized instead of dropped
(no reader running alongside a writer); a numeric constant pinned by the tech design, if changed, stops the
run immediately (no waiting for acceptance); a failed round no longer spawns the cleanup task (spawning fails
in a failed state, burning two attempts).
⑤ QA rework loop survives resume (fix)
QA rework rounds now accumulate across resume (the bounded loop previously collapsed after a resume);
the over-limit log no longer falsely claims "product acceptance skipped"; a hung long command emits a visible
reminder plus QA command discipline (one command per call, with timeouts).
⑥ Supply-chain hardening + host floor anchored
CI actions pinned to commit SHAs, dependabot groups added, process invocations switched to execFileSync
(no shell strings); engines.dsh floor raised to 0.2.1-rc.5.
v0.3.2
TeamFlow v0.3.2
发布日期:2026-10-26。变更清单按 release-npm-package 铁律 1 由
git log v0.3.1..HEAD生成
(16 笔开发提交 / 26 个文件 / +1253 −116;该区间另含 1 笔 v0.3.1 合并与本次发版提交),不凭会话记忆。
⚠️ 本版含一处行为变更(PRD 改写的等待判据)。逐笔归属与真机取证见docs/devlog.md的 v0.3.2 批次。
本文件面向使用者;内部流水不在正文里。
中文
⬆️ 升级要点
engines未变(宿主仍须 ≥0.2.1-alpha.2,Node ≥ 22.18)。升完重启dsh --profile web
(桌面版重启应用)—— 四条修复都在宿主侧,不重启不生效。- 一处行为变更:PRD 改写的等待判据由「固定 300s 墙钟」改为「无进展即放手」(idle 240s / 天花板 600s)。
用户可见差异:带较大改动的 PRD 改写不再莫名回落成「重跑 PRD」。 - 无
teamflow_*工具 schema / journal / 任务夹契约改动,不需要迁移。
① 界面语言不再漂移成英文(修复)
现象:新开一个工作区后,整轮产物被生成成英文(团队名、子代理 label、PRD 正文),而界面是中文 ⇒ 中英混排。
根因:宿主侧拿语言的唯一通道是客户端上报。上报器把「值还没就绪」当成「无事可做」——既不推送、也不重试;
而语言服务只在语言变化时回调、不含初始值 ⇒ 挂载那一刻取到空值,之后永远没人再推,宿主停在 en 兜底。
修复:把「值没就绪」算作待重试,且重试时重新取值(不是重发旧值);宿主侧同时修掉「空值把已有好值抹掉」。
真机验证:locale 为 zh、无兜底告警、团队名/阶段名/PRD 正文均为中文。
② 分诊不再白跑一次(修复)
现象:用户在确认单上点选后重新开工,却看到一个全新的分诊子代理又跑了一遍。
根因:分诊缓存键含「需求 + 补充说明」;点选那一步模型会重写补充说明(即使语义没变)⇒ 键变 ⇒ 缓存未命中。
修复:「待决策」期间按需求原文放宽命中并迁移待决策状态。真改了需求仍会重跑。
真机验证:四次 resume、每次补充说明都不同,分诊仍只跑一次。
③ PRD 改写不再「完工前放弃」(行为变更 + 修复)
现象:在确认单上改了 PRD,日志写「第二轮跑了但没拿到可用文本」然后重跑整份 PRD——白等约 5 分钟,
而且两份 PRD 并行跑了一段。
根因:等待用的是固定 300s 墙钟。一次真实改写跑了 377s(含多次编辑 + 全文重写 + 状态文件更新),
插件在它完工前 77 秒放弃;子代理会话证据显示那一轮其实完整跑完了。
修复:放弃判据改为「无进展」——既没有新事件、也没有产物变更,持续一段时间才放手;天花板抬到 600s。
同时支持「回合结束但回复没有正文」时按文件即产物判完成(真实改写常常只写文件、回复只给一段小结)。
④ 蓝图不再整份丢弃(修复)
现象:开发阶段退化成「整体开发」一张卡,并行度消失、调用数暴涨。
根因:技术阶段写的架构蓝图是 JSON,而模型把正则原样写进其中(如 /\.md$/i)。JSON 不允许 \. 这类转义
⇒ 整份蓝图被拒绝 ⇒ 拆卡信息全部丢失。
修复:对字符串内的非法 JSON 转义做确定性修复(加倍成字面反斜杠——JSON 里它没有第二种解释),
并覆盖「末尾截断 + 非法转义」并发的形态;修复过会打一条告警留痕,不静默。
真机验证:同一份失败蓝图恢复出 8 个任务 / 16 个模块;后续 run 正常拆成 5 波、3 路并发。
⑤ 需求澄清与确认单(体验)
- 确认单清单化:最多一张汇总卡,不再逐题弹卡片;事实项与口味项分开标注。
- PRD 增加 anti-goals 段:显式回答「技术可行但产品失败」的形态(可观察、具体)。
/teamflow-brainstorm:需求头脑风暴入口,只允许用户调用,模型不能自己触发。- 缺陷修复先看「最近改了什么」:先看提交与对基线的 diff,再决定读哪个模块。
⑥ 稳健性(修复)
- 参数类失败判据补第三族:曾把「参数写错」误判成「环境不可用」⇒ 直接停掉自动重试。
- 批准后的续跑不重复计数:复用已完成的 PRD 时,用量不再叠加。
- 部署自检:核对 profile 的版本号与产物指纹;依赖声明用
file:,防npm install打回旧版。
验证
- 全量测试与静态检查全绿;每条修复都配了单维度变异(改坏一处只让对应断言变红),共 14 轮。
- 逐条真机取证(含未压实的三条)记录在
docs/devlog.md的 v0.3.2 批次。
English
Upgrade notes
enginesunchanged (host ≥0.2.1-alpha.2, Node ≥ 22.18). Restartdsh --profile webafterwards
(restart the desktop app) — all four fixes live in the host process.- One behaviour change: the PRD-revision wait criterion moved from a fixed 300s wall clock to
"no progress" (idle 240s / ceiling 600s). Visible difference: a large revision no longer falls back
to a full PRD re-run for no reason. - No
teamflow_*tool-schema / journal / task-folder contract changes — no migration needed.
① The UI language no longer drifts to English (fix)
Symptom: after opening a new workspace, a whole run was produced in English (team name, subagent label,
PRD body) while the UI stayed Chinese — a mixed-language screen.
Root cause: the host learns the language only from the client's report. The reporter treated "the value is
not ready yet" as "nothing to do" — it neither pushed nor retried, and the locale service only notifies on
change (no initial value) ⇒ one empty read at mount means nobody ever pushes again, and the host stays on
the en fallback.
Fix: "not ready" now counts as retry-worthy, and every retry re-reads the value (instead of
re-sending a stale one); the host side no longer lets an empty value wipe a good one.
Verified on a real run: locale is zh, no fallback warning, team/stage names and PRD body are Chinese.
② Triage no longer runs twice (fix)
Symptom: after picking an option on the confirm sheet, a brand-new triage subagent ran again.
Root cause: the triage cache key includes "requirement + supplement", and the model rewrites the
supplement during that round (even when the meaning is unchanged) ⇒ the key changes ⇒ cache miss.
Fix: while a decision is pending, match on the requirement text alone and carry the pending state over.
A genuinely changed requirement still re-runs triage. Verified: four resumes with different supplements, triage
ran once.
③ PRD revisions no longer give up before finishing (behaviour change + fix)
Symptom: after a revision, the log said "the second turn ran but produced no usable text" and the whole PRD
was re-generated — about five minutes wasted, with two PRDs running in parallel for a while.
Root cause: the wait used a fixed 300s wall clock. One real revision took 377s (several edits + a full
rewrite + a state-file update); the plugin gave up 77 seconds before it finished — while the session evidence
shows that turn did complete.
Fix: the give-up criterion is now "no progress" — no new events and no artifact change for a while;
the ceiling is 600s. Also, when a turn ends without reply text, the artifact file counts as the deliverable
(real revisions often write the file and reply with only a short summary).
④ Blueprints are no longer discarded wholesale (fix)
Symptom: the dev stage collapsed into a single "whole-requirement" card — parallelism gone, call count up.
Root cause: the architecture blueprint is JSON, and the model pasted regexes into it verbatim (e.g.
/\.md$/i). JSON does not allow escapes like \. ⇒ the entire blueprint is rejected ⇒ all task-split
information is lost.
Fix: deterministic repair of invalid JSON escapes inside strings (doubling them into literal
backslashes — nothing else they could mean), covering the combined "truncated + invalid escape" shape too.
A repair emits a warning so it is never silent.
Verified: the same failing blueprint now yields 8 tasks / 16 modules; later runs split into 5 waves, 3-wide.
⑤ Clarification and confirm sheet (UX)
- Confirm sheet as a list: at most one summary card instead of one card per question; fact items and taste
items are labelled separately. - Anti-goals in the PRD: the PRD now explicitly answers "technically fine but product-wise a failure".
/teamflow-brainstorm: a brainstorm entry for requirements, user-invocable only — the model cannot
trigger it on its own.- Look at "what changed recently" first when fixing a defect: check commits and the diff against the
baseline before deciding which module to read.
⑥ Robustness (fix)
- Argument-failure family #3: a malformed argument used to be misclassified as "environment unavailable"
⇒ automatic retries stopped. - No double counting after approval: reusing an already-finished PRD no longer adds its usage again.
- Deploy self-check: verifies the profile's version and artifact fingerprint; the dependency uses
file:
so annpm installcannot silently pull an old version.
Verification
- Full test suite and static checks are green; each fix ships with a single-dimension mutation check
(break one thing, only the matching assertion goes red) — 14 rounds in total. - Per-fix real-machine evidence (including the three items that remain only partially verified) is recorded in
the v0.3.2 batch ofdocs/devlog.md.
v0.3.1
v0.3.1 发布说明(取证)
版本:
0.3.1(patch)
基线:v0.3.0→HEAD,12 笔提交 / 18 个文件 / +1540 −24 行(git log v0.3.0..HEAD生成)
主题:确认单上的「改 PRD」做成闭环 —— 改写绑定版本、在原 child 上下文里续跑、用量准确、批准语义收紧。
本文件是取证与论证;结论层见CHANGELOG.md。
1. 为什么做这一版
v0.3.0 把「确认单必须由人批准」这道闸门立起来了,但用户提出「要改」时,链路还是断的:
- 改完 PRD,上一版的批准没有失效 ⇒ 改过的 PRD 从未被批准却直接开工(issue #11,用户截图实证);
- 修成「重出确认单」后加了次数上限(2 次)⇒ 第 3 次直接拒绝,用户只能重开 run、白丢已跑的 PRD;
- 改写实现是重跑整个 PRD 子代理(新 spawn、从零读需求)⇒ 又贵又漂移:用户只要求改一处,
没提的部分也可能被顺手改写; - 放开次数后又发现:
confirmApprovedRev被模型自己写死 ⇒ PRD 永久改不动。
本版把这条链路补完整,并用真机逐环验通。
2. 变更清单(按 git 对比,逐笔归属)
| 提交 | 类型 | 内容 |
|---|---|---|
90012eb |
fix | 确认单绑 PRD 版本:改写后批准失效、必须重出单(当时含次数上限) |
c831b07 |
fix | 去掉次数上限,改为「版本号 + 上一版快照」 |
ba628a5 |
feat | 改写走 continuable 续跑(prd-revise.ts),失败回落重跑 |
ad617c0 |
fix | 批准语义:无 supplement 的 resume 必须有显式批准;记账加严 |
5ca24d9 |
fix | prdChildId 必须在摘阶段记录之前取;回落出口全部可见 |
ccd7bb0 |
fix | 续跑等待 180s→300s;回落原因拆成三条可区分诊断 |
f8988ed |
fix | 快照路径:runDocs 是相对路径,必须拼工作区 |
e93859b |
fix | 快照从任务夹移到 logs/teamflow/<runId>/(不进交付物) |
7daa91d |
fix | 阶段用量必须累计(改写多轮时前几轮 token 不能消失) |
2c5a933 |
fix | 修用量精确翻倍(prdUsageAcc 与 stage.usage 同引用 ⇒ addUsage(a,a)) |
22aa418 |
chore | 用量逐轮留痕 + 重复护栏 |
edde46e |
docs | ADR-0011 追加「快照=机器可查的落实证据」 |
3. 真机取证
工具链:dsh --profile web,插件经 deploy.mjs --sync 同步(50 文件),每条 run 均重启宿主后执行。
3.1 改写闭环(hy3)
| run | 轮数 | 结果 |
|---|---|---|
tf-mvchuuvf-j7kmzk |
5 版 | 第 4/5 版走续跑;第 5 版正确删除 --depth、保留 children 与 stdout/stderr |
tf-mvcl5m8t-0qhl8y |
5 版 | 同上;第 3 版曾因 180s 超时回落(后提到 300s) |
tf-mvdeqi60-p48r0f |
4 版 | 每版都命中续跑、零回落 |
tf-mvdhxn6h-87vnog |
3 版 | 11 条 AC 逐条对上两次要求;旧结构字段只出现在说明性语境 |
上下文累积的直接证据:PRD.v1 → v2 → PRD.md 的特征计数
(--depth 0→16、children 0→11),证明第 1 轮加参数、第 2 轮改 JSON 结构的落地情况。
3.2 续跑 vs 重跑的成本(同一子代理、按 turn 统计)
| 路由 | 轮次 | 每轮新算 input | cacheRead | 耗时 |
|---|---|---|---|---|
workbuddy/hy3 |
重跑 / 续跑 ×2 | 217,782 / 203,350 / 231,713 | 27,904 / 7,744 / 2,368 | 122s / 116s / 163s |
deepseek-account/deepseek-flash |
首版 / 续跑 ×3 | 16,168 / 14,072 / 17,877 / 18,687 | 278k → 1.47M(累计) | — / 84s / 78s |
结论:续跑的技术收益(同上下文、不漂移)与路由无关;成本收益取决于路由是否做 prompt 缓存——
真机 workbuddy/hy3 三轮 request/header 前缀 40,790 字节逐字节相同(仅末字段 initial→resume),
但 cacheWrite 恒为 0 ⇒ 服务端没做自动缓存;deepseek-flash 有 ⇒ 每轮新算降到 1.5 万量级。
判据沉淀:看
cacheWrite是否恒 0,别只看 cacheRead(cacheRead 低可能只是前缀变了)。
3.3 批准闸门(双向)
| 方向 | run | 证据 |
|---|---|---|
| 拦得住代批 | tf-mvdhxn6h-87vnog |
暂停后零人类消息 ⇒ 续跑被拒:确认单必须由用户本人批准 |
| 放得行真批准 | tf-mvdixrhs-asht1p |
用户「批准,开始吧」⇒ 放行、approvedRev == prdRev、进技术方案 |
3.4 用量(修复前后)
| journal | 子会话实际 | 结论 | |
|---|---|---|---|
修复前(tf-mvdeqi60) |
in 2,419,276 | in 1,209,638 | 精确 2 倍 |
修复后(tf-mvdhxn6h) |
in 608,144 | in 608,144 | 逐项一致 |
逐轮日志(修复后):144,949 → +260,198 = 405,147 → +202,997 = 608,144,算术正确。
4. 门禁与变异
| 新增/更新门禁 | 覆盖 |
|---|---|
test/prd-revise.test.js(38 条) |
取文本字段名、offset 切分、turn/end 结束判据、六种失败全部回落、读不到基线不投递 |
test/human-approval.test.js §5/§6 |
批准词真值表、否定优先、修改词、AUQ 答案解析、以最后一条为准、三态 |
test/orchestration.test.js |
续跑不 spawn、对原 child 投递、快照落 logs、用量累计、批准拒绝/放行 |
test/metering.test.js |
addUsage 真值表(含真机那组数) |
test/smoke.js §11/§12 |
续跑接线、回落可见性、快照落点、累计与护栏的源码锁 |
变异(每次只有一个维度被破坏,期望只有新断言变红):
| 变异 | 结果 |
|---|---|
| 禁用续跑 | orchestration 6 红 |
| 关掉显式批准闸门 | orchestration 2 红 + smoke 1 红 |
| 去掉「修改词」判定 | human-approval 2 红(第一版夹具没区分力,补「批准,但把 X 改成 Y」后才有牙) |
| usage 忽略 offset | prd-revise 1 红(夹具同样需要第一轮带 usage 才有区分力) |
夹具必须先证明它能区分正确实现与错误实现,否则测的是寂寞。
5. 已知未验
- 桌面版(宿主
0.2.0-rc.2):本插件 0.3.x 要求宿主 ≥0.2.1-alpha.2,桌面侧需等上游发版。
已 grep 确认2c5a933之前的续跑所需 API(sendMessage/observeSession/prepareContinuable)
在 rc.2 中也存在,但startActivation不存在 ⇒ 桌面目前跑不了 0.3.x。 - 重复护栏(
log.usageAccumDup)从未触发:修复后不再需要它,保留作保险,属于死路径。 - 超 5 轮改写未验:上下文会被 compaction 裁剪(窗口 192k),早期内容存在被挤掉的风险。
缓解手段是快照(文件不丢),但没有量化验证。
6. 升级指引
- 无
engines变化,无 schema / journal / 任务夹契约变更。 - 升级后必须重启
dsh --profile web(桌面版重启应用)——插件是构建产物lib/host.mjs,
进程不重启则仍加载旧代码(本版排查中曾因此误判过一次"修复无效")。 - 想验证改写是否走了续跑:看日志里
PRD 改写走**续跑**;出现回落为重跑会写明是哪一类原因。
v0.3.0
TeamFlow v0.3.0
发布日期:2026-10-17。变更清单按 release-npm-package 铁律 1 由
git log v0.2.9..HEAD生成
(4 笔提交 / 10 个文件 / +349 −18),不凭会话记忆。
⚠️ 本版含行为变更(PRD 确认单必须由人批准)。逐条论证见各提交信息。
中文
⬆️ 升级要点
engines未变(宿主仍须 ≥0.2.1-alpha.2)。升完重启dsh --profile web(桌面版重启桌面应用)。- 宿主若跑在 Node 24 上,本版修掉「续跑与面板启动 100% 起不来」的硬故障,务必升级。
- 无
teamflow_*工具 schema / journal / 任务夹契约改动,不需要迁移。
① Node 24 上续跑 / 面板启动全挂(修复)
现象:宿主跑在 Node 24 上时,任何「续跑」与「面板启动」都在 1–3ms 内失败,报
failed to start/execute: signals[1] is not of type AbortSignal.;只有模型工具 teamflow_start(带真
exec.signal)幸免。Node 22 上完全正常。
根因链:
- 兜底
SAFE_SIGNAL是鸭子对象{aborted:false, addEventListener(){}, removeEventListener(){}, throwIfAborted(){}},
不是真正的AbortSignal; normalizeSignal的判定也是鸭子式(有addEventListener/aborted/throwIfAborted即收)⇒ 伪对象一路通过,
被塞进request.signal与spec.signal;- 宿主把调用方 signal 原样丢进
AbortSignal.any([...])(dsh-api-gateway的
AbortSignal.any([token.abort.signal, callerSignal])与dsh-subagent的
AbortSignal.any([inputs.signal, controller.signal])); - Node 22 的
AbortSignal.any只做鸭子校验(是对象且有aborted即收,composite 行为甚至完全正确)
⇒ 三年零症状;Node 24 改走 WebIDLsequence<AbortSignal>真 brand 校验 ⇒ 抛错。
传给宿主 undefined 的入口共三条,全部中招:resumeRun(续跑)、api.start(面板启动)、
clarificationPreflight(triage 子代理)。
修复:SAFE_SIGNAL 改为 new AbortController().signal(真 signal、永不 abort,语义不变);
normalizeSignal 只认 brand(instanceof + Object.prototype.toString 跨 realm 兜底 + throwIfAborted 复核),
不再接受鸭子形状。
门禁(关键):断言必须落在契约上(normalizeSignal(x) instanceof AbortSignal),而不是
「跑一遍宿主那行看抛不抛」—— 后者在 Node 22 上对伪对象是绿的,同一份代码换个运行时才红。新增
test/signal-brand.test.js(29 条)+ 编排级 brand 断言([N] 每个阶段交给宿主的 signal、
[23] 真 resumeRun 链路)。变异:复原伪 SAFE_SIGNAL → 编排 2 红 + brand 12 红;放宽成鸭子判定 → brand 4 红。
CI 另加 Node 24 矩阵,但注释写明它抓不住这一类(同一套测试当年在 22/24 上全绿)。
真机取证:tf-mvc4jrl7-7g6hnb(Node 24.9.0)—— 08:34 续跑后 PRD 子会话在续跑日志后约 200ms 创建成功;
09:04 起 dev 三路并发同时 spawn,全轮 signals[ / failed to start 零命中。
② PRD 确认单必须由人批准(行为变更 + 修复)
现象:确认单暂停后,主会话模型自己调 teamflow_resume 把单批了,而代价是「立即开始写代码」。
实锤 tf-mvc4jrl7-7g6hnb:08:33:36 暂停,08:34:02 模型自调 resume 并附上自己撰写的 398 字「批准文」,
该时间窗内零条人类消息。
根因:插件把暂停汇报作为 user/message 注入主会话,而汇报原文给的是可执行样例
「批准:调 teamflow_resume(runId=…)」,还强调「批准 = 立即开始写代码」——
「等用户批准」与「给模型一份操作手册」自相矛盾。
修复(判据下沉到宿主,不依赖模型自律):
- 会话日志里人类消息带
data.source.kind === 'user',插件注入的是plugin:dsh-plugin-teamflow,
另有time-context/model-selection/skill-catalog等系统来源 ⇒ 「暂停之后有没有人答复过」可机器判定。 resumeRun新增origin(tool|ui):tool 路径且停在确认单形态(afterPrd && prdOnly)时,
要求存在严格晚于interruptedAt的人工答复,否则拒绝并留痕、要求先把确认单要点问用户;
界面入口(api.resume)标注ui,不受此限(那次点击本身就是人的动作)。- 人工答复认两类,缺一不可:
- 人会话说 ——
user/message+source.kind === 'user'; - 人点了
ask_user_question的选项 —— 宿主把回答落盘为tool/result(data.message.source={kind:'tool',callId}),
不产生user/message。只认结果不认调用(调用=模型问了,结果=人答了)。漏掉这条的症状是:
我们自己在汇报里让模型用 AUQ 问,用户点完选项却被闸门拒 —— 这条是本版验证过程中实测发现并补齐的。
- 人会话说 ——
- 读不到会话日志(宿主 API 变更)时放行 + warn:宁可降级,也不能让所有续跑全挂。
- 汇报文案不再给可执行样例,改为「这一步只能由人决定,不要替用户批准」。
- 读取接口优先
snapshotEvents()(ownEvents()在宿主 d.ts 已@deprecated,且只返回子级自有事件
⇒ fork/重启后可能漏读人类消息而误挡合法续跑)。
门禁:test/human-approval.test.js(24 条,含三态 null/false、旧消息不算、严格晚于、AUQ 结果才算)+
编排 [23]「暂停后无人工答复 ⇒ 代批被拒且不起任何子代理」。变异:关掉闸门 → 编排 1 红;
放宽成「任何 user/message」→ 5 红 + 编排 1 红;删掉 AUQ 分支 → 2 红。
真机取证:
- 文案侧生效:沙箱 run
tf-mvcd8hw3-5c8nzl(mode=medium)在确认单暂停后,模型没有自作主张,
而是用ask_user_question转述确认单要点并等答复; - 放行侧生效:同一 run 中用户只点了卡片选项(会话里唯一的痕迹是
tool/result,没有user/message),
随后模型调teamflow_resume→ 放行(journal 记「断点续跑:从「技术方案」继续」,拒绝/降级日志零命中)。
这条路径在缺 AUQ 判据时必然被误拒,因此同时证明该判据必要且生效。 - 已知边界:
patch档按设计不走确认单(shouldConfirm && mode !== 'patch'),因此该档位不受本闸约束;
沙箱 runtf-mvca1x3i-17yi63即为mode=patch,PRD 后直接进 dev 并自测收口。
验证
test/全套 38 个套件在 Node 22.22.2 与 nvm 24.9.0 上双绿;tsc --noEmit0;oxlint --deny-warnings0 warning。- 变异实验 6 组(signal brand 2 组 / 闸门 3 组 / snapshotEvents 优先级 1 组),每组均使新增断言变红。
- 真机两条路径(Node 24 上的续跑与并发、确认单的文案侧与放行侧)均已复现并验证。
English
Upgrade notes
enginesunchanged (host still needs ≥0.2.1-alpha.2). Restartdsh --profile web(or the desktop app) after upgrading.- If your host runs on Node 24, this release fixes a hard failure where every resume and panel-start died
within 1–3ms (signals[1] is not of type AbortSignal.). Upgrade.
① Node 24 broke every resume / panel start (fix)
The fallback SAFE_SIGNAL was a duck-typed fake ({aborted:false, addEventListener(){}, …}) handed to the host as
request.signal / spec.signal, and normalizeSignal accepted it because its check was duck-typed too. The host feeds
caller signals into AbortSignal.any([...]): Node 22 validates leniently (any object with aborted passes, and the
composite even behaves correctly) — hence no symptoms for years; Node 24 goes through WebIDL
sequence<AbortSignal> and rejects it outright.
Fixed by using a real new AbortController().signal (same semantics: never aborts) and by making normalizeSignal
accept only genuine brand (instanceof + cross-realm toString fallback).
Gates assert the contract (x instanceof AbortSignal), not “does the host line throw” — the latter is green on
Node 22 for the very same code. New test/signal-brand.test.js (29 assertions) plus orchestration-level brand
assertions; a Node 24 CI matrix is added but explicitly documented as unable to catch this class.
② The PRD confirm sheet could be self-approved by the model (behaviour change + fix)
The pause report is injected into the main session as a user/message, and it used to hand the model an executable
recipe (“to approve: call teamflow_resume”). The model complied and approved on the user's behalf — with zero human
messages in that window — and approving means code immediately starts being written.
Now the host decides: on the tool path, when a run sits at the confirm-sheet pause, a resume requires a human
reply strictly after the pause, otherwise it is refused and the model is told to ask the user first. A human reply is
either a user/message with source.kind === 'user', or the answer to an ask_user_question — which the host
persists as a tool/result and never as a user/message (missing this half would reject users who merely clicked
an option, so both halves ship together). The UI entry (api.resume) is exempt because the click itself is the human
action; an unreadable session log degrades to allow + warn rather than blocking every resume.
Gates: test/human-approval.test.js (24 assertions) plus an orchestration case proving a self-approval attempt is
refused without spawning any sub-agent.
v0.2.9
TeamFlow v0.2.9
发布日期:2026-10-17。变更清单按 release-npm-package 铁律 1 由
git log v0.2.8..HEAD生成
(11 提交 / 35 文件 / +1371 −102),不凭会话记忆。⚠️ 本版含破坏性变更(宿主 subagent API)。
逐条论证见各提交信息与docs/TODO.md。
中文
⬆️ 升级要点
- 先升宿主,再升插件。本版把
engines.dsh收窄为>=0.2.1-alpha.2 <0.3.0——宿主低于该版本会被宿主直接
跳过本插件(skippedBundles,工作台不可用)。这是宿主在 alpha.2 删除旧接口后的必然结果,不是可绕过的。 - 升级命令见 README;升完重启
dsh --profile web(桌面版重启桌面应用)。 - 之后的新版本会由插件自己提示(v0.2.8 引入的更新提示),无需再手动查。
① 破坏性:适配宿主 alpha.2 的 subagent API(issue #20)
宿主 dsh-v0.2.1-alpha.2(提交 3d852a1cea)删除了扁平入口 subagents.start(name, request),唯一入口改为
startActivation(spec)。
- 不做旧版兼容、不写 shim:以
engines.dsh当门禁。⚠️ 顺带订正两条旧结论——engines.dsh是宿主强校验
的(旧 README 写「宿主不校验」是错的);alpha.2 起 one-shot 也占maxActiveSubagents池(默认 8)。 - 计量句柄必须在「启动窗口内」取:宿主对本地子级 idle 即 close,而
activation.result要等 handle cleanup
之后才 settle ⇒ 原来的「lazy getter」读 token usage 时句柄早已注销(实锤某个 run 21/21 阶段 usage 全空、
静默数小时,run 还照报 completed)。现在改为startActivation返回那一刻取活 Agent 塞进句柄;再取不到就以
diag.usageMissing每 run 一条 warn 显式上报(空值必须可见)。 - 跳子代理会话的地址
mode传'unknown':本插件的阶段子代理在 alpha.2 起一律记continuable,写死'one-shot'
会被宿主判subagent/unauthorized(实机点「跳转子代理会话」即失败)。'unknown'是宿主钦定的浏览语义,不是猜。
② 真机暴露后的修复批次
| # | 症状(真机实锤) | 修复要点 |
|---|---|---|
| 1 | 宿主重启后中文环境整轮产物全英文,且界面中英混排 | 客户端语言上报改有界重试(成功即停 / 2s 重排 / 上限 8 / 世代判据 / dispose 安静);宿主新增 localeSource() 三态,新 run 语言取自兜底时写可见 warn(原先全静默,正是它长期没被发现的原因) |
| 2 | 确认单串了上一轮(「汇报层串了模版」):r5 的确认单显示 r4 的目标与 AC | state.json 是按项目累积、跨 run 的;确认单改为只读本轮快照 journal.prdSnapshot,缺就诚实显示「没抓到」,不回落旧值冒充本轮 |
| 3 | 模型把参数传错却被判「环境不可用」→ 硬停且不重试 | 「参数类失败」豁免从「列举 fs 工具三条文案」改为按解析层前缀整体覆盖(含宿主通用前缀 invalid arguments:)——否则任何非 fs 工具的类型错都会硬停 |
| 4 | QA 复验表里「已修复」的缺陷被计成本轮阻断 → 三轮「未收敛」→ 验收被跳过、run 误降级 needs-human | 判据补「状态/结论列」:明确标已闭环的行跳过(残余/待修复/开放 照常计入);新增冻结语料 + 循环级行为门禁 |
| 5 | 「文件即产物」只查「在不在/够不够长」→ 上游越界写下游产物,下游自评 | 加新鲜度指纹(阶段开始时的 size:mtimeMs):只有指纹变了才认、只填缺失项 |
| 6 | 12 个缺陷卡都显示「待认领」,但两类原因不可区分 | 卡片/详情面板新增「停留原因」行:P3 观察项(不自动关)/未闭环·来源需求需人工裁定/未闭环·待修复 |
验证
- typecheck 0 错;全量测试通过(新增/扩写
confirm-run-snapshot、bug-open-reason、locale、empty-turn、
orchestration、smoke,以及新增冻结语料qa-reverify-status-column.md);各修复均配单维度变异证明断言有牙。 - 真机:alpha.2 适配经两个真实 run(
tf-mvaubzdz/tf-mvb3lm0b,共 24 阶段)全程跑通子代理;确认单快照与
invalid arguments:豁免均在真机日志中确认生效。 - 唯一未真机项:第 4 条(QA 复验表)。其输入形态取自真实 QA 报告并已冻结成语料,运行路径由循环级行为门禁
(orchestration.test.js [10b])覆盖——该项的真实触发很随机(历史样本里触达复验上限只有 1/52),故以门禁替代真机。
English
⬆️ Upgrade notes
- Upgrade the host first, then this plugin.
engines.dshis now>=0.2.1-alpha.2 <0.3.0. On an older host the
plugin is skipped by the host (skippedBundles, workbench unavailable). This is forced by the host deleting the
old subagent entry in alpha.2 — there is no workaround. - After upgrading, restart
dsh --profile web(restart the desktop app on desktop). - Future versions will announce themselves (the update notice introduced in v0.2.8).
① Breaking: adapt to host alpha.2 subagent API (issue #20)
Host dsh-v0.2.1-alpha.2 (commit 3d852a1cea) removed the flat entry subagents.start(name, request); the only
entry point is now startActivation(spec).
- No backward compatibility, no shim — the
engines.dshrange is the gate. Two earlier conclusions were also
corrected:engines.dshis host-enforced (the old README claim that it is not was wrong), and since alpha.2
one-shot agents also occupy themaxActiveSubagentspool (default 8). - Metering handles must be taken inside the startup window. The host closes a local child as soon as it goes idle,
whileactivation.resultsettles only after handle cleanup — so the previous lazy getter read token usage after
the handle was already unregistered (one real run had all 21/21 stages with empty usage, silently, for hours).
Handles are now captured the instantstartActivationreturns; missing values raise a per-rundiag.usageMissingwarn. - Subagent-session address
modeis now'unknown'. Stage subagents are recorded ascontinuablesince alpha.2, so
a hard-coded'one-shot'is rejected assubagent/unauthorized.'unknown'is the host's own browsing semantics.
② Fixes found on real machines
| # | Symptom (observed on a real machine) | Fix |
|---|---|---|
| 1 | Whole run produced English artifacts in a Chinese environment | Client locale push is now a bounded retry (stop on success / 2s reschedule / max 8 / generation guard / quiet dispose); host adds a three-state localeSource() and warns visibly when a new run falls back |
| 2 | Confirm sheet carried the previous run's content | state.json accumulates across runs; the confirm sheet now reads a run-level snapshot (journal.prdSnapshot) and honestly says "not captured" instead of showing stale values |
| 3 | A wrong tool argument was classified as environment unavailable and hard-stopped | The argument-failure exemption now matches the parse-layer prefix class (invalid arguments:), not just three fs-tool messages |
| 4 | Fixed defects in a QA re-verification table counted as open blockers → "never converged" → acceptance skipped, run wrongly downgraded to needs-human | The status/conclusion column is now honored (rows explicitly marked closed are skipped); frozen corpus + loop-level gate added |
| 5 | "File is the artifact" only checked existence/length → upstream could write downstream artifacts | Freshness fingerprint (size:mtimeMs captured at stage start) |
| 6 | All open defect cards showed "unclaimed" while two very different reasons were indistinguishable | Cards/detail pane now show a "why still open" line |
Verification
- typecheck clean; full test suite green (new/extended
confirm-run-snapshot,bug-open-reason,locale,empty-turn,
orchestration,smoke, plus the new frozen corpusqa-reverify-status-column.md); every fix ships with a
single-dimension mutation proof. - Real machine: the alpha.2 adaptation ran through two real pipelines (
tf-mvaubzdz/tf-mvb3lm0b, 24 stages total)
with subagents working end to end; the confirm-sheet snapshot and theinvalid arguments:exemption were confirmed in
real logs. - The only item not verified on a real machine is #4; its input shape comes from a real QA report and is frozen as a
corpus, and its execution path is covered by the loop-level gate (orchestration.test.js [10b]).
v0.2.8
TeamFlow v0.2.8
发布日期:2026-10-12。变更清单按 release-npm-package 铁律 1 由
git log v0.2.7..HEAD生成
(5 提交 / 25 文件 / +1677 −22,含 5 份通稿文档),不凭会话记忆。无破坏性。
设计决策与五轮 UI 真机反馈的完整论证见docs/adr/0012-update-notice-and-one-click-upgrade.md。
中文
⬆️ 升级要点
- 从 0.2.7 及以前升级:本版起插件会自己提示新版本;本次升级仍需手动(命令见 README「升级(对使用者)」),
之后的新版本就能在工作台 / 插件页里看到并一键升级。 - 升完重启
dsh --profile web(桌面版重启桌面应用)。
① 为什么做这个(ADR-0012 的问题定义)
真机观察:插件已更新多版,仍有不少用户停在旧版本。拆成两轴后结论是——问题不在「装不上」,
而在 ①用户不知道有新版(发现)②升级不是点一下(动作成本)。本版只解决这两轴;
可达性(npm 镜像滞后等)不是本轮问题。
对存量用户的第一次触达无效——通知机制本身要靠升级才能获得 ⇒ 老版本用户看不到它。
撬动存量靠 README / 发版通稿写明升级命令,这根杠杆不能省。
② 发现:三处落点,一份实现
| # | 落点 | 覆盖谁 |
|---|---|---|
| ① | 宿主插件页徽标 + 操作(plugins.detail.badge / .actions 槽位贡献) |
去设置页管理插件的人——找「更新」的默认位置 |
| ② | 工作台顶部提示条 | 打开工作台的人(打开时按需探测一次) |
| ③ | 全局面板头部「检查更新」按钮 | 唯一的手动入口:①②都吃 24h 缓存,用户得有主动问一次的办法 |
三处共用 client/update-ui.tsx(唯一实现,不许调用方各写一套)。
web 走命令,桌面版在应用内操作(CLI 拒绝 --profile desktop)。
探测按需触发 + 24h 缓存,不做后台定时(探测功能不得变成轮询)。
为什么值得在宿主页面上再贡献一个按钮:宿主插件页能装(npm 包 / Git 仓库 / 本地目录),
但它的「更新」只给官方插件 —— 第三方插件在宿主口径里是「请先卸载再安装新版」
(ui-plugin-manager/src/client/locales.ts 的 installUpgradeNotice / installProblemInstalled)。
⇒ 本插件贡献的徽标+「升级」按钮=把「卸载+重装」换成「原地升级」,这也是一键升级真正的缺口所在。
③ 动作:一键升级 + 守卫
- 走宿主
pluginManager.installBundle:备份 / 回滚 / profile 写锁 / install-script 审批 / 取消
都是宿主现成的,不自己 spawn pnpm。 - 两步确认:点一次只展开说明(在当前 profile 执行 pnpm 安装、改动依赖、装完要重启),
再点确认才真调。这条服务路径绕过了宿主工具层的审批门槛 ⇒ 知情责任落在我方。 - run 进行中拒绝;失败按宿主分型结论渲染(run 中 / 无插件管理服务 / 非 registry / 待构建包是不同的事)。
- 只对 registry 安装开放一键:
file:/ git / tarball 装法一键会把依赖静默换成线上版本
(真机踩过:0.2.7 本地部署被一键切成 registry 版,随后回滚)⇒ 只给可复制的裸命令。
④ 探测底座(host/core/update-check.ts,纯函数)
TEAMFLOW_UPDATE=0可整体关闭(环境开关不进公开 schema——同craft/confirm的处置)。- 「跳过此版本」跳的是具体版本号:更新的版本出现时仍会提示(跳过 ≠ 永久静默)。
- 4s 超时;永不抛:联网失败降级为「不提示」并在返回对象里显式带
error——探测不得成为故障源。 - 版本比较含 prerelease 语义;解析不了的版本一律不提示(宁可漏报不做假提醒)。
⑤ 文档
- README / README.en 新增「升级(对使用者)」:一键与命令两条路径、跳过语义、重启要求。
docs/adr/0012:完整决策记录 + 工具条呈现七条纪律(五轮真机反馈:
整句挤爆工具条 →title点不了 → 浮层够不到(margin 间隙归属)→ 按钮竖排(NO_WRAP)→
复制到说明书(引号括号))。核心教训:一个字符串有两个消费者就是两份契约。
验证
- typecheck 0 错;全量 35 项测试通过。
- 新增
test/update-check.test.js(负向判据为主:不提示的情形比提示的情形多);
四条单维度变异各让对应新断言变红、其余保持绿 ⇒ 断言有牙。 - 真机(desktop profile):探测 → 提示条 / 悬浮层悬停 / 一键复制 / 升级全链路实测通过;
升级后由命令行路径回装验证installCommand的裸命令可直跑。 installRecipe拆分重构前后六个分支逐字节比对(该文案进__runCtx,零漂移)。
English
⬆️ Upgrade notes
- Upgrading from ≤ 0.2.7: this is the version that starts telling you about newer ones — this
particular upgrade is still manual (commands in README "Upgrade (for users)"); after it, new
versions surface in the workspace / plugins page with one-click upgrade. - Restart
dsh --profile webafterwards (or restart the desktop app).
① Why (ADR-0012)
Real-world observation: many users stay on old versions despite frequent releases. Split into two
axes, the problem is NOT "can't install" but ①discovery (users don't know) and ②action cost
(upgrading isn't one click). Reachability (mirror lag etc.) is explicitly out of scope.
First contact does not reach existing users — the notice mechanism itself arrives only by
upgrading ⇒ the lever for the installed base is the README / release notes carrying the upgrade
command.
② Discovery: three surfaces, one implementation
| # | Surface | Who it covers |
|---|---|---|
| ① | Host plugins-page badge + action (plugins.detail.badge / .actions slot contributions) |
People managing plugins in settings — the default place to look for updates |
| ② | Workspace top banner | People opening the workspace (probes once on open) |
| ③ | Global panel "Check for updates" button | The only manual entry: ①② eat a 24h cache, so users need a way to ask |
All three share client/update-ui.tsx (single implementation). Probing is on-demand + 24h cache;
no background polling.
only the upgrade action differs — a command for web, the in-app page for desktop (the CLI refuses
--profile desktop).
Why contribute a button to the host page at all: that page can install (npm package / Git repo
/ local directory), but its "Update" is offered for official plugins only — for third-party plugins
the host's own wording is "uninstall first, then install the new version"
(ui-plugin-manager/src/client/locales.ts: installUpgradeNotice / installProblemInstalled).
⇒ The badge + upgrade button contributed here turn "uninstall + reinstall" into "upgrade in place",
which is the actual gap one-click upgrade fills.
③ Action: one-click upgrade + guards
- Goes through the host's
pluginManager.installBundle(backup / rollback / profile lock /
install-script approval / cancel are the host's, not reimplemented). - Two-step confirm; refuses mid-run; failures render by the host's typed verdict, not one
generic "upgrade failed". - Registry installs only: for
file:/ git / tarball installs, one click would silently swap
the dependency to the registry version (hit on a real machine, then rolled back) ⇒ those get a
copyable bare command instead.
④ Probe base (host/core/update-check.ts, pure functions)
TEAMFLOW_UPDATE=0disables the whole feature (env-only, not in the public schema).- "Skip this version" skips that exact version; a newer one still notifies.
- 4s timeout; never throws — network failure degrades to "no notice" with an explicit
error. - Unparseable versions never notify (prefer a missed notice over a false one).
⑤ Docs
- README / README.en gain "Upgrade (for users)": one-click and command paths, skip semantics, restart.
docs/adr/0012: full decisions + seven toolbar-presentation rules from five rounds of real-machine
feedback. Core lesson: a string with two consumers is two contracts.
Verification
- typecheck clean; all 35 tests pass.
- New
test/update-check.test.js(negative criteria first); four single-dimension mutations each
turned exactly the matching new assertions red ⇒ the assertions have teeth. - Real machine (desktop profile): probe → banner / hover popover / copy / upgrade verified end to end.
installRecipeoutput compared byte-for-byte across six branches before/after the refactor
(that text goes into__runCtx— zero drift).
v0.2.7
TeamFlow v0.2.7
Release — 「澄清闸门 + PRD 确认单落地 + 机器契约彻底离开人类可读通道」版本(43 笔新提交 / 36 个文件 / +3485 −277 行,v0.2.6..HEAD 全量对比)。无破坏性:teamflow_start 的参数对象是 additionalProperties: true,本版摘出工具 schema 的两个字段(needConfirm / clarifyStrength)即使被传入也只被忽略、不会报错;journal / 任务夹契约 / teamflow_* 其余参数全部向后兼容。
中文
⬆️ 升级要点
- 本版主线三条:① 澄清闸门 + PRD 确认单(ADR-0010 / ADR-0011,最大一块);② resume / 断点系列修复(四处,其中两处是「机制上听起来对、实际全错」的伪代理判据);③ 机器契约彻底离开人类可读通道(state 块与蓝图块双双改走独立文件)。
- 产品路径改由内容驱动,不再靠开关:
needConfirm/clarifyStrength摘出公开工具 schema,改环境变量(TEAMFLOW_CONFIRM/TEAMFLOW_CLARIFY)。- 理由:一个「用户不会主动设、工具描述里也没说何时该设」的参数 = 功能不存在(用户原话:「用户又不知道这参数」)。
- 兼容性:参数对象
additionalProperties: true⇒ 老调用传入不报错,仅被忽略。
- PRD 确认单会主动暂停:PRD 里出现主观品味项(美术风格 / 交互形态 / 人设等)时,流水线停在 PRD 后等你批准,纯信息缺口不阻断(保住无人值守)。
- 升完重启
dsh --profile web(桌面版重启桌面应用)生效。
① 澄清闸门与 PRD 确认单(本版头条)
目的判据(用户拍板):不是「防返工」,而是让用户确认自己要实现的东西 —— 不能靠模型能力强度替代,因为产品主观性强的东西必须让用户自己创造迭代(否则「用户说实现贪吃蛇,就给他一个简单贪吃蛇」)。
- 确认单改「内容驱动」:旧实现用
needConfirm显式开关(用户得自己知道要敲它);现改为有主观项才停。配套:PRD 假设段从「只有信息不足」扩一维到主观决策项(7e2cf0a),openQuestions每条加kind: 'fact' | 'taste'(有taste才停)。 - AC 抽取三层(ADR-0011 C):结构化优先 → 正文兜底 → 来源必须自曝(空壳比空更糟)。真实语料逼出两条规则:故事表
| 用户故事 | AC-1: … |(AC 在第二列,只锚行首会漏 4/4)、区间引用行不得造垃圾条目。 - 确认单断点开放「回退重跑 PRD」(
4135abb):原先选项文案写着「我再更新确认单后开工」而后端明确拒绝supplement⇒ 假承诺(用户以为能改、点完才发现要么重开要么白跑)。现在断点开放回退重跑,判据落在内容上(stages除 prd 外无记录 ⇒ 作废面为零)。 - 澄清协议归 ctx 手册(
814472d):clarifyStrength从未影响过流水线(只选 ctx 文案变体)⇒ 整体移出PipelineOptions,默认档写进手册。判别式:若一个参数的唯一作用是在几个文案变体里挑一个,它就该是文案的默认值,不是参数。 - 开工前对齐也走 AUQ(
f96852d):手册补ask_user_question指令。 - 失效可见化(
a144138):澄清闸门失效时不再静默。 - 分诊侧:
blocker增guess软字段(模型自己的倾向,供用户快速作答)。
731fd42 —— journal.options 白名单漏了一个字段,导致 needConfirm 与 clarifyStrength 在真实路径下根本不生效,而测试全绿(因为 runScenario 直调 executePipeline,绕过了白名单)。⇒ 新增 PipelineOptions 字段时必须同时问「白名单收不收它」。
② resume / 断点系列修复(四处,两处是伪代理判据)
| 提交 | 事故 | 教训 |
|---|---|---|
9a97cd4 |
确认单暂停后无法续跑 | 无断点判据用了「已记录阶段全 done」这条伪代理 |
a298242 |
同一 run 内 state 从不刷新 | mergeStateBlock 返回新对象而调用点丢返回值 ⇒ 本 run 后续阶段看不见 extra.* |
9388cc6 |
断点定位第二层 | 「启用过但从未尝试」的阶段也应算断点 |
05f9fa3 |
零阶段 run 断点修正 | +续跑补录入口 |
1f1c4fa |
resume 文案把「从未尝试」说成「失败」 | !st || !st.done 同时含两种形态 ⇒ 两个数分开报 |
③ 机器契约彻底离开人类可读通道
一句话:机器契约(state 块 / 蓝图块)不再出现在任何用户会读的文本里 —— 既不在回复正文,也不在交付文档,连标记包裹都去掉。
b96b44e机器契约块不得出现在展示面(用户截图实锤:子任务卡把整段<!-- state -->{…}摊在用户眼前)。049961bstate 块改文件通道:机器契约不再走人类可读通道。9958cec蓝图改独立文件通道 + 截断抢救 + 原文落档(本版收口):- 蓝图从「
TECHNICAL.md末尾的块」改为logs/teamflow/<runId>/blueprint.json。Tech 文档是 user-facing deliverable,而蓝图块真机实测占其 31% 字符(5403/17583)。解析链三级回退:回复正文 → 独立文件 →TECHNICAL.md(历史通道)。 - 中转文件读完即删,清理与「通道是否命中」解耦 —— 模型「既回显又写文件」时不得漏删(否则可续跑终态不归档 ⇒ 永久残留)。
- 截断抢救
closeTruncated:实锤tf-mv3op3i2-0ok7nb,模型写了 5937 字符完整蓝图却缺最外层 1 个},既有repairBlueprintJson只治「提前闭合」救不了 ⇒ 12 个任务退化成 1 个整体开发。现按栈逆序补齐未闭合的{/[;⚠️ 仅当截断不在字符串内、且转义已收尾时才补 —— 断在字符串中间硬补会得到「语法合法但语义缺失」的对象,比直接失败更危险。 - 原文落档到
$DSH_HOME/teamflow/<ws>/blueprints/<runId>.json(无条件,成败都留)。此前蓝图三处去向都不留原文(render只在注入 prompt 那一刻、journal.blueprint只存{modules,tasks}、中转文件读完即删)⇒ 「模型到底设计成什么架构」无从复查。
- 蓝图从「
- 标记形态修正(
9958cec):<!-- state -->/<!-- blueprint -->是自闭合注释 ⇒ 渲染器把整条消化掉,紧随其后的 JSON 成为裸文本,在 Typora / VS Code 预览里直接显示给用户。open 去掉尾部-->;解析器(?:-->)?两种都认(兼容存量产物与模型旧习惯)。 - state 块改纯 JSON:文件是独立
.json,整份就是那个 JSON,标记纯属冗余(还会多出「写漏关闭标记」这类失败)。新增stateFromText(先按标记、失败再JSON.parse);extractStateBlock保持不动 —— pipeline 与dev.ts的回落是在自然语言回复里找块,标记仍是唯一锚。顺带删掉state.memory死字段。 9036446dev 任务标签:if (resume)分支原先无条件写「(补跑)」⇒ 哪怕 dev 一次都没跑过(确认单暂停后续跑)12 个任务也全被标成补跑,读起来像「上次跑挂了」。判据改为两路取或:断点在 dev 或更晚 / journal 里有该任务记录。
- 凡「用完即删」的中转物,必须留一条事后取证旁路(否则同类问题会变成「无法诊断」——实锤只能靠解压 dsh 会话轨迹、从
write的tool/call参数里抠出模型写了什么)。 - 测「函数正确」≠ 测「调用点接线正确」:
stateFromText单测全绿,而readStateBlockFile若没接上它,文件通道会静默返回 null。接线必须单独断言。
④ 其它修复
5ed35d2可自愈的文件防护门禁失败不再计入 env-unavailable(issue #15)。7bdf8a1「工具在这一层不适用」补入unknown tool+ 宿主文案漂移检查。ffb0c86蓝图tasks[]限定实现范围 —— 不许把 QA / 验收的活划成 dev 任务。b7072ab确认单改两步(先导总问单独一次);认可补录语义改成「固化进 PRD」。
⑤ 文档
- ADR-0010(澄清闸门)、ADR-0011(访谈协议 + PRD 确认门)、ADR-0006(蓝图形态与通道订正)。
docs/TODO.md重排为三段结构(真待办 / 观察中 / 已否决)。docs/devlog.md改按发布批次归并(非逐 commit)。
验证
- 门禁:
npm test全绿(含 golden corpus 的零 LLM 确定性回归)。 - 真机:本版三条主线各有实机 run 实证(
tf-mv3op3i2-0ok7nb/tf-mv3rdt0y-qmlxl3/tf-mv3upkft-2oggfu等)。 - ⏳ 截断抢救尚未被真机触发(单测用真机那个 5937 字符截断样本,另有部署产物验证;三次真机 run 的蓝图均为合法 JSON)——如实标注,不作「已验证」声明。
English
Release — "clarification gate + PRD confirm sheet + machine contracts fully out of human-readable channels" (43 commits / 36 files / +3485 −277 vs v0.2.6). No breaking changes: teamflow_start's parameter object is additionalProperties: true, so the two fields dropped from the tool schema (needConfirm / clarifyStrength) are silently ignored, never an error.
⬆️ Upgrade notes
- Three main threads: ① clarification gate + PRD confirm sheet (ADR-0010/0011); ② four resume / checkpoint fixes (two were "sounds right, actually wrong" proxy criteria); ③ machine contracts fully out of human-readable channels (both the state block and the blueprint block moved to dedicated files).
- Product paths are content-driven now, not switch-driven:
needConfirm/clarifyStrengthwere removed from the public tool schema in favour of env vars (TEAMFLOW_CONFIRM/TEAMFLOW_CLARIFY). Rationale: a parameter the user would never set, and whose description never says when to set it, is a feature that does not exist. - The PRD confirm sheet pauses on subjective items (art style / interaction / persona). Pure information gaps do not block, preserving unattended operation.
- Restart
dsh --profile web(or the desktop app) after upgrading.
① Clarification gate & PRD confirm sheet (headline)
The purpose is letting the user confirm what they are actually getting built — not "preventing rework". Model strength cannot substitute for it: highly subjective product decisions must be left to the user to iterate on.
- Content-driven confirm sheet: the old explicit
needConfirmswitch is gone; the pipeline stops only when a subjective item exists. The PRD assumption section gained a "subjective decisions" dimension, and eachopenQuestionsentry now carrieskind: 'fact' | 'taste'(onlytasteblocks). - Three-layer AC extraction: structured first → prose fallback → source must self-disclose. Two rules came from real corpora: the story table puts ACs in the second column; range-reference rows must not produce junk entries.
- The confirm-sheet checkpoint can now re-run the PRD: the previous UI text promised "I'll update the sheet then start", while the backend rejected
supplement— a false promise. Now the checkpoint supports going back. - Clarification protocol moved into the ctx manual:
clarifyStrengthnever affected the pipeline (it only picked a ctx wording variant) and was removed fromPipelineOptions. Rule of thumb: if a parameter's only job is to pick one of several wording variants, it belongs in the wording defaults, not in a parameter. - 分诊 gained a
guesssoft field on blockers.
journal.options whitelist missed one field, so needConfirm and clarifyStrength never took effect on the real path — while tests stayed green, because runScenario calls executePipeline directly and bypasses the whitelist.
② Resume / checkpoint fixes
| Commit | Incident | Lesson |
|---|---|---|
9a97cd4 |
Could not resume after a confirm-sheet pause | Checkpoint criterion used "all recorded stages done" — a proxy |
a298242 |
State never refreshed within a run | mergeStateBlock returns a new object; the call site dropped it ⇒ later stages never saw extra.* |
9388cc6 |
Checkpoint detection, second layer | "Enabled but never attempted" also counts as a checkpoint |
05f9fa3 |
Zero-stage run checkpoint fix | plus a resume backfill entry |
1f1c4fa |
Resume wording called "never attempted" a "failure" | the !st || !st.done predicate covers both shapes ⇒ report two numbers |
③ Machine contracts out of human-readable channels
Machine contracts (state block / blueprint block) no longer appear in any text a user reads — neither in the reply body nor in delivered documents, not even as wrapped markers.
b96b44eMachine-contract blocks must not appear on the presentation surface (user screenshot: a subtask card displayed the whole<!-- state -->{…}).049961bThe state block moved to a file channel.9958cecThe blueprint moved to a dedicated file, plus truncation rescue and raw-text archiving:logs/teamflow/<runId>/blueprint.json— the Tech document is a user-facing deliverable, and the blueprint block measured 31% of its characters (5403/17583) on a real run. Parse chain falls back three levels: reply body → dedicated file →TECHNICAL.md(legacy).- The transient file is deleted after reading, with cleanup decoupled from "was the channel hit" — a model that both echoes and writes must not leave the file behind (resumable terminal states are never archived ⇒ it would linger forever).
- Truncation rescue
closeTruncated: a real run wrote a 5937-character complete blueprint missing the outermost}, and the existingrepairBlueprintJson(which only h...
v0.2.6
TeamFlow v0.2.6
Release — 「枚举不再被截断 + 大文档不再被裸 clip + 宿主升到 0.2.0」版本(23 笔新提交 / 47 个文件 / +3450 −139 行,v0.2.5..HEAD 全量对比)。无破坏性:宿主窗口由 >=0.1.7-alpha.1 <0.2.0 放宽为并集区间(含 0.2.0 预览版);journal / 任务夹契约 / teamflow_* 工具参数全部向后兼容。
中文
⬆️ 升级要点
- 🐛 安装阻塞修复(发版前最后一步拦下的):
postinstall: lefthook install→prepare。postinstall在消费者从 registry 安装时也会跑,而lefthook是 devDependency(不在 tarball 里)⇒ 安装直接失败。- 反证(同一 tarball,只改这一个字段):
版本 npm install <tarball>结果postinstall(v0.2.5 形态)❌ npm error command … lefthook install/'lefthook' 不是内部或外部命令prepare(本版)✅ added 1 package in 9s,lib/齐全 prepare只在本地 install / pack / publish 时跑 ⇒ git hook 仍自动装,发布不再炸。- 门禁:
test/smoke.js断言preinstall/install/postinstall必须为空、prepare必须是lefthook install。
- 反证(同一 tarball,只改这一个字段):
- 宿主窗口放宽到 dsh 0.2.0-rc.2(预览版)。区间是并集
">=0.1.7-alpha.1 <0.3.0 || >=0.2.0-rc.1 <0.3.0"——⚠ 上界不能简单放宽:semver 的「预发布只匹配同 tuple 区间」规则下,把<0.2.0直接改成<0.3.0,0.2.0-rc.1/0.2.0-rc.2仍判 fail(semver 7.7.4 实测)。 - 桌面版(dsh 0.2.0 起)需要单独安装一次:它用独立的
desktopprofile(~/.dsh/profiles/desktop),与web各自一份 node_modules。只装在 web 上,桌面版里看不到本插件(不是插件坏了,是那份 profile 里没装)。且 CLI 明确拒绝--profile desktop(error: profile "desktop" is managed exclusively by the Electron application)⇒ 桌面版请用应用内的插件管理界面安装。 - 升完重启
dsh --profile web(桌面版重启桌面应用)生效。 - ⚠ prompt 变长了:
host/prompts/index.ts净增 76 行(craft 五条款 + 四处磁盘路径指引)。本仓的instruction-budget门禁只管 AGENTS.md、不管 prompt,所以这部分未量化(增量以百字符计,单次调用多几十到几百 tok)。 - 新增日志键
log.hostScanTruncated。
① 交付枚举不再被上限截断(本版头条)
取证:tf-mupnk8h0-1otbl2(工作区 E:\Code\Probe\tf-stpanel-r1)——工作区里有 pnpm 的 .pnpm-store/,里面是几千个无扩展名 blob,而旧的跳过名单漏了 .pnpm-store,且它在 readdir 顺序里排在 AGENTS.md 之前 ⇒ 瞬间吃满 400 上限 ⇒ 返回的 400 条里源文件 0 个、HTML 0 个 ⇒ 四项检查同时静默跳过:
host 侧加载检查:本次未执行(no-html;根=E:\Code\Probe\tf-stpanel-r1)
host 侧接口核对:本次未执行(skip;源文件 0 个)
host 侧冒烟:本次未执行(no-html)
QA 验证证据观察:交付未见可执行入口(扫到 400 个交付文件),本项跳过
而交付其实完好:28 个交付文件 / 13 个源文件 / 1 个 index.html。机制复现:同目录形状 + 1000 个 store blob ⇒ 不跳点目录时「400 条 / 源文件 0 / HTML 0」;跳过点目录后恢复「23 条 / 源文件 11 / HTML 1」。用户是在浏览器里点不动才把它暴露出来的。
改动:scanDeliverableFiles(root, limit) 返回 { files, truncated, limit },点目录一律不遍历(它们从不是交付物,又最易吃满配额);listDeliverableFiles 退为薄包装(签名与旧行为不变)。三项检查任一 truncated ⇒ 追加一条 log.hostScanTruncated 撤回提示。
⚠ 措辞要说准:那三条「未执行 / 源文件 0 个 / no-html」的原文保留(检查确实没执行),撤回靠的是后一条 warn 点名它们不是事实 ⇒ 顺序也是契约(编排测试第 ⑨ 组钉住,实测 neg@13 / warn@16)。
门禁两层:test/smoke-check.test.js ②b 段(扫描器层)、test/orchestration.test.js 第 ⑨ 组(接线层,430 个无扩展名文件顶满 400)。变异实测:短路 if (scanTruncated) ⇒ 4 条立刻红。
② 大文档注入:裸 clip → 「小 teaser + 磁盘完整路径」
按 256 份真实任务夹文档实测:TECHNICAL.md 中位 19075 / 最大 35547;DESIGN.md 中位 9338;PRD 中位 6334;QA-REPORT 中位 4430。
| 文档 | 原阈值 | 会截断 | 处置 |
|---|---|---|---|
| TECHNICAL.md(dev / qaFix) | 12000 | 47/50(94%) | 4000 + 磁盘路径 |
| DESIGN.md(scaffold / tech) | 10000 | 11/31(35%) | 4000 + 磁盘路径 |
| PRD.md(design / tech / architect) | 15000 / 12000 | 3 |
阈值不动,只补路径 |
| QA-REPORT.md(qaFix) | 12000 | 2/51(4%) | 阈值不动,只补路径 |
⚠ 一个反直觉结论:TECHNICAL 中位 19075 ⇒ 连 4000 的 teaser 也截 94% ⇒ 磁盘路径才是修复主体,teaser 价值有限——缺了它,模型根本不知道后面还有内容。
明确不动的两处:clip(scaffold, 10000) 与 clip(devSummary, 8000)——它们不是磁盘文件,无路径可指,加提示就是空话。
门禁:clip 纪律源码锁 6 道(tech / design 各一条「只用 4000」+ 四条「路径指引覆盖 N 处」:TECHNICAL 2 处、DESIGN 3 处、PRD 3 处、QA 1 处)。
③ craft 引导通道(design → dev 接通)
来源是「流水线 vs 裸跑」对照的机制归因:designPrompt 写出的 DESIGN.md 到不了 dev 手里(devPrompt 签名没有 design 参数)⇒ 流水线在「做好看」这件事上结构性输给单 agent。
devPrompt(task, tech, prd, design, …)新增 design 入参,注入视觉规格 + 磁盘路径,明写「视觉规格写在 DESIGN.md 开头,先读它再动手做任何可见的东西」。- 开关
TEAMFLOW_CRAFT:未设置 / 空串 = 开,只有0/false/off/no才关;经util.envFlagOn→options.craft→state.__runCtx.craft。不做工具参数(工具 description 每次都进主会话上下文),resume 读 journal 快照不读环境变量。 - 定位收窄为交互反馈下限 + 状态可见性,不做审美裁决;QA 只记 P3 observation、绝不阻断(弱/本地模型产不出 craft,硬门禁会让插件对它们不可用)。
- ⚠ 净收益至今无定论:三组样本方向互不相同,全被 QA 返工轮 / dev 任务拆分数 / host 检查静默跳过盖住。详见
docs/benchmarks/craft-ab-pomodoro.md。
④ 新增脚本
scripts/artifact-selfcheck.mjs—— 产物自洽探针(静态、零浏览器依赖):[geo]几何自洽 /[bind]未绑定按钮与歧义选择器 /[craft]反馈态计数 /[state]死快照 /[persist]持久化缺口。[state]来自一次真实误判:某产物「滑块点不动」,而此前所有静态指标(反馈态规则数 10 vs 9、AC 数、bind/geo)全绿——真问题(入口把 value 写死成常量)没有任何门禁看得见。scripts/hi-report.mjs—— 人工介入归因巡检(只读)。动机是本仓曾长期按「humanIntervention 8/23 = 35%」排优先级,而那是早于 09-17 的样本。分层实测 91 个 run:09-17 之前 58 个(20.7%、failed 12);09-17 之后 33 个(9.1%、failed 0),且剩下 3 次全部是knownIssuesAcceptance(QA 复验超限 → 产品决策刻意交给人,不是缺陷)。
⇒ 没有可复跑的度量,旧数字就会一直驱动错误的优先级——本脚本就是那个度量。
⑤ 护栏 / 编排 / 客户端(本轮前段)
- 澄清闸门埋点(ADR-0010 第 1 步)。
- env-unavailable 不再误杀「文件级可自愈冲突」,并加进展豁免(用成功调用判定,不用
lastMutationAt——后者在 tool/call 就更新,反复失败的写会让检测永不触发)。 - 「工具在这一层不适用」不再判死(子代理无 Agent Team 身份 / 无委派深度时调用必失败,但那不代表工作区全废)+ 禁调协作工具 + dev 端到端证据。
- 可续跑终态不归档暂存日志——resume 要靠上一轮的 checker 当回归契约。
- 消除「验证脚本会随 run 丢弃」的误导定性,禁止拆 checker 重建任务。
- 客户端窄屏:画布支持触摸、缩放下限不再写死、窄容器默认列表(issue #10);⚠ 缩放下限保留 0.5(0.12 时卡片只有 37×7px,糊成一团;上一版把「能看全」当目标是错的,已 revert);详情浮层修窄屏左侧溢出;画布提示改设备中性。移动端不在支持范围(宿主 dsh 自身未做)——记进
docs/TODO.md⛔ 已否决清单,不写 README。
⑥ 语料 / 测试
corpus/qa-defect-rich-columns.md:补「富列 + 列序由表头决定(严重级刻意放最后一列)+ 转义|还原」。此前 5 份缺陷语料全走parseDefects投影 ⇒reproduce/expected/actual/ac/check/criterion与整张columns从未被断言。变异实测:不认转义时只有新语料变红——那份 note 自称「R3-2 转义回归门禁」的旧语料照旧全绿 ⇒ 一条语料断言了什么只能由变异测试回答,不能由它的 note 回答。corpus/tech-blueprint-valid.md/tech-blueprint-unrepairable.md:补extractBlueprint的正常路径与「不可抢救 ⇒ null」,连同既有那条构成合法 / 可抢救 / 不可抢救三态(conformance 24 → 26)。test/locale.test.js新增占位符覆盖门禁(224 处带参调用)。起因是桌面版首跑 journal 里出现字面量host 侧冒烟:{entry} 真浏览器加载后…——模板有{entry}而调用只传了{draw, raf, mut, ms}。同型漏传全仓 2 处(另一处guard.envProgress缺label)。⚠ 参数表必须括号配平地取:实调用常嵌t(..., { n }),用[^}]*会把「已传」误判成「漏传」。
⑦ 宿主兼容核对(dsh 0.2.0-rc.2)
逐项核对:未发现破坏,七条证据(完整版见 CHANGELOG.md 对应节):
tsc --noEmit直接对着 profile 里已装的 0.2.0-rc.2 编译(tsconfig.json的 paths 指向~/.dsh/profiles/node_modules/@deepseek-ai/*)且 0 错;host 源码 12036 行里只有 14 行用: any(0.1%)⇒「类型过」不是靠any糊过去的。- 实际 import 的
@deepseek-ai/*只有 4 个包、每包 1 个符号,运行时导出逐个核对 ⇒ 0 缺失。 dsh.client.inject语义未变。⚠ manifest 类型里那句 "Informational … not Cordis service injection" 指的是对 Cordis 而言,运行时仍用 manifest 的inject排 factory 到达顺序——别把它误读成语义变更。- manifest schema 兼容;新增的
external是可选的,本插件不需要。 cordis.patch.yml的insert:行仍被支持(0.2.0 的新能力是新增「bundle 自列其 rows」)。- 宿主只校验
peerDependencies、不读engines.dsh。 - dsh 侧无
CHANGELOG;docs/upgrade-guide/尚无 0.2.0 篇。
⚠ 以上全是静态核对:运行时(cordis 服务在 activate 时是否仍在、插件能否在桌面版 profile 正常加载)需重启宿主实测。
验证
- 桌面版首次验证:
tf-mur2p4y0-9qkpqh——completed/humanIntervention=false/ 12m04s / input 1.43M / 49 calls。分诊判成 lite(design / scaffold 未经历,符合「小活别上流水线」);三项 host 检查全部执行(接口核对是skip;源文件 0 个 —— 不足两个源文件,属有明确原因的合理 skip,不是静默跳过);ACCEPTANCE.md末行验收结论:✅ 通过,46 条断言 0 失败。 - 全量:
npm test/tsc --noEmit/oxlint --deny-warnings/ conformance 26/26 / instruction-budget(19 条锚点、无孤儿)全绿;web 与 desktop 两个 profile 副本逐字节一致。
English
⬆️ Upgrade notes
- Host window widened to dsh 0.2.0-rc.2 (preview). The range is a union,
">=0.1.7-alpha.1 <0.3.0 || >=0.2.0-rc.1 <0.3.0"— ⚠ the upper bound cannot simply be widened: under semver's rule that a prerelease only matches a range on the same tuple, changing<0.2.0to<0.3.0still fails for0.2.0-rc.1/0.2.0-rc.2(measured with semver 7.7.4). - The desktop app (dsh 0.2.0+) needs its own install: it runs from a separate
desktopprofile with its ownnode_modules. Installed only intoweb, the desktop app will not show the plugin (nothing is broken — that profile simply doesn't have it). The CLI also refuses--profile desktop(managed exclusively by the Electron application) ⇒ install it from the desktop app's own plugin management UI. - Restart
dsh --profile web(or the desktop app) after upgrading. - ⚠ Prompts got longer:
host/prompts/index.tsgrows by 76 lines net (five craft clauses + four disk-path pointers). This repo'sinstruction-budgetgate covers AGENTS.md only, not prompts, so the cost is unquantified (increment is in the hundreds of characters per prompt). - New log key:
log.hostScanTruncated.
① The deliverable scan no longer gets cut off (headline)
Evidence: tf-mupnk8h0-1otbl2 — the workspace held a pnpm .pnpm-store/ with thousands of extension-less blobs, and the old skip list missed it while readdir returns it before AGENTS.md ⇒ the 400-file cap was consumed instantly ⇒ the returned 400 entries contained 0 source files and 0 HTML ⇒ all four host checks (load / interface / smoke / evidence observation) silently skipped and the journal read as "the delivery contains nothing" — while the delivery was intact (28 files / 13 sources / 1 index.html). It surfaced only when the user found the demo page's slider un-clickable in the browser.
Fix: scanDeliverableFiles(root, limit) returns { files, truncated, limit } and never walks dot-directories (they are never deliverables and are the usual cap-eaters); listDeliverableFiles becomes a thin wrapper (same signature, same behaviour). If any of the three checks reports truncated, a log.hostScanTruncated retraction line is appended.
⚠ Wording matters: the three "not executed / 0 source files / no-html" lines are kept verbatim (the checks genuinely didn't run) — the retraction is a following warn that names them as not facts ⇒ **order is pa...
v0.2.5
TeamFlow v0.2.5
Release — 「编排可测 + 交付质量可客观判定」版本(19 个新提交 / 49 个文件 / +2484 −719 行,v0.2.4..HEAD 全量对比):① 把 1646 行的 executePipeline 拆成阶段模块、host/core 按职责分目录,并让真实编排行为可以被测试驱动;② 补「接口契约」三层与「跑得起来」要求;③ 新增三条 host 侧客观检查(能否打开 / 接口对不对 / 有没有真动)—— 它们不依赖模型能力,因此对本插件面向的任意模型(含本地小模型)都成立。无破坏性,宿主窗口不变。
Release — "testable orchestration + objectively verifiable delivery" (19 new commits / 49 files / +2484 −719, full v0.2.4..HEAD diff): ① splits the 1646-line executePipeline into stage modules, groups host/core by responsibility, and makes real orchestration behavior testable; ② adds the three-layer interface contract and the "must actually run" requirement; ③ adds three host-side objective checks (does it open / are interfaces consistent / does it actually move) — all independent of model capability, so they hold for any model this plugin is used with, including small local ones. No breaking changes; the host window is unchanged.
中文
升级要点(TL;DR)
- 无破坏性:宿主窗口不变(
>=0.1.7-alpha.1 <0.2.0);journal 格式、任务夹契约、teamflow_*工具参数全部向后兼容。 - 升完重启
dsh --profile web生效(本版改动包含 host 运行时代码)。 - 新增的 host 侧检查全部只记录、不阻断(warn/info 进 journal 日志),处于观察期,用来积累真实样本。
- 交付侧多了一处成本:架构蓝图(
api签名)现在也会注入 QA / 验收的上下文,单次约 +1.4k tokens。
为什么做这三件事
都来自实测事故,不是设计偏好:
- 编排不可测:
executePipeline单函数 1646 行,dev/qa/acceptance 揉在一起,改一处无法验证「行为没变」。 - 交付质量只能靠模型自觉:贪吃蛇那次产出的
main.js调用了GameEngine上不存在的move()/render(),首帧 TypeError 直接死机,而「文件存在 / 有 export」级别的检查全绿;另一次是把循环判定写反(lastTime - timestamp),不抛异常、控制台干净、画面全黑,QA 三轮全放行。 - 约束能不能生效取决于模型:同一个「请在报告里给出命令 + 退出码」的要求,强模型自发就做到了,弱模型三次全部做不到 ⇒ 靠模型自律的门禁只对一部分用户有效。
本次变更
① 编排可测:拆分与分目录
executePipeline1646 → 1159 行:dev / qa / acceptance 三块整体外移到host/core/stages/,函数体按行切片搬运、一字未改;共享状态装进PipelineCtx(三兄弟用import type反向引类型 ⇒ 运行时无回边、无循环依赖)。host/core按职责分组:stages/agent/workspace/domain/,pipeline/triage/locale/report留根。- 新增
test/orchestration.test.js:stub 子代理驱动真实executePipeline,覆盖档位护栏 / 波次调度 / 澄清闸门 / 提测门禁 / 取消 / 终态归一等 17 个场景。
② 交付质量:契约三层 + 跑得起来
- 接口契约三层:蓝图必须给
api精确签名 → dev 必须逐字调用、禁止自创成员名 → QA 必须做静态接口一致性核对(不匹配 = P1)。 - 冒烟运行(0a2):交付必须被证明「主路径产生可观测状态变化」,存在性不是证据;无浏览器时用 DOM/Canvas 桩但必须保留真实定时器语义。
- 运行证据探针(warn-only):交付含可执行入口时,QA 报告应给出「命令 + 结果」类证据。
- 措辞修正:明确「不得强行执行不该执行的交付」—— 需编译 / 依赖服务 / 纯库的形态走等价检查。
③ host 侧三条客观检查(与模型能力无关)
| 检查 | 判什么 | 实证 |
|---|---|---|
| 加载检查 | 真浏览器加载,抓未捕获异常(抓「打开就崩」) | 子代理起不了浏览器(受限令牌下 Chromium crashpad 恒失败),这件事只能 host 做 |
| 接口核对 | 解析产物真实导出 vs 跨模块调用点 | 同一把尺子:事故 run 命中 5 处(engine.move 等),健康 run 0 处 |
| 冒烟 | 有 canvas 却从未绘制 = 空转 | 事故 run(黑屏)→ no-motion;三个健康交付 → ok,4/4 判定正确 |
④ 一次公开撤回:浏览器配方
曾基于「本机 headless Chrome 可用」给 QA 写了启动配方(含 --no-sandbox)。用户在 dsh 宿主实测证明:加不加 --no-sandbox 输出逐字节相同,都失败 —— --no-sandbox 关的是 Chromium 自己的沙箱,管不到宿主沙箱对 crashpad 跨进程 OpenProcess 的拒绝。配方已删除,改为写明「agent 沙箱内不可用 + 具体证据 + 不要再试参数组合」。
⑤ 修两个真 bug
- 两项 host 检查静默空转:误用
root(=options.productRoot) 当文件系统路径,而它工作区模式下恒为 null(连查 5 个 run)⇒ 检查从未执行且 journal 零痕迹。改为经fsRootOf()解析,并让每种结局都留痕。 - 蓝图收不到:
stateSliceFor的角色白名单只有arch/tech/dev,而qaPrompt与acceptancePrompt都写着「若注入蓝图则依它核对」⇒ 那两句恒为假。已补qa/acceptance。
验证
- 30 个测试套件全绿,断言 2177 条(v0.2.4 为 2013)。
tsc0 错;oxlint --deny-warnings0 警告 0 错误(73 文件)。- CI(ubuntu-latest)全绿;host 冒烟在 Linux 上真跑了(不是环境跳过)。
- 编排测试对拆分前 / 拆分后两份代码各跑一遍均全绿 ⇒ 锁的是行为而非结构。
已知边界
- 三条 host 检查纯记录、不参与判定;判据(尤其冒烟的「有 canvas 却零绘制」)需更多真实样本校准后才考虑升级。
- 冒烟依赖本机 Chromium 可启动;起不来即
skip,绝不因此影响流水线。 - 接口核对是内部一致性(调用点 vs 产物导出);「蓝图声明 vs 产物实现」的契约一致性尚未覆盖。
- 缺陷闭环目前只有「整轮干净」的批量核销,没有逐条核销(已知问题,留待后续)。
English
Upgrade notes (TL;DR)
- No breaking changes: host window unchanged (
>=0.1.7-alpha.1 <0.2.0); journal format, task-folder contract andteamflow_*tool arguments all remain backward compatible. - Restart
dsh --profile webafter upgrading (this release touches host runtime code). - Every new host-side check is record-only and non-blocking (warn/info in the journal log) — it is in an observation period to collect real samples.
- One added cost: the architecture blueprint (
apisignatures) is now also injected into QA / acceptance context, roughly +1.4k tokens per call.
Why these three themes
All trace back to observed failures, not preferences:
- Orchestration was untestable: a single 1646-line
executePipelinemixing dev/qa/acceptance — there was no way to prove a change preserved behavior. - Delivery quality depended on the model's discipline: a snake game shipped a
main.jscallingmove()/render()that did not exist onGameEngine— instant TypeError, while "file exists / has exports" checks were all green. Another run inverted a loop comparison (lastTime - timestamp): no exception, clean console, black screen, and QA signed off across three rounds. - Whether a constraint works depends on the model: the same "record the command and its exit code" requirement was met spontaneously by a strong model and missed three times out of three by a weak one ⇒ model-discipline gates only work for part of the audience.
Changes
① Testable orchestration
executePipeline1646 → 1159 lines: dev/qa/acceptance moved tohost/core/stages/, bodies transferred line-by-line, unmodified; shared state collected intoPipelineCtx(the three modules import the type back withimport type, so there is no runtime back-edge).host/coregrouped by responsibility:stages/agent/workspace/domain/.- New
test/orchestration.test.js: stub subagents driving the realexecutePipelineacross 17 scenarios.
② Delivery quality: contract + "it must run"
- Three-layer interface contract: the blueprint must declare exact
apisignatures → dev must call them verbatim → QA must statically verify cross-module consistency (mismatch = P1). - Smoke run (0a2): the delivery must be shown to produce observable state change; existence is not evidence. Without a browser, use DOM/Canvas stubs but keep real timer semantics.
- Verification-evidence probe (warn-only) and a wording fix: never force-launch something not meant to be launched.
③ Three host-side objective checks (model-independent)
| Check | What it decides | Evidence |
|---|---|---|
| Load check | real-browser load, captures uncaught errors ("dead on arrival") | subagents cannot launch a browser at all (restricted token ⇒ Chromium crashpad always fails) |
| Interface check | real exports vs cross-module call sites | same ruler: 5 hits on the incident run (engine.move etc.), 0 on healthy ones |
| Smoke | canvas present but never drawn = inert | incident run (black screen) → no-motion; three healthy deliveries → ok — 4/4 correct |
④ One public retraction: the browser recipe
A launch recipe (including --no-sandbox) was added for QA based on "headless Chrome works here". The user's probe inside the dsh host proved identical byte-for-byte failure with and without --no-sandbox — that flag disables Chromium's own sandbox, not the host sandbox that denies crashpad's cross-process OpenProcess. The recipe was removed and replaced with "not launchable inside the agent sandbox + concrete evidence + do not retry flag combinations".
⑤ Two real bugs fixed
- Two host checks were silently no-ops:
root(=options.productRoot) was used as a filesystem path, but it is always null in workspace mode (checked across 5 runs) ⇒ the checks never ran and left no trace. Now resolved viafsRootOf(), and every outcome is logged. - The blueprint never reached QA/acceptance:
stateSliceFor's role whitelist had onlyarch/tech/dev, while bothqaPromptandacceptancePromptsaid "if the injected blueprint JSON is present…" ⇒ those two clauses were always false.qa/acceptanceadded.
Verification
- 30 test suites green, 2177 assertions (v0.2.4 had 2013).
tscclean;oxlint --deny-warnings0 warnings / 0 errors across 73 files.- CI (ubuntu-latest) green; the host smoke check actually ran on Linux (not skipped for environment).
- The orchestration suite passes against both pre-split and post-split code ⇒ it locks behavior, not structure.
Known limits
- All three host checks are record-only and non-blocking; thresholds (especially "canvas present but zero draws") need more real samples before being promoted.
- Smoke depends on a launchable local Chromium; otherwise it reports
skipand never affects the pipeline. - The interface check covers internal consistency (call sites vs exports); contract consistency (blueprint declaration vs implementation) is not covered yet.
- Defect closure is still all-or-nothing per clean round; per-defect closure is a known gap.
v0.2.4
TeamFlow v0.2.4
Release — 一次「工程基建」版本(5 个提交 / 41 个文件 / +1757 −1062 行,v0.2.3..HEAD 全量对比):把代码风格从「人工维持 + smoke 正则兜底」升级为「机器维持 + 单一格式仲裁者」,对齐宿主 dsh 的同款工具链(oxlint jsPlugins + @stylistic),并加 lefthook 预提交钩子让贡献者提交即合规。零功能性变更、零用户可见行为变化、无破坏性,宿主窗口不变。
Release — an "engineering hygiene" release (5 commits / 41 files / +1757 −1062, full v0.2.3..HEAD diff): moves code style from "human-maintained + smoke-regex backstop" to "machine-maintained + single formatting authority", aligning with the host dsh's own toolchain (oxlint jsPlugins + @stylistic), and adds a lefthook pre-commit hook so contributor commits are compliant by default. No functional changes, no user-visible behavior changes, no breaking changes; the host window is unchanged.
中文
升级要点(TL;DR)
- 无破坏性:宿主窗口不变(
>=0.1.7-alpha.1 <0.2.0);journal 格式、任务夹契约、teamflow_*工具参数全部向后兼容。 - 升完重启
dsh --profile web生效。 - 纯工程基建:确立代码格式单一仲裁者(
@stylistic/indent,不引入 Prettier)、行长上限对齐宿主收严到 140、加 lefthook 预提交钩子自动修复 staged 文件、风格门禁改走宿主同款@stylistic(oxlint jsPlugins)。
为什么做这套基建:三种漂移
本版的每一组改动都来自「风格不收敛」导致的实际摩擦:
- 格式无单一权威:此前缩进靠
@stylistic/indent之外的手工约定,AI 生成代码与贡献者提交在缩进/分行上易漂移;Prettier 与@stylistic/indent在 JSX 缩进上互斥,不能混用。 - 行长无统一上限:各文件行宽不一,diff 噪声大,跨仓(与宿主 dsh)协作时认知成本高。
- 合规靠事后门禁:只有 oxlint 的 warning 门禁,没有「提交即修」的钩子,不合规范的代码会先溜进历史再被修。
本次变更
① 代码格式单一仲裁者(规范 docs/anchors/code-style.md)
- 确立
@stylistic/indent为唯一格式权威,明确不引入 Prettier(两者在 JSX 缩进上互斥)。 - 规范文件落地两条纪律:① 规则准入要么可自动修、要么存量清零;② 源码断言锁(smoke 正则)只锁内容不锁排版(跨行分隔符写
\s*、容忍尾逗号,?),避免每次折行都需改断言。
② max-len 收严到 140(对齐宿主)
- 全仓 53 处超长行手工折行(不改语义);3 条长正则(失败分类 / 内容失败 / 安装块)拆成语义分组数组
new RegExp([...].join('|'), 'i'),并对每个拆分做等价性校验(.source必须等于原始字面量),disable计数归零——不靠eslint-disable逃避。
③ lefthook 预提交钩子(贡献者提交即合规)
pnpm format(两遍--fix)+git diff --cached --check空白检查,stage_fixed: true;pnpm-workspace.yaml加allowBuilds: lefthook: true,package.json加postinstall自动安装钩子。
④ 风格门禁改走宿主同款 @stylistic(oxlint jsPlugins)
- oxlint
jsPlugins加载@stylistic/eslint-plugin,103 规则、0 warning 门禁;因 jsPlugins 仍 alpha 不随 semver,oxlint锁精确版本1.85.0。 - 三层缩进兜底:
@stylistic/indent规则 +pnpm format自动修 +.editorconfig编辑器侧约定,兜住 lint 管不到的缩进。
验证方式
| 验证项 | 结果 |
|---|---|
| oxlint 门禁 | oxlint --deny-warnings 0/0 @ 103 规则(含 jsPlugins 加载成功) |
| 类型检查 | tsc --noEmit 0 错 |
| 测试套件 | 28 套件全绿(含 instruction-budget / changelog / prompt-contract / conformance / client-host-api / runs-eviction) |
| lefthook 预提交 | 实跑通过(pnpm format 两遍 --fix + 空白检查),自动修复 staged 文件 |
| 提交面 | 5 提交 / 41 文件 / +1757 −1062,全 chore/lint/hooks,零 .ts 逻辑改动 |
| 真实流水线回归 | 宿主下限不变,旧 journal / 续跑行为不受影响(纯工程改动,无需重跑验收) |
已知边界与后续
- 折行仍靠手工:max-len 目前「只报警不自动修」的自动化未做,已在
docs/TODO.md留痕(低优先)。 - smoke 源码断言锁已明确为补充防线(只锁内容不锁排版),主防线是 oxlint + lefthook。
- 计量维持到 token 量为止:成本「从量到钱」已拍板不做(每模型单价不同、会调价、plan/套餐式下「token×单价」算式不成立)。
- 下一版 v0.2.5 = 编排可测:pipeline 拆分成 qa(状态机)/ dev(波次)/ acceptance 后,用 fake subagents 做端到端编排测试,仍是当前最大测试缺口。
engines.dsh窗口维持>=0.1.7-alpha.1 <0.2.0(semver 不覆盖 prerelease 的口径不变,按正式版边界书写)。
English
Upgrade notes (TL;DR)
- No breaking changes: host window unchanged (
>=0.1.7-alpha.1 <0.2.0); journal format, task-folder contract andteamflow_*tool arguments are all backward compatible. - Restart
dsh --profile webafter upgrading. - Pure engineering hygiene: a single formatting authority (
@stylistic/indent, no Prettier), line-length cap tightened to 140 to match the host, a lefthook pre-commit hook that auto-fixes staged files, and the lint gate now using the host's own@stylistic(oxlint jsPlugins).
Why this hygiene pass: three sources of drift
Every change group here comes from friction caused by non-convergent style:
- No single formatting authority: indentation relied on hand-written conventions outside
@stylistic/indent; AI-generated code and contributor commits drifted on indentation/wrapping. Prettier and@stylistic/indentare mutually exclusive on JSX indentation and cannot be mixed. - No unified line-length cap: inconsistent line widths created diff noise and high cognitive cost when collaborating across repos (with the host dsh).
- Compliance was post-hoc only: there was an oxlint warning gate but no "fix on commit" hook, so non-conforming code entered history first and got fixed later.
Changes
① Single formatting authority (spec docs/anchors/code-style.md)
@stylistic/indentis the sole formatting authority; Prettier is explicitly not introduced (the two are mutually exclusive on JSX indentation).- The spec pins two disciplines: ① a rule is admitted only if it is auto-fixable or its existing violations are cleared; ② source-assertion locks (smoke regexes) lock content, not formatting (cross-line separators use
\s*, trailing commas tolerate,?), so every wrap does not require rewriting an assertion.
② Line-length cap tightened to 140 (host-aligned)
- All 53 over-long lines across the repo were hand-wrapped (semantics untouched); 3 long regexes (external-failure / content-failure / install-block) were split into semantic grouped arrays
new RegExp([...].join('|'), 'i'), each split equivalence-checked (.sourcemust equal the original literal). Thedisablecount dropped to zero — no escape viaeslint-disable.
③ lefthook pre-commit hook (contributor-commits-compliant)
pnpm format(two--fixpasses) +git diff --cached --checkwhitespace check,stage_fixed: true;pnpm-workspace.yamlgainsallowBuilds: lefthook: true;package.jsongains apostinstallthat installs the hook automatically.
④ Lint gate now uses the host's own @stylistic (oxlint jsPlugins)
- oxlint
jsPluginsloads@stylistic/eslint-pluginat 103 rules with a 0-warning gate; because jsPlugins is still alpha and not semver-tracked,oxlintis pinned to exact1.85.0. - Three-layer indentation backstop: the
@stylistic/indentrule +pnpm formatauto-fix +.editorconfigeditor-side convention, covering indentation that lint alone cannot reach.
Verification
| Item | Result |
|---|---|
| oxlint gate | oxlint --deny-warnings 0/0 @ 103 rules (jsPlugins loaded) |
| Type check | tsc --noEmit 0 errors |
| Test suites | 28 suites green (incl. instruction-budget / changelog / prompt-contract / conformance / client-host-api / runs-eviction) |
| lefthook pre-commit | ran live (pnpm format two --fix passes + whitespace check), auto-fixed staged files |
| Commit surface | 5 commits / 41 files / +1757 −1062, all chore/lint/hooks, zero .ts logic changes |
| Real-pipeline regression | host floor unchanged; old journals / resume behavior unaffected (pure engineering change, no re-verification run needed) |
Known boundaries and follow-ups
- Wrapping is still manual: max-len currently "warns but does not auto-fix"; that automation is filed in
docs/TODO.md(low priority). - smoke source-assertion locks are now explicitly a backstop (content-only, not formatting); the primary gate is oxlint + lefthook.
- Metering stops at token volume: cost "from volume to money" was decided against (per-model prices differ, prices change, and "token × unit price" does not hold under plan/subscription billing).
- Next version v0.2.5 = orchestration testability: after splitting pipeline into qa (state machine) / dev (waves) / acceptance, add end-to-end orchestration tests with fake subagents — still the largest test gap.
engines.dshwindow stays>=0.1.7-alpha.1 <0.2.0(semver does not cover prereleases; documented against official-release boundaries).