@yangdcm/dsh-expert-team 1.3.15 → 1.3.17

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
@@ -731,3 +731,116 @@ DAG 并行扇出、角色 chip、工件脚注全部保留。
731
731
  - `/team` 命令面:一次性组队 / 持久化活团队 / 仅工件 / 先确认后开工 / 流程档位 / 画布 / 代码索引 /
732
732
  自学习 / 配额 / 冷启动清算
733
733
  - 零运行时依赖、无构建步骤、无安装钩子
734
+
735
+ ## 1.3.17
736
+
737
+ **重活有界化 + 「为谁而做」前置与其主视觉。**
738
+
739
+ ### 性能:把两段最贵的活变成"有界"(不是"更快")
740
+
741
+ 真机基线(30+ 子代理的重会话,用户实测):`?section=people,feed` 单发 **5.4–9.2 s**,分段
742
+ `subs` **2.9–3.7 s**、`roles` **2.4–5.5 s** —— 两段都**没有上限**,请求还压在事件循环上
743
+ (`summary` 与重活并发时从 16 ms 涨到 **772 ms**)。
744
+
745
+ - **`subs`**:枚举整棵 sessions 树是最贵的一步 ⇒ 两次之间至少 N 秒
746
+ (`DSH_EXPERT_TEAM_SUBS_ENUM_MIN_INTERVAL_MS`,默认 30 s),窗口内**零枚举**;另给枚举期限
747
+ (`DSH_EXPERT_TEAM_SUBS_DEADLINE_MS`,默认 800 ms),到点即停。被挡住的 id **不丢成员**:
748
+ 它只是没有 header,按既有口径如实显示"细节不可得"(不假装 0)。
749
+ - **`roles`**:读 MB 级子会话日志很贵且**不紧急** ⇒ 实时路径**默认零读**,未知角色如实显示为
750
+ **待解析**(新字段 `rolesPending`,替代旧名 `rolesDeferred`;客户端**真的渲染**它),解析交给
751
+ 低频后台(每轮 ≤`DSH_EXPERT_TEAM_ROLES_PER_BURST`(默认 1)条、两次间隔
752
+ ≥`DSH_EXPERT_TEAM_ROLES_MIN_INTERVAL_MS`(默认 30 s)),期限
753
+ `DSH_EXPERT_TEAM_ROLES_DEADLINE_MS`(默认 600 ms)。已解析结果永久缓存 ⇒ `rolesPending`
754
+ **单调下降收敛到 0**(旧实现每请求从零重算 ⇒ 实测 16→40 **上涨**)。
755
+ - **噪声纪律**(新):**节流不是失败**,绝不每轮进 `degraded` —— 节流窗口内每次轮询都会命中,
756
+ 否则面板会长期挂一个降级标记,把真告警一起降权。节流的事实由 `subsPending` / `rolesPending`
757
+ 诚实表达;`degraded` 只承载**真截断**(`subs:deadline` / `roles:deadline`)。"缺块 ≠ 空数据"不变。
758
+ - 护栏:新增 `state-bounded.test.mjs`(13 条)钉死"零枚举 / 期限即停 / 零读 / 收敛 / 字段单一真源
759
+ 且被渲染 / 节流不进 degraded"。
760
+ - 顺带修两处**源码级断言与文本强耦合**(加五行代码就假红,属"断言失去判据对象"那一类):
761
+ `run-ownership` 的固定 2600 字符窗口改为**语义锚点切片**;`state-sections` 的正则改为只表达
762
+ 意图(不再绑死参数表)。**断言本身一字未改**。
763
+ - **诚实边界**:端到端墙钟需实机复测(本机装不进运行中的宿主);本版给的是"机制级证据 +
764
+ 单位成本账"。默认值都可用上面的环境变量覆盖 —— 想更激进就调小间隔。
765
+
766
+ ### 文档:「为谁而做」前置 + 主视觉(图 1)
767
+
768
+ - 「为谁而做 / Who it is for」提到 hero + 指标行之后、`速览` 之前(中英同序);
769
+ - 新增 `docs/images/who-is-it-for.svg`(中)与 `.en.svg`(英,同布局同生成器):顶带主张
770
+ 「一句话 → 一支完整技术部」、左带三类对象、主带 **12 个岗位卡 4×3**、
771
+ 「**你 · lead**」橙色胶囊**独占一带**、底带诚实边界;纯矢量、系统字体栈、配色克制;
772
+ - **保留**「岗位 → 角色 → 做什么」对照表(图下方):**图给冲击力、表给检索**(表格是机读文本,
773
+ 比 SVG 里的字更可靠);图号顺延为 图 1…图 8(中英一致、无重复);图用绝对地址 + 关键词 alt;
774
+ - 自检:包围盒估算 → 两两相交 → 带约束 → 越界 → **文字溢出父容器**;首轮抓到英文 chip 超框
775
+ 16.8px 并修掉,最终两语言 无相交/无跨带/无越界/无溢出 + `xmllint` 良构;渲染由父会话浏览器核对。
776
+ ## 1.3.16
777
+
778
+ **三件收尾:`/state` 重活的最大一段(`subs`)、冷启动、以及 R1 的"bash 绕过"坦白。**
779
+
780
+ ### A. `subs` 2,398 ms —— 根因不是缓存不够,而是"永远查不完"
781
+
782
+ - **真机 profile 定位**:重活 `?section=people,feed` 单发 3,295 ms = `subs` **2,398** + `roles` 874 + 其余 ~2。
783
+ - **根因(真 bug)**:`STATE.members → membersFromState().byRole` 这个 Map **同时**存了
784
+ `role→agentId` 与 `agentId→role` 两种键,而调用点直接取 `.values()` ⇒ **一半是角色名**
785
+ (`backend` / `reviewer`)。角色名永远不可能是 session header 的 id ⇒ `missingIds` **永久非空**
786
+ ⇒ **每个请求都重新枚举 475 个 artifact**,`SUB_HEADER_MEMO`(1.3.11 加的按 id 备忘)因此形同虚设。
787
+ - **修法**:新增 `memberAgentIds()`(只取真 id)并在调用点使用;`isAgentIdLike()` **按角色名精确排除**
788
+ (等于角色 id,或首段是角色 id 的 `frontend-F4`/`reviewer-R1` 这类带后缀标签),**不按长度猜**
789
+ —— 长度阈值会误伤短 id(`ended-x`/`live-1`),那才是真丢数据。函数内**再兜一道过滤**,
790
+ 防止未来调用方又把角色名传进来。顺带修掉 `buildRoleSubMap` 把 id 当角色的同一处根因。
791
+ - **效果**(进程内、可复现):同一份数据第二次请求 `listSessions` **调用 0 次**(原为每次 1 次)。
792
+
793
+ ### B. 冷启动 1,313 ms → 打**逐 run 戳缓存**(含落盘)
794
+
795
+ - **真机 profile 定位**:重启后第一次 `?section=summary` = 1,313 ms,其中 `runs+select` **876 ms**
796
+ (每个 run 都要读 `STATE.json` + `TASKS.json` 再算 health/violations/owner);随后 267 ms → 17 ms。
797
+ - **修法**:按**每个 run 自己**的 `STATE.json`/`TASKS.json` 的 `(mtimeMs, size)` 作失效键缓存"列表行",
798
+ 索引**落盘**到 `$DSH_HOME/expert-team/runs-index.json`(可用 `DSH_EXPERT_TEAM_RUNS_INDEX` 覆盖位置)
799
+ ⇒ **重启后第一次**也只是 stat 校验 + 命中,不必从零算。
800
+ - **为什么不能只戳 run 目录**:改文件**不会**改父目录 mtime(只有增删条目会)⇒ 那样会读到旧阶段/旧计数。
801
+ 戳到文件本身才是"看到的就是真的"。
802
+ - **实测**(进程内、`/tmp` 索引):第一次算 5 个 run 并落盘 2,558 B;**模拟重启后第一次 1 ms、命中 5、零重算**;
803
+ 只改一个 run 的 `STATE.json` ⇒ **恰好重算那 1 个**;新增 run ⇒ **立即可见**。
804
+ - **顺带修**:`team/` 根下的**普通文件**(`CODEINDEX.json` / `LEARNINGS.md`…)过去被当作 run 读
805
+ `STATE.json` ⇒ 面板 run 下拉里出现一串假的 "broken run"(画布上真能看到)。现在**只列目录**。
806
+
807
+ ### C. R1 的 `bash` 绕过:**只报不拦**(刻意不阻断)
808
+
809
+ - R1 硬门禁只覆盖 `write`/`edit`;持 `bash` 的 backend/frontend/researcher/qa/dba/devops 理论上可
810
+ `cat > SPEC.md` 绕过。**静默绕过**违背本仓纪律,但静态判断 bash 写目标不可靠(重定向/变量/子命令)
811
+ ⇒ 新增 `lib/artifact-redirect-watch.js`:挂在 `tools/post-execute`,**只在**"命令里明显写向
812
+ `<team 根>/<runId>/<已知工件>`"时**留痕一行 + onEvent**。
813
+ - **绝不**阻断、**绝不**改结果、**绝不**抛错(沿用"监听器不得成为故障源"的纪律,有源码级禁令断言);
814
+ 含变量/`/dev/null`/工作区代码/更深路径一律**不命中**(宁可漏报,不可误伤)。
815
+ - 已知工件名来自**两处既有真源的并集**(`ARTIFACT_TEMPLATES` ∪ `ARTIFACT_OWNERS`)—— 同时把模板清单
816
+ 提升为模块级单一真源 `ARTIFACT_TEMPLATES`,`authority` / `artifact-ownership` 两组断言改为**读这份真源**
817
+ (原先按源码字面量解析,重构后会"失去判据对象"—— 那比断言失败更危险,它看起来像通过)。
818
+ - **验收样例**:7 个命中形态(`cat > SPEC.md`、`>>`、`tee`、`tee -a`、带引号绝对路径、`2>`)
819
+ + 9 个不命中形态(工作区代码、`/tmp`、无写目标、工作区根的 `SPEC.md`、非工件名、含变量、
820
+ `/dev/null`、只读命令、更深路径)全部符合预期(`artifact-redirect-watch.test.mjs`)。
821
+
822
+ ### D. 会话模型 effort 预检(**只告警,不阻断**)—— 一次真实故障的定性
823
+
824
+ - **故障现象**:`model "deepseek-flash" does not support reasoning effort "low"`。**不是插件的错,
825
+ 也不是宿主缺 `low`** —— 是用户 `~/.dsh/settings.yaml` 里会话默认路由(命名空间 `agent-default-model`)
826
+ 的模型条目**漏写 `reasoningEfforts`** ⇒ 宿主能力表里该模型只剩 `off` ⇒ **任何**显式 effort 都被拒
827
+ (`dsh-llm` 的 `resolveCallWithInfo`:`reasoning === undefined` 时只要传了 `reasoningEffort` 就抛
828
+ `UNSUPPORTED_REASONING_EFFORT`)。而本 preset **8 个角色声明 `high`、4 个声明 `low`** ⇒ 该路由下
829
+ **12 个角色全会失败**;"只有 low 报错"是假象(先派谁先报谁)。宿主在**任何网络 I/O 之前**就拒。
830
+ - **插件能做什么 / 不能做什么(如实写)**:派工由宿主 `tool-subagent` + LLM 运行时执行,插件**无法**
831
+ 在派工前改变宿主行为;能做的是**提前一行告警** + 给出修法。因此本项**只告警、不阻断、不改 preset 的
832
+ effort 分档**(那是设计意图)。
833
+ - **实现**:新增 `lib/effort-preflight.js`(纯函数判定 + 有界重探接线):读 preset 声明的 effort(真源,
834
+ 按行正则,不引 YAML 解析器)→ 读宿主公开入口 `agentDefaultModel.currentSelection()` 与
835
+ `llm.resolveModelInfo(provider, model)` → 覆盖不全就**打一行**(含"改哪个命名空间/字段/值域")。
836
+ **读不到/抛错一律静默**(fail-open);一次加载最多一行;服务晚挂则 `ctx.inject` 事件驱动重探。
837
+ - **`scripts/validate-agent-preset.mjs`** 补**值域**校验(`reasoningEffort ∈ off/low/high/max`)+ 结尾指路
838
+ (脚本**不读用户机器**,只校验 preset 侧)。
839
+ - **文档**:README 中英 FAQ 各补一条(自然语言问句,便于检索)+ `llms.txt` 一行故障排查指针。
840
+ - **测试**:`effort-preflight.test.mjs`(真源分档 8/4 / 判定矩阵 / 只报一次且可操作 / fail-open 静默 / 只告警不阻断);
841
+ 变异体 `M140-effort-preflight-blind`(缺档也不报 ⇒ 预检变睁眼瞎)**已实测**能杀死测试(5 条断言失败)。
842
+ ### 其它
843
+
844
+ - 新增变异体 `M139-artifact-redirect-watch-blind`(候选提取恒空 ⇒ 观测器变睁眼瞎),
845
+ catalog 139 条;**已实测**注入后该测试 **10 条断言失败**、还原后逐字节恢复。
846
+ - 测试文件 **85 个**;`npm run test:all` EXIT=0。
package/README.en.md CHANGED
@@ -22,31 +22,15 @@ A plugin for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)
22
22
 
23
23
  > Zero runtime dependencies. Recommended: also install **Hindsight** (cross-project memory) — see [Dependencies and recommended plugins](#dependencies-and-recommended-plugins).
24
24
 
25
- ## At a glance
26
-
27
- | Item | Value |
28
- |---|---|
29
- | Package | `@yangdcm/dsh-expert-team` (public npm package) |
30
- | Repository | <https://github.com/yangdcm/dsh-expert-team> |
31
- | Host | [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) ≥ **0.1.5-rc.1** (`web` profile) |
32
- | Runtime dependencies | **none** (`dependencies: {}`; `lib/` imports only sibling files and Node builtins) |
33
- | Node.js | ≥ 20 |
34
- | License | MIT |
35
- | Install (one line) | `dsh plugin --profile web add @yangdcm/dsh-expert-team` |
36
- | First run (one line) | switch the session to "Expert team mode" → `/team build a payments module with login` |
37
- | Process artifacts | `<your workspace>/team/<run-id>/` (`SPEC.md` · `PLAN.md` · `TASKS.json` · `REVIEW.md` · `TEST.md` · `SUMMARY.md` …) |
38
- | Machine-local data | `$DSH_HOME/expert-team/` (`settings.json` · `LEARNINGS.md` · `session-runs.json`) |
39
-
40
- > A dsh plugin · a DeepSeek Harness multi-agent (agent team) orchestrator: role-based subagents · DAG parallelism · staged gates · quality gates · artifact trail.
41
- >
42
- > Index for LLMs and retrieval: [`llms.txt`](https://github.com/yangdcm/dsh-expert-team/blob/main/llms.txt)
43
-
44
25
  ## Who it is for
45
26
 
46
27
  **A complete engineering department for small teams and solo builders** — no hiring, no assembling a team:
47
28
  one sentence spins up the twelve seats (product, architecture, research, UI/UX, backend, frontend, data,
48
29
  security, review, QA, devops, docs) and delivers through a 9-phase gated pipeline. Implementers edit your
49
30
  codebase directly, and everything is logged as reviewable artifacts.
31
+ ![Who it is for: a complete engineering department for small teams and solo builders — product, architecture, research, UI/UX, backend, frontend, data, security, review, QA, devops and docs woven into a 9-phase gated pipeline](https://raw.githubusercontent.com/yangdcm/dsh-expert-team/main/docs/images/who-is-it-for.en.svg)
32
+
33
+ <sub>Figure 1: **who it is for**. Three audiences (internal tools and product iteration in small companies · freelance and outsourced delivery · solo developers shipping a complete project) share one way of working: twelve department seats woven into a 9-phase gated pipeline. Note the amber strip at the bottom — **you (lead) sit on a different layer from the twelve seats**: you decide product-level and scope-level questions, the team drives the rest.</sub>
50
34
 
51
35
  | Department seat | Agent | What it does in the pipeline |
52
36
  |---|---|---|
@@ -70,12 +54,31 @@ solo developers shipping a complete project · any long task where "someone inde
70
54
 
71
55
  ![The expert-team 9-phase gated pipeline: clarify → research → design → spec-review (hard gate) → plan approval → implement (DAG parallel) → review → test → deliver](https://raw.githubusercontent.com/yangdcm/dsh-expert-team/main/docs/images/pipeline.svg)
72
56
 
73
- <sub>Figure 1: the 9-phase gated pipeline. `spec-review` is a **hard gate** — if the SPEC.md
57
+ <sub>Figure 2: the 9-phase gated pipeline. `spec-review` is a **hard gate** — if the SPEC.md
74
58
  "boundaries and prohibitions" section is empty, the run does not advance (`lib/interception.js`).
75
59
  `plan-approval` is an approval gate that is **on by default** (`identity.keepPlanGate`, can be turned off in settings).
76
60
  The `implement` phase **fans out** along the dependency DAG: several implementers start at once, each touching only its own files.</sub>
77
61
 
78
62
  ---
63
+ ## At a glance
64
+
65
+ | Item | Value |
66
+ |---|---|
67
+ | Package | `@yangdcm/dsh-expert-team` (public npm package) |
68
+ | Repository | <https://github.com/yangdcm/dsh-expert-team> |
69
+ | Host | [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) ≥ **0.1.5-rc.1** (`web` profile) |
70
+ | Runtime dependencies | **none** (`dependencies: {}`; `lib/` imports only sibling files and Node builtins) |
71
+ | Node.js | ≥ 20 |
72
+ | License | MIT |
73
+ | Install (one line) | `dsh plugin --profile web add @yangdcm/dsh-expert-team` |
74
+ | First run (one line) | switch the session to "Expert team mode" → `/team build a payments module with login` |
75
+ | Process artifacts | `<your workspace>/team/<run-id>/` (`SPEC.md` · `PLAN.md` · `TASKS.json` · `REVIEW.md` · `TEST.md` · `SUMMARY.md` …) |
76
+ | Machine-local data | `$DSH_HOME/expert-team/` (`settings.json` · `LEARNINGS.md` · `session-runs.json`) |
77
+
78
+ > A dsh plugin · a DeepSeek Harness multi-agent (agent team) orchestrator: role-based subagents · DAG parallelism · staged gates · quality gates · artifact trail.
79
+ >
80
+ > Index for LLMs and retrieval: [`llms.txt`](https://github.com/yangdcm/dsh-expert-team/blob/main/llms.txt)
81
+
79
82
 
80
83
  ## In 30 seconds
81
84
 
@@ -118,32 +121,32 @@ answer them:
118
121
 
119
122
  ![Expert-team full-screen canvas: phase bar, progress, and the role-based subagent roster (who is running, on which model)](https://raw.githubusercontent.com/yangdcm/dsh-expert-team/main/docs/images/canvas.png)
120
123
 
121
- <sub>Figure 2: **the full-screen canvas**. Look at the phase bar and progress, the roster (who is running, on which model), and the four views (people / tasks / artifacts / board) — one layer above the floating panel.</sub>
124
+ <sub>Figure 3: **the full-screen canvas**. Look at the phase bar and progress, the roster (who is running, on which model), and the four views (people / tasks / artifacts / board) — one layer above the floating panel.</sub>
122
125
 
123
126
  ![Expert-team task dependency graph: tasks running in parallel along the DAG, including the repair/review rework loop](https://raw.githubusercontent.com/yangdcm/dsh-expert-team/main/docs/images/canvas-tasks.png)
124
127
 
125
- <sub>Figure 3: **the task dependency graph** — 11 tasks advance in parallel along the dependency DAG; 7 completed, 4 failed. A failure triggers `repair` plus **independent re-verification** (`repair-1 → review-2 → repair-2 → review-3`) until it passes or is honestly marked as needing revision — this is what the "rework does not converge" hard gate looks like in a real run.</sub>
128
+ <sub>Figure 4: **the task dependency graph** — 11 tasks advance in parallel along the dependency DAG; 7 completed, 4 failed. A failure triggers `repair` plus **independent re-verification** (`repair-1 → review-2 → repair-2 → review-3`) until it passes or is honestly marked as needing revision — this is what the "rework does not converge" hard gate looks like in a real run.</sub>
126
129
 
127
130
  ![Expert-team quality-gate violation banner: a missing spec boundary blocked by plugin code, not by a prompt](https://raw.githubusercontent.com/yangdcm/dsh-expert-team/main/docs/images/panel-gate.png)
128
131
 
129
- <sub>Figure 4: **gate violations**. Look at the banner at the top — the violation and its refusal reason
132
+ <sub>Figure 5: **gate violations**. Look at the banner at the top — the violation and its refusal reason
130
133
  (e.g. "SPEC.md's boundary section has entered `implement` but still has no 'expected rejection' row")
131
134
  is decided by `lib/interception.js`, hooked onto the host's `tools/post-execute` waterfall, and surfaced
132
135
  immediately. This is code, not a prompt reminder.</sub>
133
136
 
134
137
  ![Expert-team overlay: role members, the model each one uses, task detail and artifact preview](https://raw.githubusercontent.com/yangdcm/dsh-expert-team/main/docs/images/panel-live.png)
135
138
 
136
- <sub>Figure 5: **the roster**. Look at the member list — who is running, on which model, and what it is doing;
139
+ <sub>Figure 6: **the roster**. Look at the member list — who is running, on which model, and what it is doing;
137
140
  expand a member for its tasks and artifacts. Models are configurable per role; heterogeneous models are used for cross-checking.</sub>
138
141
 
139
142
  ![Expert-team phase progress: current and completed phases, plus the artifact body written at that phase](https://raw.githubusercontent.com/yangdcm/dsh-expert-team/main/docs/images/panel-flow.png)
140
143
 
141
- <sub>Figure 6: **phases and artifacts**. Look at the phase bar and the preview pane — the current phase, the phases
144
+ <sub>Figure 7: **phases and artifacts**. Look at the phase bar and the preview pane — the current phase, the phases
142
145
  already passed, and the actual body of the artifact written in that phase (artifacts are the single source of truth; the overlay is just a view of them).</sub>
143
146
 
144
147
  ![The expert-team section inside the official DeepSeek Harness settings page: 18 settings, Chinese labels, applied on change](https://raw.githubusercontent.com/yangdcm/dsh-expert-team/main/docs/images/settings.png)
145
148
 
146
- <sub>Figure 7: **settings**. Look at the official `Settings → Expert team` page — 18 settings, Chinese labels,
149
+ <sub>Figure 8: **settings**. Look at the official `Settings → Expert team` page — 18 settings, Chinese labels,
147
150
  **saved on change and applied immediately** (caps, rounds, the tier gate and the oscillation detector are recomputed
148
151
  in-process). Values live in the host namespace `expert-team`, so they travel with the plugin market's backup/restore.</sub>
149
152
 
@@ -341,7 +344,9 @@ Full command list (`/team codeindex` code index, `/team limit` quotas, `/team se
341
344
 
342
345
  **Does it work without the "Expert team mode" preset?** Yes. `/team` is a host-plane command and runs under any preset; it then falls back to the generic `subagent` (role personas go into the prompt), losing only the configuration-level boundaries (`toolFilter` / `maxDepth: 1`).
343
346
 
344
- **Why is the overlay/canvas slow to open?** See [Troubleshooting](#troubleshooting) — since 1.3.5 `/state` no longer reads every sub-session log in full; upgrade to ≥ 1.3.5 and restart `dsh web`.
347
+ **Why is the overlay/canvas slow to open?** See [Troubleshooting](#troubleshooting)
348
+
349
+ **A role subagent fails with `does not support reasoning effort`?** That is the **session route's model** not declaring `reasoningEfforts` — **not this plugin**: the host compares the requested effort against the model's published efforts **before any network I/O** and rejects a mismatch. Fix: add `reasoningEfforts` (`off/low/high/max`) to that model's entry under `agent-default-model` in `~/.dsh/settings.yaml`, or switch the session to an official route. **Note:** this preset declares **`high` for 8 roles and `low` for 4** — with none declared, **every** role that carries an effort is rejected ("only `low` fails" is an illusion: whoever is dispatched first reports first). The plugin runs a **one-line preflight warning** at load (warn only, never blocks). — since 1.3.5 `/state` no longer reads every sub-session log in full; upgrade to ≥ 1.3.5 and restart `dsh web`.
345
350
 
346
351
  ## Glossary
347
352
 
package/README.md CHANGED
@@ -20,30 +20,14 @@
20
20
 
21
21
  > 零运行时依赖。推荐同时装 **Hindsight**(跨项目记忆)—— 见[依赖与推荐插件](#依赖与推荐插件--dependencies-and-recommended-plugins)。
22
22
 
23
- ## 速览 / At a glance
24
-
25
- | 项目 | 值 |
26
- |---|---|
27
- | 包名 | `@yangdcm/dsh-expert-team`(npm 公开包) |
28
- | 仓库 | <https://github.com/yangdcm/dsh-expert-team> |
29
- | 宿主 | [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) ≥ **0.1.5-rc.1**(`web` profile) |
30
- | 运行时依赖 | **无**(`dependencies: {}`;`lib/` 只 import 同目录文件与 Node 内建) |
31
- | Node.js | ≥ 20 |
32
- | License | MIT |
33
- | 安装(一行) | `dsh plugin --profile web add @yangdcm/dsh-expert-team` |
34
- | 上手(一行) | 会话切到「专家团模式」→ `/team 做一个带登录的支付模块` |
35
- | 过程产物 | `<你的工作区>/team/<run-id>/`(`SPEC.md` · `PLAN.md` · `TASKS.json` · `REVIEW.md` · `TEST.md` · `SUMMARY.md` …) |
36
- | 本机数据 | `$DSH_HOME/expert-team/`(`settings.json` · `LEARNINGS.md` · `session-runs.json`) |
37
-
38
- > dsh 插件 · DeepSeek Harness 多智能体(multi-agent)编排器:角色化 subagent 团队 · 依赖 DAG 并行 · 阶段门控 · 质量门禁 · 工件留痕。
39
- >
40
- > 给 LLM / 检索用的索引:[`llms.txt`](https://github.com/yangdcm/dsh-expert-team/blob/main/llms.txt)
41
-
42
23
  ## 为谁而做
43
24
 
44
25
  **给中小团队与个人接单者的一支「完整技术部」** —— 不用招人、不用攒团队:一句话拉起产品、架构、调研、
45
26
  UI/UX、前后端、数据、安全、评审、测试、运维、文档这 12 个岗位,按 9 阶段门控流程交付,
46
27
  实现者直接改你的代码库,全程留痕成可复核的工件。
28
+ ![为谁而做:给中小团队与个人接单者的一支完整技术部 —— 产品经理/架构师/技术调研/UI/UX/后端/前端/数据/安全审计/代码评审/测试/运维/技术文档 12 个岗位编织进 9 阶段门控流水线](https://raw.githubusercontent.com/yangdcm/dsh-expert-team/main/docs/images/who-is-it-for.svg)
29
+
30
+ <sub>图 1:**为谁而做**。三类对象(中小公司内部工具与产品迭代 · 个人接单 / 外包交付 · 独立开发者做完整项目)共用同一套做法:把 12 个技术部岗位编织进一条 9 阶段门控流水线。注意底部那条橙色带 —— **你(lead)与 12 个岗位不在同一层**:你把关产品级与范围级决策,其余由团队推进。</sub>
47
31
 
48
32
  | 技术部岗位 | 角色 | 在这个流程里做什么 |
49
33
  |---|---|---|
@@ -67,9 +51,28 @@ UI/UX、前后端、数据、安全、评审、测试、运维、文档这 12
67
51
 
68
52
  ![专家团 9 阶段门控流水线:澄清→调研→设计→规格评审(硬门)→方案确认→实现(依赖 DAG 并行)→审查→测试→交付](https://raw.githubusercontent.com/yangdcm/dsh-expert-team/main/docs/images/pipeline.svg)
69
53
 
70
- <sub>图 1:9 阶段门控流水线。「规格评审」是**硬门** —— SPEC.md 的「边界与禁止项」没填就不放行(`lib/interception.js`);「方案确认」是默认开启的**确认门**(`identity.keepPlanGate`,可在设置里关掉);「实现」阶段按依赖 DAG **并行扇出**,多个实现者同时开工、各自只改自己那份文件。</sub>
54
+ <sub>图 2:9 阶段门控流水线。「规格评审」是**硬门** —— SPEC.md 的「边界与禁止项」没填就不放行(`lib/interception.js`);「方案确认」是默认开启的**确认门**(`identity.keepPlanGate`,可在设置里关掉);「实现」阶段按依赖 DAG **并行扇出**,多个实现者同时开工、各自只改自己那份文件。</sub>
71
55
 
72
56
  ---
57
+ ## 速览 / At a glance
58
+
59
+ | 项目 | 值 |
60
+ |---|---|
61
+ | 包名 | `@yangdcm/dsh-expert-team`(npm 公开包) |
62
+ | 仓库 | <https://github.com/yangdcm/dsh-expert-team> |
63
+ | 宿主 | [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) ≥ **0.1.5-rc.1**(`web` profile) |
64
+ | 运行时依赖 | **无**(`dependencies: {}`;`lib/` 只 import 同目录文件与 Node 内建) |
65
+ | Node.js | ≥ 20 |
66
+ | License | MIT |
67
+ | 安装(一行) | `dsh plugin --profile web add @yangdcm/dsh-expert-team` |
68
+ | 上手(一行) | 会话切到「专家团模式」→ `/team 做一个带登录的支付模块` |
69
+ | 过程产物 | `<你的工作区>/team/<run-id>/`(`SPEC.md` · `PLAN.md` · `TASKS.json` · `REVIEW.md` · `TEST.md` · `SUMMARY.md` …) |
70
+ | 本机数据 | `$DSH_HOME/expert-team/`(`settings.json` · `LEARNINGS.md` · `session-runs.json`) |
71
+
72
+ > dsh 插件 · DeepSeek Harness 多智能体(multi-agent)编排器:角色化 subagent 团队 · 依赖 DAG 并行 · 阶段门控 · 质量门禁 · 工件留痕。
73
+ >
74
+ > 给 LLM / 检索用的索引:[`llms.txt`](https://github.com/yangdcm/dsh-expert-team/blob/main/llms.txt)
75
+
73
76
 
74
77
  ## 30 秒看懂
75
78
 
@@ -109,27 +112,27 @@ $ /team 做一个带登录的支付模块
109
112
 
110
113
  ![专家团全屏画布:阶段条与进度、角色化 subagent 团队编制(谁在跑、用哪个模型)](https://raw.githubusercontent.com/yangdcm/dsh-expert-team/main/docs/images/canvas.png)
111
114
 
112
- <sub>图 2:**全屏画布**。看阶段条与进度、团队编制(谁在跑、用哪个模型)、以及 `人 / 事 / 料 / 盘` 四个视角 —— 比浮层更完整的一层视图。</sub>
115
+ <sub>图 3:**全屏画布**。看阶段条与进度、团队编制(谁在跑、用哪个模型)、以及 `人 / 事 / 料 / 盘` 四个视角 —— 比浮层更完整的一层视图。</sub>
113
116
 
114
117
  ![专家团任务依赖图:任务按依赖 DAG 并行,含 repair 与 review 的返工闭环](https://raw.githubusercontent.com/yangdcm/dsh-expert-team/main/docs/images/canvas-tasks.png)
115
118
 
116
- <sub>图 3:**任务依赖图** —— 11 个任务按依赖 DAG 并行推进;7 个完成、4 个失败。失败会触发 `repair` 与**独立复验**(`repair-1 → review-2 → repair-2 → review-3`),直到通过或被如实判为需修订 —— 这就是「返工不收敛」的硬门禁在真实运行里的样子。</sub>
119
+ <sub>图 4:**任务依赖图** —— 11 个任务按依赖 DAG 并行推进;7 个完成、4 个失败。失败会触发 `repair` 与**独立复验**(`repair-1 → review-2 → repair-2 → review-3`),直到通过或被如实判为需修订 —— 这就是「返工不收敛」的硬门禁在真实运行里的样子。</sub>
117
120
 
118
121
  ![专家团质量门禁违规实时横幅:规格边界未填即被插件代码拦下](https://raw.githubusercontent.com/yangdcm/dsh-expert-team/main/docs/images/panel-gate.png)
119
122
 
120
- <sub>图 4:**门禁违规**。看顶部那条横幅 —— 违规项与拒绝理由(例如"SPEC.md 的边界章节已进入 `implement` 但仍无任何一行填写")由 `lib/interception.js` 挂在宿主 `tools/post-execute` 上当场判出后推出,不是提示词提醒。</sub>
123
+ <sub>图 5:**门禁违规**。看顶部那条横幅 —— 违规项与拒绝理由(例如"SPEC.md 的边界章节已进入 `implement` 但仍无任何一行填写")由 `lib/interception.js` 挂在宿主 `tools/post-execute` 上当场判出后推出,不是提示词提醒。</sub>
121
124
 
122
125
  ![专家团浮层:角色成员列表、各自使用的模型、任务详情与工件预览](https://raw.githubusercontent.com/yangdcm/dsh-expert-team/main/docs/images/panel-live.png)
123
126
 
124
- <sub>图 5:**角色编制**。看成员列表 —— 谁在跑、用哪个模型、当前在做什么;展开任一成员可看它的任务与产物。模型可按角色分别配置,异构模型用于交叉验证。</sub>
127
+ <sub>图 6:**角色编制**。看成员列表 —— 谁在跑、用哪个模型、当前在做什么;展开任一成员可看它的任务与产物。模型可按角色分别配置,异构模型用于交叉验证。</sub>
125
128
 
126
129
  ![专家团阶段推进视图:当前阶段、已过阶段与该阶段的工件正文](https://raw.githubusercontent.com/yangdcm/dsh-expert-team/main/docs/images/panel-flow.png)
127
130
 
128
- <sub>图 6:**阶段与工件**。看阶段条与预览区 —— 当前阶段、已过阶段、以及该阶段真正写下的工件正文(工件是唯一真源,浮层只是它的视图)。</sub>
131
+ <sub>图 7:**阶段与工件**。看阶段条与预览区 —— 当前阶段、已过阶段、以及该阶段真正写下的工件正文(工件是唯一真源,浮层只是它的视图)。</sub>
129
132
 
130
133
  ![DeepSeek Harness 官方设置页里的「专家团」分节:18 个设置项、中文标签、改动即时生效](https://raw.githubusercontent.com/yangdcm/dsh-expert-team/main/docs/images/settings.png)
131
134
 
132
- <sub>图 7:**设置**。看官方 `设置 →「专家团」` 这一页 —— 18 个设置项、中文标签、**改动即保存并即时生效**(上限/轮次/档位门/振荡检测在进程内重算);值存在宿主命名空间 `expert-team`,随插件市场的备份/恢复一起走。</sub>
135
+ <sub>图 8:**设置**。看官方 `设置 →「专家团」` 这一页 —— 18 个设置项、中文标签、**改动即保存并即时生效**(上限/轮次/档位门/振荡检测在进程内重算);值存在宿主命名空间 `expert-team`,随插件市场的备份/恢复一起走。</sub>
133
136
 
134
137
  **输入框正上方还有一条常驻状态条**(client 槽 `conversation.input.dock`,id `expert-team-subagents`,order 200)—— 有子代理在跑时是琥珀色横幅「N 个子代理运行中」+ 最多 3 个角色名 + 一个跳动圆点,点击它直接打开团队面板;没有在跑时只剩一行暗灰字「无子代理在运行」,会话或状态尚未就绪时则完全不渲染(判据与页头徽章同一条:`/state` 的 `agents[].activity === 'running'`)。
135
138
 
@@ -297,7 +300,9 @@ dsh plugin --profile web add dshmarket
297
300
 
298
301
  **不切「专家团模式」preset 也能用吗?** 能。`/team` 是 host 平面命令,任何预设下都能跑;此时退回通用 `subagent`(角色人设写进 prompt),少的是配置层的边界保证(`toolFilter` / `maxDepth: 1`)。
299
302
 
300
- **浮层/画布打开很慢?** 见[排障](#排障) —— 1.3.5 起 `/state` 不再逐条全量读子会话日志;升级到 ≥ 1.3.5 后重启 `dsh web` 即可。
303
+ **浮层/画布打开很慢?** 见[排障](#排障)
304
+
305
+ **角色子代理起不来、报 `does not support reasoning effort`?** 这是**会话路由的模型没声明** `reasoningEfforts` 造成的,**不是本插件的问题** —— 宿主在**任何网络 I/O 之前**就把"请求的 effort"与"该模型公布的 efforts"比对,不匹配即拒。修法:在 `~/.dsh/settings.yaml` 的 `agent-default-model` 条目里给该模型补 `reasoningEfforts`(`off/low/high/max`),或把会话切到官方路由。**注意**:本 preset 里 **8 个角色声明 `high`、4 个声明 `low`** ⇒ 漏声明时**任何**带 effort 的角色都会被拒("只有 low 失败"是假象:先派谁先报谁)。插件加载时会**预检并告警一行**(只告警、不阻断)。 —— 1.3.5 起 `/state` 不再逐条全量读子会话日志;升级到 ≥ 1.3.5 后重启 `dsh web` 即可。
301
306
 
302
307
  ## 术语 / Glossary
303
308
 
package/client.js CHANGED
@@ -2194,6 +2194,11 @@ window.__ModuleLoader__.load({
2194
2194
  wfRunBlock,
2195
2195
  peopleNoteBlock,
2196
2196
  roster.length ? roster : h('div', { className: 'exp-empty' }, t('(暂无成员)', '(no members)')),
2197
+ // 有界化(2026-09-15):实时路径**不读**大日志解析角色 ⇒ 未知角色是"**待解析**",
2198
+ // 与下面那段"确实解析不出角色"是**两件事**(本仓纪律:两种零必须分得开,别混成一句话)。
2199
+ (data && Number(data.rolesPending) > 0) ? h('div', { className: 'exp-legend', key: 'roles-pending' },
2200
+ t('另有 ' + data.rolesPending + ' 条子代理的角色**待解析**(实时路径不读大日志,后台低频解析中;"待解析"≠"解析不出来")',
2201
+ 'Roles for ' + data.rolesPending + ' subagent(s) are still **pending** (the live path does not read large logs; pending is not the same as unresolvable)')) : null,
2197
2202
  // 真实性提示:活子代理里没被名册认领的分两类,必须分开说 ——
2198
2203
  // ① 同角色的重复派工/历史 leg(**有角色**,只是每个角色只展示 1 个成员)
2199
2204
  // ② 真的解析不出角色的(label 为空、子会话日志不可读、事件流里也没有派工 label)
@@ -2500,10 +2505,25 @@ window.__ModuleLoader__.load({
2500
2505
  if (!sid || typeof fetch === 'undefined') return
2501
2506
  stateHubFetch(liveUrl(sid), liveDeliver)
2502
2507
  }
2508
+ // 会话 id → 角色名(来自 /state 的 agents[],**只用于把 id 翻译成可读名字**)。
2509
+ // 为什么不参与判定:判定必须与页头同源(宿主会话态)。这份映射只是显示层的润色,
2510
+ // 缺了它就回落 id 前 8 位;它来自本来就有的 /state 拉取(页头徽章/面板),**不新增任何请求**。
2511
+ var liveRoles = {}
2512
+ function harvestRoles(d) {
2513
+ try {
2514
+ var arr = (d && Array.isArray(d.agents)) ? d.agents : null
2515
+ if (!arr) return
2516
+ for (var i = 0; i < arr.length; i++) {
2517
+ var a = arr[i]
2518
+ if (a && a.id && a.role) liveRoles[String(a.id)] = String(a.role)
2519
+ }
2520
+ } catch (e) {}
2521
+ }
2503
2522
  function liveDeliver(d) {
2504
2523
  if (!d) return
2505
2524
  liveStore.data = d
2506
2525
  try { harvestActivity(d) } catch (e) {}
2526
+ try { harvestRoles(d) } catch (e) {}
2507
2527
  try { stateHub.busy = teamBusy(d) } catch (e) {}
2508
2528
  liveStore.subs.forEach(function (f) { try { f(d) } catch (e) {} })
2509
2529
  }
@@ -2512,30 +2532,81 @@ window.__ModuleLoader__.load({
2512
2532
  return Object.keys(m).some(function (k) { var v = m[k]; return v && typeof v === 'object' && (v.activity === 'running' || v.shortStatus === 'running') })
2513
2533
  }
2514
2534
  // ── 子代理运行状态条的**唯一**判据(纯函数,无 React、无 DOM:便于单测直接断言)──────
2515
- // 为什么用 /state `agents[]` 而不是别的:它由 host 的 `subagents.listChildren` 产出,
2516
- // `activity === 'running'` 正是页头徽章用的同一个判据(两种指示同源,不会互相打架)。
2517
- // 两种"零"必须分清:`agents` 缺键/非数组(老 host、ok:false、?section=summary)是"未知",
2518
- // 只有拿到数组且里面没有 running 才是"确实没有人在跑"。未知时显示占位符,不谎报"无人在跑"。
2519
- /** 从一份 /state 负载里取出 activity === 'running' 的子代理行(非数组/缺键 ⇒ 空数组)。 */
2520
- function runningAgents(d) {
2521
- var arr = (d && Array.isArray(d.agents)) ? d.agents : []
2522
- return arr.filter(function (a) { return a && String(a.activity || '') === 'running' })
2535
+ // 与页头「N 个子代理」**同源**:宿主会话态(host session store)里 `running` 为真的子代理。
2536
+ // 之前的版本读插件自己的 `/state` → `agents[].activity === 'running'`,那是**错的**:服务端把
2537
+ // 子代理语料的归属会话解析成「当前 run 的 ownerSession」(`lib/command.js` `peopleSid`),
2538
+ // 客户端传的 sessionId 会被忽略 ⇒ 新 run 还没落 `STATE.json` 时视图会落到同工作区的旧 run 上,
2539
+ // 面板拿到别人的(已冷的)子代理,状态条就谎报「无子代理在运行」。
2540
+ // 两种"零"必须分清:拿不到会话态(null 快照 / 无 sid)是"未知"⇒ 不渲染;只有拿到会话态且
2541
+ // 里面没有 running 的子代理后代,才是"确实没有人在跑"。
2542
+
2543
+ /**
2544
+ * 正在运行的子代理 id(宿主会话态的 `running` 字段,**与页头「N 个子代理」同源**)。
2545
+ *
2546
+ * 为什么不再用插件自己的 `/state` → `agents[].activity`:服务端把子代理语料的**归属会话**
2547
+ * 解析成「当前 run 的 ownerSession」(`lib/command.js` 的 `peopleSid`),于是**客户端传的
2548
+ * sessionId 会被忽略**。真机实测的翻车场景:一个刚开跑、还没落 `STATE.json` 的新 run,
2549
+ * 在 `newestRun` 按 `updatedAt` 排序时输给同工作区一个 13 天前的旧 run,于是本会话明明
2550
+ * 有子代理在跑,面板却拿到旧 run 的 82 个**已冷**子代理(`activity:'inactive'`)⇒ 状态条
2551
+ * 谎报「无子代理在运行」。改用宿主会话态后,既与页头同源,又不再经过那条会串会话的解析。
2552
+ *
2553
+ * 两个集合都要:`byId` 给 `running`/`origin`/`parentId`,`items` 给直接子会话
2554
+ * (`parentSessionId` + `origin:'subagent'`)—— 只靠其一都会漏(与页头同一套数据源)。
2555
+ * 计数按**会话是当前会话的子代理后代**判定(任一深度),不是只数直接子级。
2556
+ */
2557
+ function runningSubagentIds(sessions, sid) {
2558
+ var out = []
2559
+ if (!sid || !sessions || typeof sessions !== 'object') return out
2560
+ var byId = sessions.byId || {}
2561
+ var items = Array.isArray(sessions.items) ? sessions.items : []
2562
+ var seen = {}
2563
+ function consider(id, running) {
2564
+ if (!id) return
2565
+ var k = String(id)
2566
+ if (seen[k] || !running) return
2567
+ seen[k] = 1
2568
+ out.push(k)
2569
+ }
2570
+ // ① 直接子会话:列表项自带 parentSessionId + origin
2571
+ items.forEach(function (s) {
2572
+ if (!s) return
2573
+ if (s.origin !== 'subagent') return
2574
+ if (String(s.parentSessionId || '') !== String(sid)) return
2575
+ consider(s.sessionId, s.running === true)
2576
+ })
2577
+ // ② 任一深度的后代:沿 byId 的 parentId 往上走,只有 origin==='subagent' 才继续
2578
+ Object.keys(byId).forEach(function (key) {
2579
+ var e = byId[key]
2580
+ if (!e || e.origin !== 'subagent') return
2581
+ var cur = e
2582
+ var guard = 0
2583
+ while (cur && cur.parentId !== undefined && guard++ < 64) {
2584
+ if (String(cur.parentId) === String(sid)) { consider(key, e.running === true); return }
2585
+ var up = byId[String(cur.parentId)]
2586
+ if (!up || up.origin !== 'subagent') return
2587
+ cur = up
2588
+ }
2589
+ })
2590
+ return out
2523
2591
  }
2592
+
2524
2593
  /**
2525
2594
  * 状态条视图模型(纯函数)。返回值:
2526
- * null → 不渲染(没有会话 / 还没拿到负载 / 拿不到 agents 块)
2595
+ * null → 不渲染(没有会话 / 会话态还没到)
2527
2596
  * { kind: 'idle', text } → 一行灰字:确实没有子代理在跑
2528
2597
  * { kind: 'busy', n, text, names } → 运行中横幅
2529
- * 角色名取不到就回落到 id 前 8 位(如实显示"这是谁",不编造角色名)。
2598
+ * `names` 取不到角色名时回落 id 前 8 位(如实显示"这是谁",不编造角色名)。
2530
2599
  */
2531
- function subagentBarModel(d, hasSession) {
2532
- if (!hasSession) return null
2533
- if (!d || d.ok !== true || !Array.isArray(d.agents)) return null
2534
- var run = runningAgents(d)
2600
+ function subagentBarModel(sessions, sid) {
2601
+ if (!sid || !sessions || typeof sessions !== 'object') return null
2602
+ if (!sessions.byId && !Array.isArray(sessions.items)) return null
2603
+ // `/state` role 名(可选):只用于把 id 翻成更好读的角色名,**不参与任何判定**
2604
+ var roles = (sessions.__roles && typeof sessions.__roles === 'object') ? sessions.__roles : {}
2605
+ var run = runningSubagentIds(sessions, sid)
2535
2606
  if (!run.length) return { kind: 'idle', n: 0, text: t('无子代理在运行', 'No subagents running'), names: [] }
2536
- var names = run.slice(0, 3).map(function (a) {
2537
- var nm = roleLabel(a.role)
2538
- return nm || String(a.id || '').slice(0, 8)
2607
+ var names = run.slice(0, 3).map(function (id) {
2608
+ var nm = roleLabel(roles[id])
2609
+ return nm || String(id).slice(0, 8)
2539
2610
  })
2540
2611
  var more = run.length > 3 ? ' +' + (run.length - 3) : ''
2541
2612
  return {
@@ -2555,6 +2626,7 @@ window.__ModuleLoader__.load({
2555
2626
  // 收到一份只有 summary 的负载就把已知的成员/活动抹掉。
2556
2627
  liveStore.data = Object.assign({}, liveStore.data || {}, d)
2557
2628
  try { harvestActivity(liveStore.data) } catch (e) {}
2629
+ try { harvestRoles(d) } catch (e) {}
2558
2630
  try { stateHub.busy = teamBusy(liveStore.data) } catch (e) {}
2559
2631
  liveStore.subs.forEach(function (f) { try { f(liveStore.data) } catch (e) {} })
2560
2632
  }
@@ -2627,13 +2699,47 @@ window.__ModuleLoader__.load({
2627
2699
  /**
2628
2700
  * 「子代理运行中」状态条(注册进 conversation.input.dock ⇒ 输入框正上方)。
2629
2701
  * 常驻:运行中给醒目横幅,无人运行给一行灰字(用户要求"能一眼区分有没有在跑")。
2702
+ * 判据 = 宿主会话态(与页头「N 个子代理」**同源**),**不再**读插件自己的 `/state`:
2703
+ * 那条路会把子代理语料的归属会话解析成「当前 run 的 ownerSession」,客户端传的 sessionId
2704
+ * 会被忽略 ⇒ 新 run 还没落 `STATE.json` 时视图落到旧 run,状态条谎报「无子代理在运行」。
2705
+ * 取数全程 try/catch:宿主 API 形状一变就退化成"不渲染这一条",绝不把界面炸掉。
2630
2706
  */
2631
2707
  function SubagentBar(props) {
2632
- var sid = useCurrentSessionId()
2633
- var barSessionId = (props && props.sessionId) || sid
2634
- var live = useLiveState(barSessionId)
2708
+ // 会话 id 的解法与之前一致;`useCurrentSessionId()` **无条件**先调(钩子顺序必须稳定,
2709
+ // 不能因为这一帧有没有 props.sessionId 就少调一次)。
2710
+ var fallbackSid = useCurrentSessionId()
2635
2711
  ensureCss()
2636
- var m = subagentBarModel(live, !!barSessionId)
2712
+ var sid = (props && props.sessionId) || fallbackSid
2713
+ var snap = null
2714
+ var useSessions = props && props.useSessions
2715
+ if (typeof useSessions === 'function') {
2716
+ // 标准 prop:宿主把会话 store 的 hook 直接递给槽组件(页头「N 个子代理」用的同一份数据)。
2717
+ try { snap = useSessions(function (s) { return s }) } catch (e) { snap = null }
2718
+ } else {
2719
+ // 降级:宿主没递标准 prop(API 形状变了)时,直接向 `ctx.sessions` 的**同一个**会话
2720
+ // store 要列表快照(`{ ids, byId, current, … }`)。任何一步拿不到或抛错 ⇒ 保持 null
2721
+ // ⇒ subagentBarModel 返回 null ⇒ 不渲染(宁可不显示,也不显示"别人的会话"的子代理数)。
2722
+ try {
2723
+ var svc = ctxRoot && ctxRoot.sessions ? ctxRoot.sessions : null
2724
+ var list = svc && svc.list ? svc.list : null
2725
+ var st = (list && typeof list.getSnapshot === 'function') ? list.getSnapshot() : null
2726
+ if (!st && svc && typeof svc.getSnapshot === 'function') st = svc.getSnapshot()
2727
+ if (st && typeof st === 'object') snap = st
2728
+ } catch (e) { snap = null }
2729
+ }
2730
+ // 角色映射只影响**命名**,不参与判定:把它挂在交给 `subagentBarModel` 的输入上。
2731
+ // ⚠️ **不能写进 `snap`**:那是宿主会话 store 的共享/缓存快照,写进去会污染页头等别的视图。
2732
+ // 因此需要时才做一层浅拷贝(只复制自有键),再挂 `__roles`;任何一步出错都退回原快照 ⇒
2733
+ // 最坏也只是回落 id 前 8 位,绝不把状态条弄没。
2734
+ var modelInput = snap
2735
+ try {
2736
+ if (snap && typeof snap === 'object' && snap.__roles !== liveRoles) {
2737
+ modelInput = {}
2738
+ for (var k in snap) { if (Object.prototype.hasOwnProperty.call(snap, k)) modelInput[k] = snap[k] }
2739
+ modelInput.__roles = liveRoles
2740
+ }
2741
+ } catch (e) { modelInput = snap }
2742
+ var m = subagentBarModel(modelInput, sid)
2637
2743
  if (!m) return null
2638
2744
  if (m.kind === 'idle') return h('div', { className: 'exp-subbusy-idle' }, esc(m.text))
2639
2745
  function openPanel() {
@@ -2782,11 +2888,16 @@ window.__ModuleLoader__.load({
2782
2888
  // suite assert text safety without booting the overlay in a browser.
2783
2889
  exports._live = { esc: esc, tierBadge: tierBadge, TIER_LABELS_ZH: TIER_LABELS_ZH, settingsFormModel: settingsFormModel }
2784
2890
  // 测试钩子(沿用 `_live` 的约定):状态条的纯函数可脱离浏览器直接断言。
2785
- exports._subagentBar = { subagentBarModel: subagentBarModel, runningAgents: runningAgents }
2891
+ exports._subagentBar = { subagentBarModel: subagentBarModel, runningSubagentIds: runningSubagentIds }
2786
2892
  exports.inject = ['slots', 'sessions', 'remote', 'uiSession', 'uiConversation', 'locale']
2787
2893
  var ctxUISession = null
2894
+ // 宿主 ctx 本体:只给状态条**降级**取数用(标准 prop `useSessions` 拿不到时,退到
2895
+ // `ctx.sessions.list` 的同一份会话 store 快照)。声明位置与 `ctxUISession` 同理:
2896
+ // var 提升到工厂作用域,组件在 apply() 之后才渲染,读到的一定是已赋值的引用。
2897
+ var ctxRoot = null
2788
2898
  exports.apply = function (ctx) {
2789
2899
  console.log('[dsh-expert-team] client apply() called')
2900
+ try { ctxRoot = ctx || null } catch (e) { ctxRoot = null }
2790
2901
  try { ctxUISession = ctx && ctx.uiSession ? ctx.uiSession : null } catch (e) { ctxUISession = null }
2791
2902
  try {
2792
2903
  // N2:待拍板 pendingInteraction 发布(composer select 据此接管)