@yangdcm/dsh-expert-team 1.3.12 → 1.3.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,34 @@
3
3
  本包遵循[语义化版本](https://semver.org/lang/zh-CN/)。dsh 宿主版本线的对应关系写在
4
4
  `package.json` 的 `engines.dsh` 与 `dsh.compatibility` 里,插件市场按它判断"这个插件跟你的宿主兼不兼容"。
5
5
 
6
+ ## 1.3.13
7
+
8
+ **渐进式状态:`/state` 支持 `?section=`,首屏与会话体量解耦**(性能修复 #3)
9
+
10
+ - 背景(真机实测,`DSH_EXPERT_TEAM_STATE_PROFILE=1` 分步):完整负载里 `subs` **1 327–1 388 ms**、
11
+ `roles` **638–869 ms**、`runs+select` 15–21 ms、`tail` 3 ms;重会话(20+ 子代理、仍在活跃写日志)
12
+ 总耗时 **2.1–6.2 s**,成本随「子代理数 × 日志体量」线性增长。
13
+ - 新增 `?section=summary|people|feed|artifacts`(逗号可多选;**缺省或 **ALL** = 完整负载、字段语义不变**
14
+ ⇒ 老客户端/脚本/既有测试不受影响):
15
+ - `summary`(首屏用):阶段/进度/计数/违规/告警 —— 真机 `snapshotRun` 中位 **0.3 ms**;
16
+ - `people`:子会话清单/角色/成员时间线(最贵的那两块就在这里);
17
+ - `feed`:每 agent 最近事件;`artifacts`:`RUN.log` 尾 + 工作区改动文件。
18
+ - **上限 + 如实降级**:角色解析硬上限 `DSH_EXPERT_TEAM_MAX_ROLE_SUBS`(默认 60;超出部分如实进
19
+ `degraded: "roles:<n>"`);people 软期限 `DSH_EXPERT_TEAM_PEOPLE_DEADLINE_MS`(默认 2500 ms;超时跳过
20
+ workflow 元数据并记 `wf:deadline`)。响应新增 `sections`/`degraded`/`degradedReason`(缺块 ≠ 空数据)。
21
+ - **副作用修正**:`summary` 路径不再触发「派工即登记」写 `STATE.json`(原先每轮询都可能因空 `subById`
22
+ 算出"清空成员"而覆写工件)⇒ **摘要轮询是纯读**。
23
+ - 客户端:**一快一慢双订阅**(`summary` 按 `pollMs`;`people,feed,artifacts` ≥ 6 s)+
24
+ `mergeStatePayload` **合并**(摘要那一拍不再抹掉已知成员);`stateHub` 改为**按 URL 各自计时**
25
+ (`stateHubBaseOf/DueAt`,tick 只发已到期的 URL)—— 旧实现"全局取最小 base、每 tick 拉全部 URL"
26
+ 正是"慢端点被快钟拖着跑"的根源。
27
+ - 口径对齐(同批):`templates` 常量与两个 e2e 的"run 必需文件"清单**语义不同、关系确定**,
28
+ 由 `artifact-ownership.test.mjs` 的 **E 断言**钉死(常量 ≡ 模板目录;e2e 清单 ≡ 常量 ∪ `RUN.log.md`);
29
+ README/README.en/llms 写实 `METRICS.md` 的来源与位置(**`team/` 根的聚合快照**,`/team learn` 产出),
30
+ 并补一句 **R1 工件归属**(工件由产出它的角色自己写;lead 只读门控)。
31
+ - 验证:`npm run test:all` EXIT=0(**82 个测试文件**;新增 `state-sections.test.mjs`);
32
+ 变异目录随源码文本**同提交**更新(`M70` 的 `find` 串)。
33
+
6
34
  ## 1.3.12
7
35
 
8
36
  **面向"可被 AI 检索与引用"的文档与元数据补齐**(纯文档/元数据,代码与 1.3.11 完全相同)。
package/README.en.md CHANGED
@@ -90,9 +90,10 @@ $ /team build a payments module with login
90
90
  ├─ implement backend … fan out along the DAG → edits your workspace directly
91
91
  ├─ review sec·reviewer independent review (cross-model) → REVIEW.md
92
92
  ├─ test qa repro, coverage, regressions → TEST.md
93
- └─ deliver docs wrap-up and cost → SUMMARY.md · METRICS.md
93
+ └─ deliver docs wrap-up → SUMMARY.md
94
94
 
95
95
  Persisted throughout: TASKS.json · ROSTER.json · STATE.json · AUTHORITY.md · RUN.log.md
96
+ Aggregate snapshot: `METRICS.md` lives at the **`team/` root** (produced by `/team learn`; not a run-dir template)
96
97
  On disk at: <your workspace>/team/<run-id>/
97
98
  ```
98
99
 
@@ -358,7 +359,10 @@ Full command list (`/team codeindex` code index, `/team limit` quotas, `/team se
358
359
 
359
360
  **Phases (9)**: `clarify` → `research` → `design` → `spec-review` (**hard gate**) → `方案确认` plan approval (**approval gate**, on by default, can be turned off) → `implement` → `review` → `test` → `deliver`. (Ids and Chinese labels come from the single source `lib/vocab.js`.)
360
361
 
361
- **Key artifacts**: `SPEC.md` (spec and boundaries) · `RESEARCH.md` · `PLAN.md` · `TASKS.json` (task ledger) · `ROSTER.json` (staffing) · `STATE.json` · `AUTHORITY.md` (single source for write authority) · `REVIEW.md` · `TEST.md` · `SUMMARY.md` · `METRICS.md` (cost and timing) · `RUN.log.md`
362
+ **Key artifacts**: `SPEC.md` (spec and boundaries) · `RESEARCH.md` · `PLAN.md` · `TASKS.json` (task ledger) · `ROSTER.json` (staffing) · `STATE.json` · `AUTHORITY.md` (single source for write authority) · `REVIEW.md` · `TEST.md` · `SUMMARY.md` · `METRICS.md` (cost and timing — an **aggregate snapshot at the `team/` root**, produced by `/team learn`) · `RUN.log.md`
363
+
364
+
365
+ **Who writes**: each artifact is written **by the role that produces it** into `<run-dir>/`; the lead **only reads artifacts to gate and decide** (it has no `write` tool). `STATE.json` and `RUN.log.md` are maintained **by the runtime**.
362
366
 
363
367
  ## Layout
364
368
 
package/README.md CHANGED
@@ -84,9 +84,10 @@ $ /team 做一个带登录的支付模块
84
84
  ├─ 实现 backend … 按依赖 DAG 并行扇出 → 直接改你工作区的代码
85
85
  ├─ 审查 sec·reviewer 独立评审(换模型交叉验证) → REVIEW.md
86
86
  ├─ 测试 qa 复现、覆盖、回归 → TEST.md
87
- └─ 交付 docs 收尾结论与成本 → SUMMARY.md · METRICS.md
87
+ └─ 交付 docs 收尾结论 → SUMMARY.md
88
88
 
89
89
  全程留痕:TASKS.json · ROSTER.json · STATE.json · AUTHORITY.md · RUN.log.md
90
+ 聚合快照:`METRICS.md` 在 **`team/` 根**(`/team learn` 产出;不是 run 目录模板)
90
91
  落盘位置:<你的工作区>/team/<run-id>/
91
92
  ```
92
93
 
@@ -317,7 +318,10 @@ dsh plugin --profile web add dshmarket
317
318
 
318
319
  **阶段(9)**:`clarify` 澄清 → `research` 调研 → `design` 设计 → `spec-review` 规格评审(**硬门**)→ `方案确认` 方案确认(**确认门**,默认开、可关)→ `implement` 实现 → `review` 审查 → `test` 测试 → `deliver` 交付。(id 与中文名的唯一真源是 `lib/vocab.js`。)
319
320
 
320
- **主要工件**:`SPEC.md`(规格与边界)· `RESEARCH.md`(调研)· `PLAN.md`(方案)· `TASKS.json`(任务台账)· `ROSTER.json`(编制)· `STATE.json`(状态)· `AUTHORITY.md`(写入权限单源)· `REVIEW.md`(评审)· `TEST.md`(测试)· `SUMMARY.md`(交付总结)· `METRICS.md`(成本与耗时)· `RUN.log.md`(运行日志)
321
+ **主要工件**:`SPEC.md`(规格与边界)· `RESEARCH.md`(调研)· `PLAN.md`(方案)· `TASKS.json`(任务台账)· `ROSTER.json`(编制)· `STATE.json`(状态)· `AUTHORITY.md`(写入权限单源)· `REVIEW.md`(评审)· `TEST.md`(测试)· `SUMMARY.md`(交付总结)· `METRICS.md`(成本与耗时:**`team/` 根的聚合快照**,`/team learn` 产出)· `RUN.log.md`(运行日志)
322
+
323
+
324
+ **谁写**:每个工件由**产出它的角色**自己 `write` 进 `<run-dir>/`;lead **只读工件做门控与裁决**(它没有 `write` 工具)。`STATE.json` 与 `RUN.log.md` 由**运行时**维护。
321
325
 
322
326
  ## 插件结构
323
327
 
package/client.js CHANGED
@@ -1343,10 +1343,13 @@ window.__ModuleLoader__.load({
1343
1343
  var planMsgS = useState(''); var planMsg = planMsgS[0], setPlanMsg = planMsgS[1]
1344
1344
  var err = useState(''); var errV = err[0], setErr = err[1]
1345
1345
 
1346
- function stateUrl() {
1346
+ function stateUrl(section) {
1347
1347
  var url = '/plugins/dsh-expert-team/state', q = []
1348
1348
  if (sessionId) q.push('sessionId=' + encodeURIComponent(sessionId))
1349
1349
  if (selRunV && selRunV.workspace && selRunV.runId) { q.push('workspace=' + encodeURIComponent(selRunV.workspace)); q.push('run=' + encodeURIComponent(selRunV.runId)) }
1350
+ // `section`:渐进式状态(2026-09-15 性能修复 #3)。首屏只发 `summary`(便宜),
1351
+ // 人/料/事件流晚一拍、低频拉(见下面的双订阅)。不传 = 完整负载(服务端向后兼容)。
1352
+ if (section) q.push('section=' + encodeURIComponent(section))
1350
1353
  return url + (q.length ? ('?' + q.join('&')) : '')
1351
1354
  }
1352
1355
  // ── 数据到达后的副作用(**本组件不再自带轮询器**;由 stateHub 统一驱动)─────────
@@ -1354,9 +1357,22 @@ window.__ModuleLoader__.load({
1354
1357
  // 画布另走 liveStore 的 setInterval ⇒ 画布一开就有 2–3 个轮询并发压同一个重端点
1355
1358
  // (实测 10 秒 7 发、6 次重叠、单发被拖到 5.9/8.3 s)。现在全部经 stateHub:
1356
1359
  // 同 URL 同刻只跑一次、全局单一时钟、统一退避(见 stateHub 的注释)。
1360
+ // ── 分节负载的合并(2026-09-15 性能修复 #3)────────────────────────────────
1361
+ // 为什么必须合并而不是替换:首屏只发 `?section=summary`(便宜:阶段/进度/计数),
1362
+ // "人/料/事件流"晚一拍才到。若直接 `setData(d)`,摘要那一拍会把上一拍已知的成员/事件流
1363
+ // **抹掉**(UI 闪空、通知误判"成员消失")。规则:**新负载里有的键覆盖旧值,没有的键保留**
1364
+ // —— 缺块 ≠ 空数据;`sections` 字段如实告诉 UI"这份负载包含哪些块"(两种零可区分)。
1365
+ function mergeStatePayload(prev, d) {
1366
+ if (!d || typeof d !== 'object') return d
1367
+ if (!prev || typeof prev !== 'object' || prev.ok !== true) return d
1368
+ var out = {}, k
1369
+ for (k in prev) { if (Object.prototype.hasOwnProperty.call(prev, k)) out[k] = prev[k] }
1370
+ for (k in d) { if (Object.prototype.hasOwnProperty.call(d, k)) out[k] = d[k] }
1371
+ return out
1372
+ }
1357
1373
  function onState(d) {
1358
1374
  if (d && d.ok) {
1359
- setData(d); setErr(''); reschedule(d)
1375
+ setData(function (prev) { return mergeStatePayload(prev, d) }); setErr(''); reschedule(d)
1360
1376
  // N2: keep the composer takeover in sync with pendingDecision
1361
1377
  // 批 0-2:把 /state 已经返回的 runId/workspace 一并传出,供横幅拍板时回传给 /decide
1362
1378
  try { if (exports._syncPending) exports._syncPending(d.pendingDecision, sessionId, d.runId, d.workspace) } catch (e) {}
@@ -1391,13 +1407,18 @@ window.__ModuleLoader__.load({
1391
1407
  }
1392
1408
  useEffect(function () {
1393
1409
  if (!isOpen && !viewMode) return undefined // panel closed & not canvas → no polling
1394
- var off = stateHubSubscribe(stateUrl(), dispCfg.pollMs, onState)
1395
- // catch up instantly when the user returns to the tab / window
1396
- var onVis = function () { if (document.visibilityState === 'visible') stateHubFetch(stateUrl(), onState) }
1397
- var onFocus = function () { stateHubFetch(stateUrl(), onState) }
1410
+ // **双订阅**(性能修复 #3):`summary` 便宜(阶段/进度/计数)⇒ 按 `pollMs` 快拉,
1411
+ // 首屏立刻有内容;`people,feed,artifacts` 贵(成员解析/事件流/日志尾+git)⇒ 至少 6 秒一次,
1412
+ // 到达后由 `mergeStatePayload` 合并进同一份 data(**不会**把摘要拍掉的字段抹掉)。
1413
+ // stateHub URL 各自计时(per-URL due),所以"一快一慢"不会互相拖。
1414
+ var off = stateHubSubscribe(stateUrl('summary'), dispCfg.pollMs, onState)
1415
+ var offDetail = stateHubSubscribe(stateUrl('people,feed,artifacts'), Math.max(dispCfg.pollMs, 6000), onState)
1416
+ // catch up instantly when the user returns to the tab / window(只补便宜那一份)
1417
+ var onVis = function () { if (document.visibilityState === 'visible') stateHubFetch(stateUrl('summary'), onState) }
1418
+ var onFocus = function () { stateHubFetch(stateUrl('summary'), onState) }
1398
1419
  document.addEventListener('visibilitychange', onVis)
1399
1420
  window.addEventListener('focus', onFocus)
1400
- return function () { off(); document.removeEventListener('visibilitychange', onVis); window.removeEventListener('focus', onFocus) }
1421
+ return function () { off(); offDetail(); document.removeEventListener('visibilitychange', onVis); window.removeEventListener('focus', onFocus) }
1401
1422
  }, [sessionId, selRunV, isOpen, viewMode, dispCfg.pollMs])
1402
1423
 
1403
1424
  // Make the docked panel reserve space (shift the shell frame) instead of
@@ -2350,23 +2371,32 @@ window.__ModuleLoader__.load({
2350
2371
  // ③ 间隔 = clamp(max(基础间隔, 上次耗时×2), 基础间隔, 30000);
2351
2372
  // 团队有人在跑时基础间隔按 40% 加速(面板原有意图,现在对徽章/画布同样生效)。
2352
2373
  // 纪律不变:**未回绝不发下一个**(晚一点看到状态,好过把 web 拖死)。
2353
- var stateHub = { inflight: {}, lastMs: {}, subs: [], timer: 0, busy: false }
2374
+ var stateHub = { inflight: {}, lastMs: {}, lastAt: {}, subs: [], timer: 0, busy: false }
2354
2375
  function stateHubNow() { return (window.performance && performance.now) ? performance.now() : Date.now() }
2355
2376
  function stateHubUrls() { var out = []; stateHub.subs.forEach(function (x) { if (out.indexOf(x.url) < 0) out.push(x.url) }) ; return out }
2356
- function stateHubBase() {
2377
+ // 每个 URL 的**自己的**基础间隔(同一 URL 有多个订阅者时取最小 ⇒ 谁最急听谁的)。
2378
+ // 性能修复 #3 的"一快一慢"就靠它:`summary` 3 s 一发、`people,feed,artifacts` ≥ 6 s 一发,
2379
+ // 两者互不拖拽(旧实现是"全局取最小 base、每个 tick 把所有 URL 都拉一遍" ⇒ 慢的那份被快钟拖着跑)。
2380
+ function stateHubBaseOf(u) {
2357
2381
  var base = 0
2358
- stateHub.subs.forEach(function (x) { if (!base || x.base < base) base = x.base })
2382
+ stateHub.subs.forEach(function (x) { if (x.url === u && (!base || x.base < base)) base = x.base })
2359
2383
  if (!base) base = 3000
2360
2384
  return stateHub.busy ? Math.max(300, Math.round(base * 0.4)) : base
2361
2385
  }
2386
+ function stateHubDueAt(u) { return (stateHub.lastAt[u] || 0) + stateHubBaseOf(u) }
2362
2387
  function stateHubSchedule() {
2363
2388
  clearInterval(stateHub.timer)
2364
2389
  stateHub.timer = 0
2365
2390
  if (!stateHub.subs.length) return
2366
2391
  var slowest = 0
2367
2392
  stateHubUrls().forEach(function (u) { if ((stateHub.lastMs[u] || 0) > slowest) slowest = stateHub.lastMs[u] })
2368
- var base = stateHubBase()
2369
- var next = Math.max(base, Math.min(30000, Math.round(slowest * 2)))
2393
+ var now = stateHubNow(), earliest = Infinity
2394
+ stateHubUrls().forEach(function (u) { var at = stateHubDueAt(u); if (at < earliest) earliest = at })
2395
+ if (!isFinite(earliest)) earliest = now + 3000
2396
+ // 基础等待 = 最早到期的那一刻;再按"最慢一次请求 ×2"放大(未回绝不发下一个的纪律仍由
2397
+ // in-flight 守卫保证,这里的放大只是为了别把慢端点压成队列)。
2398
+ var wait = Math.max(200, Math.round(earliest - now))
2399
+ var next = Math.max(wait, Math.min(30000, Math.round(slowest * 2)))
2370
2400
  stateHub.timer = setInterval(stateHubTick, next)
2371
2401
  }
2372
2402
  function stateHubDeliver(url) {
@@ -2380,6 +2410,8 @@ window.__ModuleLoader__.load({
2380
2410
  var p = stateHub.inflight[url]
2381
2411
  if (p) { p.then(function (d) { try { onData(d) } catch (e) {} }); return } // 同刻同 URL:复用在飞结果
2382
2412
  var t0 = stateHubNow()
2413
+ // 记"上次发出时刻":即使**失败**也记(否则失败会变成热循环重试)。
2414
+ stateHub.lastAt[url] = t0
2383
2415
  p = fetch(url).then(function (r) { return r.ok ? r.json() : null }).then(function (d) {
2384
2416
  delete stateHub.inflight[url]
2385
2417
  stateHub.lastMs[url] = Math.max(0, stateHubNow() - t0)
@@ -2390,7 +2422,9 @@ window.__ModuleLoader__.load({
2390
2422
  }
2391
2423
  function stateHubTick() {
2392
2424
  if (!stateHub.subs.length) { clearInterval(stateHub.timer); stateHub.timer = 0; return }
2393
- stateHubUrls().forEach(function (u) { stateHubFetch(u, stateHubDeliver(u)) })
2425
+ var now = stateHubNow()
2426
+ // **只发改到期的那些 URL**(不是每个 tick 把所有 URL 拉一遍)
2427
+ stateHubUrls().forEach(function (u) { if (stateHubDueAt(u) <= now && !stateHub.inflight[u]) stateHubFetch(u, stateHubDeliver(u)) })
2394
2428
  stateHubSchedule()
2395
2429
  }
2396
2430
  function stateHubSubscribe(url, base, onData) {
@@ -2409,7 +2443,7 @@ window.__ModuleLoader__.load({
2409
2443
  // ── 共享 state 轮询(HeaderButton / LiveCapsule 复用,单例;无订阅者即停)──
2410
2444
  var liveStore = { data: null, sid: '', off: null, subs: new Set() }
2411
2445
  var LIVE_BASE_MS = 2500 // 徽章/画布的基础节奏(与面板的 dispCfg.pollMs 取更小者由 hub 统一决定)
2412
- function liveUrl(sid) { return '/plugins/dsh-expert-team/state?sessionId=' + encodeURIComponent(sid) }
2446
+ function liveUrl(sid) { return '/plugins/dsh-expert-team/state?sessionId=' + encodeURIComponent(sid) + '&section=people,feed' }
2413
2447
  // 手动催一次(子代理刚出现时立刻刷新一次徽章,不必等下一个 tick)
2414
2448
  function liveTick() {
2415
2449
  var sid = liveStore.sid
package/lib/command.js CHANGED
@@ -1144,6 +1144,10 @@ async function scaffoldRun(cwd, mode) {
1144
1144
  // Copy the canonical templates (single source of truth).
1145
1145
  // `AUTHORITY.md`(单源化权威表)随 run 一起发:**空着也有意义** —— 它带着「该怎么写」的说明与校验规则
1146
1146
  // (设计稿 §十二:真实 run 的 64% 返工来自「同一事实多份拷贝 / 多写者」,本表把它提到事前)。
1147
+ // ⚠️ 本常量 = **从 `templates/` 复制出来的文件**(13 项)。它与"run 目录必需文件清单"(两个 e2e
1148
+ // 测试里的 14 项)**不是同一个集合**:那边多一个 `RUN.log.md`(由 run log 写入器创建)、这边就是
1149
+ // 模板全集。两者关系由 `artifact-ownership.test.mjs` 的「工件清单一致」断言钉住,避免下次又被
1150
+ // 当成同一件事去"对齐"(2026-09-15 阶段 C 的口径核查)。
1147
1151
  const templates = ['TASK.md', 'ROSTER.json', 'STATE.json', '任务看板.md', 'SPEC.md', 'PLAN.md', 'RESEARCH.md', 'TASKS.json', 'REVIEW.md', 'TEST.md', 'SUMMARY.md', 'RETRO.md', 'AUTHORITY.md'];
1148
1152
  for (const t of templates) {
1149
1153
  try {
@@ -2781,6 +2785,18 @@ async function listRunsInWorkspace(ws) {
2781
2785
  }
2782
2786
  return out0;
2783
2787
  }
2788
+ // ── `/state?section=` 的解析(纯函数,便于单测)──────────────────────────────
2789
+ // 语义(2026-09-15 性能修复 #3,渐进式状态):
2790
+ // 缺省 / `all` ⇒ **全部分节**(向后兼容:老客户端、脚本、既有测试一律不受影响);
2791
+ // 否则只含列出的分节。`summary` 永远在内 —— 它是"便宜基线"(阶段/进度/计数),不是可选块。
2792
+ // 未知名字**忽略**而不是 400:多传一个参数不该把面板打死(本仓的失败哲学是"如实降级")。
2793
+ export function parseStateSections(raw) {
2794
+ const s = String(raw ?? '').trim().toLowerCase();
2795
+ const all = s === '' || s === 'all';
2796
+ const names = s.split(',').map((x) => x.trim()).filter(Boolean);
2797
+ const want = (n) => all || names.includes(n);
2798
+ return { all, want, included: ['summary'].concat(['people', 'feed', 'artifacts'].filter(want)) };
2799
+ }
2784
2800
  // Snapshot one run's full team state from a specific workspace.
2785
2801
  async function snapshotRun(ws, run) {
2786
2802
  const dir = join(ws, 'team', run);
@@ -4968,7 +4984,7 @@ function warnLeadToolFaceOnce(status, detail) {
4968
4984
  LEAD_TOOLFACE_WARNED.add(status);
4969
4985
  console.warn(`[expert-team] lead 工具面**未**收窄(${status}):${detail}`);
4970
4986
  }
4971
- export const _live = { pushActivity, phaseAccountingViolations, loggedPhases, authorityViolations, WRITE_TRACER, createWriteTracer, formatConflict, summarizeTool, parseLogLine, roleOfSub, mapRoleToSub, membersFromState, buildRoleSubMap, resolveSubRoles, childSessionTiming, subHeaderIndex, sessionExists, SUB_HEADER_CACHE, workflowEventIndex, workflowChildLabels, workflowChildMeta, workflowRuns, WF_EVENT_CACHE, rememberSessionRun, sessionRunFor, runOwnerSession, SESSION_RUNS, parseTeamCommand, deriveMemberEntries, schemaViolations, runHealth, RUN_STALL_MS, scaffoldFingerprint, SCAFFOLD_REQUIRED, strandedTasks, settleStranded, IN_FLIGHT_STATUSES, normalizeCoverage, SCHEMA_WARN_SEEN, pushActivityEvent, DEFAULT_LIMITS, LIMITS, resolveLimits, capacityViolations, DEFAULT_ROUND_LIMITS, ROUND_LIMITS, ROUND_LIMIT_ENV, resolveRoundLimits, ROUND_LIMIT_OF_KIND, roundOf, isQualityTask, normTitle, roundLimitViolations, reworkLoopWriteGuard, mutateTasks, readStandingRules, appendStandingRule, rulesRun, scopeOverlapWarnings, applyTaskStatus, waitRun, eventFamily, verdictFromToken, normalizeRoleName, truncateCodepoints, filterRunScopedSubs, runCreatedAtMs, runLogTail, liveFiles, LIVE_FILES_CACHE, DEFAULT_ROLES, resolveTierGate, TIER_GATE_ENV, snapshotRun, settingsPath, loadSettingsSync, currentSettings, limitsBaseFromSettings, roundLimitsBaseFromSettings, effectiveTierGate: () => TIER_GATE, ensureSkillInstalled, ensurePresetInstalled, uninstallInstalled, buildSkillRegistration, parseSkillMarkdown, PLUGIN_VERSION, INSTALL_STAMP, runtimeSkillRegistered: () => RUNTIME_SKILL_REGISTERED, agentScopedToolNames, warnLeadToolFaceOnce, resolveLeadToolFace, LEAD_TOOLFACE_ENV, effectiveLeadToolFace: () => LEAD_TOOLFACE, resolveLoopGuard, LOOP_GUARD_ENV, effectiveLoopGuard: () => LOOP_GUARD_ENABLED, resolveRosterDefaults, rosterSettings, createRun, watchScript, canvasPollMs, installHostSettings, hostValues, hostScope, hostSettingsNote, updateHostSettings, pickFileOnly, pickHostExpressible, buildHostSchema, hostBase, hostSchemaPaths, reapplySettingsDerived, currentSettings, mergeSettings, detectOptionalPlugins, OPTIONAL_PLUGINS, hintOptionalPluginsOnce, _resetOptionalHintOnce, scheduleOptionalPluginCheck, recheckOptionalPlugins, OPTIONAL_PROBE_DELAYS_MS, loaderEntryNames, hindsightToolReady, costMeterReady, listSubagentStatusBySession, cachedListSessions, _resetListSessionsCache, LIST_SESSIONS_TTL_MS, SUB_HEADER_MEMO, SUB_HEADER_STATS, _resetSubHeaderMemo, sessionsRootStamp, resetRoleReadBudget, roleReadBudgetSnapshot, syncRolePending, _resetRolePending, ROLE_READ_BUDGET_PER_REQUEST, ROLE_READ_LOG_CACHE: SUB_ROLE_LOG_CACHE };
4987
+ export const _live = { pushActivity, phaseAccountingViolations, loggedPhases, authorityViolations, WRITE_TRACER, createWriteTracer, formatConflict, summarizeTool, parseLogLine, roleOfSub, mapRoleToSub, membersFromState, buildRoleSubMap, resolveSubRoles, childSessionTiming, subHeaderIndex, sessionExists, SUB_HEADER_CACHE, workflowEventIndex, workflowChildLabels, workflowChildMeta, workflowRuns, WF_EVENT_CACHE, rememberSessionRun, sessionRunFor, runOwnerSession, SESSION_RUNS, parseTeamCommand, deriveMemberEntries, schemaViolations, runHealth, RUN_STALL_MS, scaffoldFingerprint, SCAFFOLD_REQUIRED, strandedTasks, settleStranded, IN_FLIGHT_STATUSES, normalizeCoverage, SCHEMA_WARN_SEEN, pushActivityEvent, DEFAULT_LIMITS, LIMITS, resolveLimits, capacityViolations, DEFAULT_ROUND_LIMITS, ROUND_LIMITS, ROUND_LIMIT_ENV, resolveRoundLimits, ROUND_LIMIT_OF_KIND, roundOf, isQualityTask, normTitle, roundLimitViolations, reworkLoopWriteGuard, mutateTasks, readStandingRules, appendStandingRule, rulesRun, scopeOverlapWarnings, applyTaskStatus, waitRun, eventFamily, verdictFromToken, normalizeRoleName, truncateCodepoints, filterRunScopedSubs, runCreatedAtMs, runLogTail, liveFiles, LIVE_FILES_CACHE, DEFAULT_ROLES, resolveTierGate, TIER_GATE_ENV, snapshotRun, parseStateSections, settingsPath, loadSettingsSync, currentSettings, limitsBaseFromSettings, roundLimitsBaseFromSettings, effectiveTierGate: () => TIER_GATE, ensureSkillInstalled, ensurePresetInstalled, uninstallInstalled, buildSkillRegistration, parseSkillMarkdown, PLUGIN_VERSION, INSTALL_STAMP, runtimeSkillRegistered: () => RUNTIME_SKILL_REGISTERED, agentScopedToolNames, warnLeadToolFaceOnce, resolveLeadToolFace, LEAD_TOOLFACE_ENV, effectiveLeadToolFace: () => LEAD_TOOLFACE, resolveLoopGuard, LOOP_GUARD_ENV, effectiveLoopGuard: () => LOOP_GUARD_ENABLED, resolveRosterDefaults, rosterSettings, createRun, watchScript, canvasPollMs, installHostSettings, hostValues, hostScope, hostSettingsNote, updateHostSettings, pickFileOnly, pickHostExpressible, buildHostSchema, hostBase, hostSchemaPaths, reapplySettingsDerived, currentSettings, mergeSettings, detectOptionalPlugins, OPTIONAL_PLUGINS, hintOptionalPluginsOnce, _resetOptionalHintOnce, scheduleOptionalPluginCheck, recheckOptionalPlugins, OPTIONAL_PROBE_DELAYS_MS, loaderEntryNames, hindsightToolReady, costMeterReady, listSubagentStatusBySession, cachedListSessions, _resetListSessionsCache, LIST_SESSIONS_TTL_MS, SUB_HEADER_MEMO, SUB_HEADER_STATS, _resetSubHeaderMemo, sessionsRootStamp, resetRoleReadBudget, roleReadBudgetSnapshot, syncRolePending, _resetRolePending, ROLE_READ_BUDGET_PER_REQUEST, ROLE_READ_LOG_CACHE: SUB_ROLE_LOG_CACHE };
4972
4988
 
4973
4989
  export function apply(ctx, config) {
4974
4990
  // 留一份 config:设置在运行时改变(官方面板 / 浮层)时要**用同一份 config** 重算上限与档位门,
@@ -5256,6 +5272,27 @@ export function apply(ctx, config) {
5256
5272
  const runParam = (url.searchParams.get('run') ?? '').trim();
5257
5273
  const wsParam = (url.searchParams.get('workspace') ?? '').trim();
5258
5274
  if (/[\\/]/.test(runParam)) { json(400, { ok: false, error: 'bad params' }); return; }
5275
+ // ── 渐进式状态(2026-09-15 性能修复 #3)────────────────────────────
5276
+ // 为什么:完整负载的成本随「本会话子代理数 × 日志体量」线性增长,而活跃写入会不断把
5277
+ // 目录戳 `(mtime,size)` 打失效(真机实测:小会话 311 ms;20+ 子代理的重会话 2.1–6.2 s)。
5278
+ // 可出现实需要"首屏就能看见"的只有**阶段/进度/计数**(便宜),"人/料/事件流"可以晚一拍、
5279
+ // 低频拉。于是给同一个端点加 `?section=`:
5280
+ // `summary`(便宜基线,首屏用)|`people`(成员/角色/时间线,最贵)|
5281
+ // `feed`(每 agent 最近事件)|`artifacts`(RUN.log 尾 + 工作区改动文件)
5282
+ // 兼容性第一:**不传 section(或 section=all)= 完整负载与字段语义完全不变**,
5283
+ // 所以老客户端、既有测试、脚本一律不受影响;`sections`/`degraded` 都是**新增**字段。
5284
+ // 纪律:截断**必须**上报(不许静默丢,也不许把"截断"伪装成"就这么多")。
5285
+ const secRaw = String(url.searchParams.get('section') ?? '');
5286
+ const secRequested = secRaw.trim() !== '';
5287
+ const secParsed = parseStateSections(secRaw);
5288
+ const want = secParsed.want;
5289
+ const included = secParsed.included;
5290
+ // 硬上限:角色解析最贵(每条子会话一次全量日志读),超限只解析前 N 条并如实标注。
5291
+ const MAX_ROLE_SUBS = Math.max(1, Number((typeof process !== 'undefined' && process.env && process.env.DSH_EXPERT_TEAM_MAX_ROLE_SUBS) || 0) || 60);
5292
+ // people 分节的软期限:超时后跳过更贵的可选增强(workflow 元数据),同样如实标注。
5293
+ const PEOPLE_DEADLINE_MS = Math.max(200, Number((typeof process !== 'undefined' && process.env && process.env.DSH_EXPERT_TEAM_PEOPLE_DEADLINE_MS) || 0) || 2500);
5294
+ const peopleStartedAt = Date.now();
5295
+ const degraded = [];
5259
5296
  const reg = (await registeredWorkspaces()).filter(Boolean);
5260
5297
  const workspaces = [...new Set([...(cwd ? [cwd] : []), ...reg])];
5261
5298
  if (!workspaces.length) { json(200, { ok: false, runs: [], runsAvailable: 0, error: 'no workspace registered — 请先在某项目里运行一次 /team <task>' }); return; }
@@ -5306,9 +5343,12 @@ export function apply(ctx, config) {
5306
5343
  : runOwnerSession(sel.runId);
5307
5344
  let peopleSid = owner.sid || sid;
5308
5345
  const knownIds = [...membersFromState(sel.stateMembers).byRole.values()];
5309
- let subs = await listSubagentStatusBySession(ctx, peopleSid, knownIds);
5346
+ // 只有 people 分节才解析人员:这一步要枚举子会话 durable 清单(重会话里这是整条链上
5347
+ // 最大的一块)。首屏用 summary 时不付这个成本 —— `agents` 返回空数组,
5348
+ // 由客户端 `sections` 字段知道自己拿的是"摘要",UI 如实显示"正在加载成员"。
5349
+ let subs = want('people') ? await listSubagentStatusBySession(ctx, peopleSid, knownIds) : [];
5310
5350
  // 归属会话拿不到人时退回请求会话(否则面板整块空掉),但**如实标注**这不是归属会话的人。
5311
- if (!subs.length && peopleSid !== sid) {
5351
+ if (want('people') && !subs.length && peopleSid !== sid) {
5312
5352
  const fallback = await listSubagentStatusBySession(ctx, sid, knownIds);
5313
5353
  if (fallback.length) { subs = fallback; peopleSid = sid; }
5314
5354
  }
@@ -5330,9 +5370,20 @@ export function apply(ctx, config) {
5330
5370
  // log (covers interrupted fan-outs, which have no chat card); then
5331
5371
  // ② the child's own role prompt (roleFromChildLog).
5332
5372
  mark('subs');
5333
- const wfLabels = await workflowChildLabels(ctx, peopleSid);
5373
+ const wfLabels = want('people') ? await workflowChildLabels(ctx, peopleSid) : {};
5334
5374
  mark('wfLabels');
5335
- await resolveSubRoles(ctx, subs, wfLabels);
5375
+ // 角色解析是全链最贵的一段(每条子会话一次全量日志读)。两道闸:
5376
+ // ① 只在 people 分节里做;② 硬上限 `MAX_ROLE_SUBS`(超限只解析前 N 条)。
5377
+ // 被上限挡住的条数**如实进 `degraded`**;"还没解析"(rolesDeferred,队列长度)与
5378
+ // "解析不出来"(unresolved)仍然分得开(两种零可区分)。
5379
+ if (want('people')) {
5380
+ if (subs.length > MAX_ROLE_SUBS) {
5381
+ await resolveSubRoles(ctx, subs.slice(0, MAX_ROLE_SUBS), wfLabels);
5382
+ degraded.push('roles:' + (subs.length - MAX_ROLE_SUBS));
5383
+ } else {
5384
+ await resolveSubRoles(ctx, subs, wfLabels);
5385
+ }
5386
+ }
5336
5387
  mark('roles');
5337
5388
  // R12 + R13 + R17(F-1 · P0 跨 run 成员串号):**显示与写盘都只吃过滤后的 `subById`**。
5338
5389
  // R13 裁决:只严写盘的话,数据污染修好了但用户看到的串号现象一点没变 —— 面板照样把
@@ -5346,8 +5397,8 @@ export function apply(ctx, config) {
5346
5397
  // 过滤后的候选按**创建时间升序** ⇒ 随后的"先到先占"即"同角色取创建更早者"。
5347
5398
  // 精确路径(STATE.members 非空 ⇒ 里面的 id 本来就是本 run 登记过的)不进过滤,
5348
5399
  // 二次轮询不会丢已登记成员。
5349
- let subById;
5350
- try {
5400
+ let subById = new Map();
5401
+ if (want('people')) try {
5351
5402
  if (membersFromState(sel.stateMembers).byRole.size) {
5352
5403
  subById = buildRoleSubMap(subs, sel.stateMembers, wfLabels); // 已登记:不过滤、不丢
5353
5404
  } else {
@@ -5365,11 +5416,13 @@ export function apply(ctx, config) {
5365
5416
  if (scoped.rejected > 0) sel.membersUnresolved = true;
5366
5417
  }
5367
5418
  } catch { subById = new Map(); sel.membersUnresolved = true; /* 过滤自身出错 ⇒ 一律不登记 */ }
5419
+ // ⚠️ 登记块只在 people 分节里跑:summary 时 subs 为空 ⇒ deriveMemberEntries 会算出
5420
+ // "清空成员"并覆写 STATE.json(轮询触发的写)。跳过它同时避免"GET 改写 run 工件"。
5368
5421
  // 派工即登记(C3):把 role→真实 agentId 落盘到 STATE.members。
5369
5422
  // SKILL §7.22 要求这件事,但代码从没有写路径 ⇒ 实测所有 run 的 stateMembers 恒空。
5370
5423
  // host 本就算出了 subById,直接登记:① 归属从"猜"变"精确";② 并行 run 不再串号。
5371
5424
  // **只在变化时写**(3s 轮询下否则会持续改写工件),且 best-effort 不影响读。
5372
- try {
5425
+ if (want('people')) try {
5373
5426
  const derived = deriveMemberEntries(subById, sel.stateMembers);
5374
5427
  if (derived.changed) {
5375
5428
  const stPath = join(sel.workspace, 'team', sel.runId, 'STATE.json');
@@ -5386,12 +5439,17 @@ export function apply(ctx, config) {
5386
5439
  // 「任务为什么会中断」的诚实答案:一次 fan-out 若从未写 run-end,其编排
5387
5440
  // 工具调用就没有返回(回合被打断/进程退出),成员会永远停在「进行中」。
5388
5441
  // 这里把每次扇出的命运一并交给面板显示。
5389
- sel.wfRuns = await workflowRuns(ctx, peopleSid, subs);
5442
+ // ⚠️ workflow 元数据两段(见下)只在 people 分节、且**未超软期限**时才算:
5443
+ // 它们要遍历该会话的 workflow 事件(重会话里是大头之一),而首屏并不需要。
5444
+ // 超期限 ⇒ 跳过并如实进 `degraded`(不静默、也不假装算过)。
5445
+ const peopleWithinDeadline = want('people') && (Date.now() - peopleStartedAt < PEOPLE_DEADLINE_MS);
5446
+ if (want('people') && !peopleWithinDeadline) degraded.push('wf:deadline');
5447
+ if (peopleWithinDeadline) sel.wfRuns = await workflowRuns(ctx, peopleSid, subs);
5390
5448
  // 全景图的分层依据:workflow 派生的子代理**没有会话 header**(`createdAt` 全 0,
5391
5449
  // 这正是流转图退化成「无创建时间记录,无法分批」的根因);它们唯一可辩护的
5392
5450
  // 时间来自 `tool-workflow/agent-start` 事件本身。同时给出逐子代理的结算态,
5393
5451
  // 让画布能把"被中断、从未结算"的节点画成红色,而不是永远转圈。
5394
- sel.wfChildren = await workflowChildMeta(ctx, peopleSid);
5452
+ if (peopleWithinDeadline) sel.wfChildren = await workflowChildMeta(ctx, peopleSid);
5395
5453
  // R13(lead 裁决 notes②):**显示**也走同一份 run 作用域过滤结果 `subById`。
5396
5454
  // R12 只严了写盘,面板在 members 为空时仍会把别的 run 的活人显示成本 run 成员
5397
5455
  // ⇒ 数据污染修好了,但**用户看到的串号现象一点没变**(而 membersUnresolved
@@ -5399,7 +5457,10 @@ export function apply(ctx, config) {
5399
5457
  // 归属不上的活人 ⇒ 该角色显示为**未启动**(active:false),并保留 membersUnresolved。
5400
5458
  // 精确路径(STATE.members 非空)不进过滤 ⇒ subById 直接来自 buildRoleSubMap,
5401
5459
  // 已登记成员的显示不丢(R12 第 4 点)。
5402
- sel.members = enrichMembers(sel.runId, sel.roles, sel.members, subById, sel.agents);
5460
+ // ⚠️ 只在 people 分节里 enrich:summary 时 `subById` 是空 Map,enrich 会把**每个成员**
5461
+ // 都标成"未启动"(把名册信息也一起糊掉)。summary 保留 snapshotRun 给的名册视图,
5462
+ // 活跃态缺失就如实缺失(由客户端按 `sections` 显示"正在加载成员")。
5463
+ if (want('people')) sel.members = enrichMembers(sel.runId, sel.roles, sel.members, subById, sel.agents);
5403
5464
  // cost/model plan: expose each role's planned model so the overlay
5404
5465
  // shows the scheme even before dispatch (live model wins once running).
5405
5466
  const modelPlan = sel.models || modelPlanFor(sel.roles || []);
@@ -5420,7 +5481,9 @@ export function apply(ctx, config) {
5420
5481
  const timingIdx = subHeaderIndex(ctx, subs, wfLabels);
5421
5482
  const timingOf = {};
5422
5483
  for (const s of subs) timingOf[String(s.id || '')] = timingIdx.get(String(s.id || '')) || { createdAt: 0, parentId: '', depth: 0 };
5423
- sel.agents = subs.map((s) => {
5484
+ // summary 分节返回**空数组**(不是"没有成员"):客户端据 `sections` 区分
5485
+ // "摘要里本来就没有这一块"与"真的一个成员都没有"(两种零可区分)。
5486
+ sel.agents = want('people') ? subs.map((s) => {
5424
5487
  const sid2 = String(s.id || '');
5425
5488
  const t = timingOf[sid2] || { createdAt: 0, parentId: '', depth: 0 };
5426
5489
  // 列表阶段(listSessions 分支)已经带出 header 字段时优先用它,省一次读
@@ -5433,12 +5496,13 @@ export function apply(ctx, config) {
5433
5496
  // UI 用它区分"0 = 无时间"与"真的是 0";缺字段时客户端也可用 createdAt>0 兜底
5434
5497
  hasTimestamp: createdAt > 0
5435
5498
  };
5436
- });
5499
+ }) : [];
5437
5500
  // 实时兜底视图:`TASKS.json` 为空但确有子代理在跑时(典型:lead 用 `workflow`
5438
5501
  // 扇出却没按协议回写任务),把活子代理投影成只读"实时任务",免得面板
5439
5502
  // 任务清单/依赖图/全景全空 —— 用户看到的是"有人在跑,面板却什么都没有"。
5440
5503
  // 只在真的有活子代理时投影;行上带 live:true,UI 会明确标注这不是 TASKS.json。
5441
- if (!taskList(sel.tasks).length && subs.length) {
5504
+ // (`subs` 非空本身就意味着在 people 分节里,另外再显式判一次免得将来改坏。)
5505
+ if (want('people') && !taskList(sel.tasks).length && subs.length) {
5442
5506
  sel.tasksLive = subs.filter((s) => s && s.id).map((s, i) => ({
5443
5507
  id: 'L' + (i + 1),
5444
5508
  title: String(s.label || '').trim() || (roleOfSub(s) || 'agent') + ' · ' + String(s.id).slice(0, 8),
@@ -5452,8 +5516,9 @@ export function apply(ctx, config) {
5452
5516
  }
5453
5517
  // Live per-agent activity feed (Qoder-style): last tool events
5454
5518
  // per subagent session, keyed by agent id.
5519
+ // 只在 feed 分节里收集(它按子会话遍历活动缓存;summary 首屏不需要)。
5455
5520
  const feed = {};
5456
- for (const s of subs) {
5521
+ if (want('feed')) for (const s of subs) {
5457
5522
  const arr = ACTIVITY.get(s.id);
5458
5523
  if (arr && arr.length) feed[s.id] = arr.slice(-12);
5459
5524
  }
@@ -5486,8 +5551,11 @@ export function apply(ctx, config) {
5486
5551
  try { sel.warnings = checkKindWarnings(taskList(sel.tasks)); } catch { sel.warnings = []; }
5487
5552
  // Live monitoring: RUN.log feed (what just happened) + git
5488
5553
  // changed files (what is being touched right now).
5489
- try { sel.logTail = await runLogTail(join(sel.workspace, 'team', sel.runId)); } catch { sel.logTail = ''; }
5490
- try { sel.files = await liveFiles(sel.workspace, sel.runId); } catch { sel.files = []; }
5554
+ // 只在 artifacts 分节里读(`liveFiles` 要跑 git、`runLogTail` 要读日志尾部)。
5555
+ if (want('artifacts')) {
5556
+ try { sel.logTail = await runLogTail(join(sel.workspace, 'team', sel.runId)); } catch { sel.logTail = ''; }
5557
+ try { sel.files = await liveFiles(sel.workspace, sel.runId); } catch { sel.files = []; }
5558
+ }
5491
5559
  // **GET 不写 run 工件**(2026-09-13 修):这里原先每 ≤5 分钟触发一次
5492
5560
  // `autoAggregate`(`Date.now() - lastAggAt > 5*60*1000`),于是浮层每 3 秒的轮询
5493
5561
  // 会**周期性改写 `team/METRICS.md` 与 `team/LEARNINGS.md`** —— 而 `LEARNINGS.md`
@@ -5507,6 +5575,12 @@ export function apply(ctx, config) {
5507
5575
  json(200, Object.assign(
5508
5576
  { ok: true, runs, workspaces, cwd, agents: sel.agents, rolesDeferred: budget.deferred },
5509
5577
  sel,
5578
+ // 新增字段(只在相关时出现,默认负载与老客户端保持不变):
5579
+ // sections —— 显式要了分节时,告诉调用方"这份负载包含哪些块"(缺的块 ≠ 空数据,
5580
+ // 客户端据此区分"摘要里没有"与"真的是空的");
5581
+ // degraded —— 命中硬上限/软期限而**少算了东西**时的如实标注(不许静默丢)。
5582
+ secRequested ? { sections: included } : null,
5583
+ degraded.length ? { degraded: true, degradedReason: degraded.join(','), sections: included } : null,
5510
5584
  prof ? { profile: Object.assign({}, prof.steps, { rolesBudget: budget }) } : null,
5511
5585
  ));
5512
5586
  } catch (e) {
@@ -3,10 +3,12 @@
3
3
  // `/team check` 事后报红、或等用户走查才发现。
4
4
  //
5
5
  // 为什么是"落地后纠偏"而不是"写前拒绝"(设计见 docs/Qoder对标/07-B2-1真拦截设计稿.md §二):
6
- // 本插件**不注册任何模型可调用的工具**,今天的协议是让 lead **直接改文件**
7
- // (SKILL.md:24/95/99/157 明写"你落盘 / 你统一更新 TASKS.json")。
8
- // 一刀 deny 直写会让整条协议当场断掉。因此第 1 阶段只做"落地后 block + 反馈":
9
- // 写仍然发生(不破坏任何现存写法),但工具结果变成 isError,且逐条告诉模型哪条不合法。
6
+ // 本插件**不注册任何模型可调用的工具**;协议已单源化为 **R1(角色写自己的 run 工件)**,
7
+ // lead 的工具面本就**没有** `write`(`lib/lead-toolface.js` @ 34 行把 `write` 列入
8
+ // `LEAD_DENY_CANDIDATES`)⇒ 本拦截器对角色写 run 工件是**放行**的:`runScopedTarget`
9
+ // 只判 `TASKS.json`/`SPEC.md`,其余工件 `kind:'other'` 不拦(见本文件 @ 59-73 与 @ 191 行)。
10
+ // 因此这里只做"落地后 block + 反馈":写仍然发生(不破坏任何现存写法),
11
+ // 但工具结果变成 isError,且逐条告诉模型哪条不合法。
10
12
  //
11
13
  // 与既有仲裁的关系(`lib/command.js:68` 的 L1-4′:「schema 告警**只上报、不阻断**」):
12
14
  // 本模块**不推翻**该仲裁 —— schema 形状告警仍然只上报。这里只拦**两个永不合法**的
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yangdcm/dsh-expert-team",
3
- "version": "1.3.12",
3
+ "version": "1.3.13",
4
4
  "description": "dsh「专家团」bundle:一句自然语言自动组建/持久化一支 12 角色多智能体团队,共享工作区协议 + 阶段门控编排 + 结构化交接 + 质量门禁/自动调度,实现者直接改代码并产出持久工件;带 live 团队浮层(质量门禁/覆盖率/工件预览)。 · Role-based multi-agent expert team for DeepSeek Harness: one sentence in, a staged and gated team delivery out.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -88,7 +88,7 @@
88
88
  "check:name": "node scripts/rename-package.mjs --check",
89
89
  "test:regression": "node regression.test.mjs",
90
90
  "test:e2e": "node e2e.test.mjs",
91
- "test:all": "node bootstrap.test.mjs && node host-settings.test.mjs && node smoke.test.mjs && node regression.test.mjs && node e2e.test.mjs && node flow.test.mjs && node plan-decide.test.mjs && node plan-discard.test.mjs && node models-honesty.test.mjs && node evidence-gate.test.mjs && node sync-gate.test.mjs && node artifact-writer.test.mjs && node dag.test.mjs && node metrics.test.mjs && node broken-chain.test.mjs && node schema-warn.test.mjs && node template-copy.test.mjs && node task-binding.test.mjs && node role-merge.test.mjs && node wf-recovery.test.mjs && node panorama.test.mjs && node run-ownership.test.mjs && node role-identity.test.mjs && node command-parse.test.mjs && node member-registry.test.mjs && node schema-check.test.mjs && node run-health.test.mjs && node cordis-fs-port.test.mjs && node scaffold-fingerprint.test.mjs && node preset-lint.test.mjs && node stranded-tasks.test.mjs && node agent-scope-guard.test.mjs && node schema-warn-noise.test.mjs && node tool-card-status.test.mjs && node dag-status.test.mjs && node capacity-limits.test.mjs && node task-cas.test.mjs && node write-lock.test.mjs && node card-stall.test.mjs && node multi-session-and-confirm.test.mjs && node standing-rules.test.mjs && node scope-overlap.test.mjs && node task-cancel-protect.test.mjs && node wait-run.test.mjs && node rework-loop.test.mjs && node client-css-integrity.test.mjs && node interception.test.mjs && node mutation-catalog.test.mjs && node write-bypass-ratchet.test.mjs && node asi-hazards.test.mjs && node dispatch-contract.test.mjs && node closure-ledger.test.mjs && node rework-nature.test.mjs && node loop-guard.test.mjs && node vocab-consistency.test.mjs && node state-no-write.test.mjs && node routes-shared.test.mjs && node metrics-render.test.mjs && node metrics-collect.test.mjs && node first-runnable.test.mjs && node closing-budget.test.mjs && node scan-single-source.test.mjs && node tier.test.mjs && node dispatch-ledger.test.mjs && node tier-gate.test.mjs && node tier-badge.test.mjs && node validate-module.test.mjs && node settings.test.mjs && node settings-page.test.mjs && node policy.test.mjs && node phase-accounting.test.mjs && node log-parse-module.test.mjs && node command-parse-module.test.mjs && node authority.test.mjs && node write-tracer.test.mjs && node token-accounting.test.mjs && node lead-toolface.test.mjs && node settings-consumers.test.mjs && node settings-wiring.test.mjs && node state-perf-guard.test.mjs",
91
+ "test:all": "node bootstrap.test.mjs && node host-settings.test.mjs && node smoke.test.mjs && node regression.test.mjs && node e2e.test.mjs && node flow.test.mjs && node plan-decide.test.mjs && node plan-discard.test.mjs && node models-honesty.test.mjs && node evidence-gate.test.mjs && node sync-gate.test.mjs && node artifact-writer.test.mjs && node dag.test.mjs && node metrics.test.mjs && node broken-chain.test.mjs && node schema-warn.test.mjs && node template-copy.test.mjs && node task-binding.test.mjs && node role-merge.test.mjs && node wf-recovery.test.mjs && node panorama.test.mjs && node run-ownership.test.mjs && node role-identity.test.mjs && node command-parse.test.mjs && node member-registry.test.mjs && node schema-check.test.mjs && node run-health.test.mjs && node cordis-fs-port.test.mjs && node scaffold-fingerprint.test.mjs && node preset-lint.test.mjs && node stranded-tasks.test.mjs && node agent-scope-guard.test.mjs && node schema-warn-noise.test.mjs && node tool-card-status.test.mjs && node dag-status.test.mjs && node capacity-limits.test.mjs && node task-cas.test.mjs && node write-lock.test.mjs && node card-stall.test.mjs && node multi-session-and-confirm.test.mjs && node standing-rules.test.mjs && node scope-overlap.test.mjs && node task-cancel-protect.test.mjs && node wait-run.test.mjs && node rework-loop.test.mjs && node client-css-integrity.test.mjs && node interception.test.mjs && node mutation-catalog.test.mjs && node write-bypass-ratchet.test.mjs && node asi-hazards.test.mjs && node dispatch-contract.test.mjs && node closure-ledger.test.mjs && node rework-nature.test.mjs && node loop-guard.test.mjs && node vocab-consistency.test.mjs && node state-no-write.test.mjs && node routes-shared.test.mjs && node metrics-render.test.mjs && node metrics-collect.test.mjs && node first-runnable.test.mjs && node closing-budget.test.mjs && node scan-single-source.test.mjs && node tier.test.mjs && node dispatch-ledger.test.mjs && node tier-gate.test.mjs && node tier-badge.test.mjs && node validate-module.test.mjs && node settings.test.mjs && node settings-page.test.mjs && node policy.test.mjs && node phase-accounting.test.mjs && node log-parse-module.test.mjs && node command-parse-module.test.mjs && node authority.test.mjs && node write-tracer.test.mjs && node token-accounting.test.mjs && node lead-toolface.test.mjs && node settings-consumers.test.mjs && node settings-wiring.test.mjs && node state-perf-guard.test.mjs && node state-sections.test.mjs && node artifact-ownership.test.mjs",
92
92
  "test:toolcard": "node tool-card-status.test.mjs",
93
93
  "test:flow": "node flow.test.mjs",
94
94
  "test:decide": "node plan-decide.test.mjs",