@mstar-harness/dsh 3.8.1 → 3.8.3

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.
Files changed (78) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +16 -6
  3. package/README.zh.md +16 -6
  4. package/dist/client/panel/engine-status-client.d.ts +84 -6
  5. package/dist/client/panel/graph/project-graph.d.ts +26 -13
  6. package/dist/client/panel/guards.d.ts +41 -1
  7. package/dist/client/panel/locale.d.ts +1 -1
  8. package/dist/client/panel/pages/AgentListPage.d.ts +1 -1
  9. package/dist/client/panel/sidebar.d.ts +3 -2
  10. package/dist/client/panel/state-section.d.ts +25 -3
  11. package/dist/client/panel/use-mstar-engine-status.d.ts +28 -4
  12. package/dist/client.js +346 -48
  13. package/dist/engine-status-endpoint.d.ts +85 -8
  14. package/dist/engine-status-store.d.ts +91 -1
  15. package/dist/engine-status-wire.d.ts +9 -0
  16. package/dist/gates/_shared.d.ts +61 -9
  17. package/dist/gates/adapter.d.ts +32 -2
  18. package/dist/gates/agent-flow.d.ts +312 -60
  19. package/dist/gates/catalog.d.ts +58 -37
  20. package/dist/gates/dispatch.d.ts +11 -2
  21. package/dist/gates/goal-bridge.d.ts +10 -130
  22. package/dist/gates/plan-mode-bridge.d.ts +20 -11
  23. package/dist/gates/role-persona.d.ts +16 -0
  24. package/dist/gates/steering.d.ts +41 -0
  25. package/dist/gates/workflow-ledger.d.ts +31 -4
  26. package/dist/gates/workflow-selection.d.ts +41 -20
  27. package/dist/index.js +1206 -392
  28. package/dist/types.d.ts +36 -11
  29. package/harness-commands/amazing-e2e-check.md +10 -0
  30. package/harness-commands/amazing-pr-review.md +2 -0
  31. package/harness-commands/codebase-audit.md +2 -0
  32. package/harness-commands/iteration-drive.md +1 -1
  33. package/harness-skills/mstar-artifacts/references/plan-files-and-reports.md +2 -2
  34. package/harness-skills/mstar-artifacts/references/plan-quality-bar.md +14 -12
  35. package/harness-skills/mstar-artifacts/templates/plan.main.md +19 -6
  36. package/harness-skills/mstar-audit/SKILL.md +5 -5
  37. package/harness-skills/mstar-coding-behavior/SKILL.md +8 -8
  38. package/harness-skills/mstar-dispatch-gates/SKILL.md +9 -7
  39. package/harness-skills/mstar-e2e/SKILL.md +40 -0
  40. package/harness-skills/mstar-e2e/references/report-template.md +32 -0
  41. package/harness-skills/mstar-engine-legacy/references/qc-seat-n-restatements.md +3 -3
  42. package/harness-skills/mstar-harness-core/SKILL.md +14 -1
  43. package/harness-skills/mstar-host/SKILL.md +3 -1
  44. package/harness-skills/mstar-host/references/_shared/host-role-binding-core.md +1 -1
  45. package/harness-skills/mstar-host/references/cursor.md +1 -1
  46. package/harness-skills/mstar-host/references/dsh-workflow-scripts.md +424 -0
  47. package/harness-skills/mstar-host/references/dsh.md +180 -51
  48. package/harness-skills/mstar-host/references/kimi.md +3 -3
  49. package/harness-skills/mstar-host/references/omp.md +3 -3
  50. package/harness-skills/mstar-host/references/parallel-dispatch.md +6 -6
  51. package/harness-skills/mstar-host/references/zcode.md +4 -4
  52. package/harness-skills/mstar-iteration/SKILL.md +1 -1
  53. package/harness-skills/mstar-iteration/references/phase-1-prepare.md +2 -2
  54. package/harness-skills/mstar-iteration/references/phase-2-worktree-lease.md +3 -3
  55. package/harness-skills/mstar-review-qc/SKILL.md +4 -4
  56. package/harness-skills/mstar-review-qc/references/review-responsibility-boundaries.md +9 -7
  57. package/harness-skills/mstar-roles/SKILL.md +2 -0
  58. package/harness-skills/mstar-roles/references/_shared/leaf-executor-core.md +9 -0
  59. package/harness-skills/mstar-roles/references/ops-engineer.md +3 -0
  60. package/harness-skills/mstar-roles/references/project-manager/dispatch-and-assignment.md +12 -9
  61. package/harness-skills/mstar-roles/references/project-manager/qa-trigger-matrix.md +7 -5
  62. package/harness-skills/mstar-roles/references/project-manager/qc-and-residuals.md +2 -2
  63. package/harness-skills/mstar-roles/references/project-manager/routing-and-dev-allocation.md +2 -2
  64. package/harness-skills/mstar-roles/references/project-manager.md +4 -2
  65. package/harness-skills/mstar-roles/references/qa-engineer/acceptance-gate.md +12 -13
  66. package/harness-skills/mstar-roles/references/qa-engineer.md +5 -4
  67. package/harness-skills/mstar-roles/references/qc-specialist/deep-review-lenses.md +5 -5
  68. package/harness-skills/mstar-roles/references/qc-specialist/report-template.md +2 -0
  69. package/harness-skills/mstar-roles/references/qc-specialist/reviewer-checklist.md +1 -1
  70. package/harness-skills/mstar-roles/references/qc-specialist/reviewer-workflow.md +5 -4
  71. package/harness-skills/mstar-roles/references/qc-specialist-shared.md +3 -1
  72. package/harness-skills/mstar-sdd/SKILL.md +17 -9
  73. package/harness-skills/mstar-sdd/references/file-handoffs.md +40 -20
  74. package/harness-skills/mstar-sdd/references/implementer-continuation-prompt.md +9 -4
  75. package/harness-skills/mstar-sdd/references/implementer-prompt.md +11 -6
  76. package/harness-skills/mstar-sdd/references/sticky-implementer-session.md +4 -2
  77. package/harness-skills/mstar-sdd/references/task-reviewer-prompt.md +8 -4
  78. package/package.json +2 -2
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # Blob hashes (git hash-object) of each side as of the last confirmation that
3
3
  # both languages say the same thing (dsh i18n contract: a pair is three
4
4
  # sibling files; editing either side obligates re-confirming and re-recording).
5
- README.md: 03cc2063bf2602fbf16de9732324c0c18cf6a38c
6
- README.zh.md: ef7f524ab9146d87368d43670115b3ba3638333e
5
+ README.md: 38694c86b3d4bc15bb04dbcdb2a76756ceb2df72
6
+ README.zh.md: 74284ed74c76697032700864da84b7ec3e407be5
package/README.md CHANGED
@@ -14,7 +14,7 @@ How a dsh app consumes the plugin — install paths, configuration, what mounts
14
14
 
15
15
  The package ships as a workspace package (`workspaces: ["packages/*"]`) with the engine bundled into `dist/` at build time (`bun run build`; dist is gitignored). The install path is the **profile bundle**, added to the shipped `web` profile (`dsh --profile web` — the ready-made web app profile, `dsh web`), through the `dsh.bundle.patch` manifest — a patch layer mounted over the dsh-base defaults:
16
16
 
17
- **One-command CLI entry (recommended)** — `npx @mstar-harness/cli init --target dsh` installs the full capability in one go: it runs the two `dsh plugin --profile web add` installs below in order (the mstar bundle first, then `dsh-llm-fallbacks`), and `npx @mstar-harness/cli doctor --target dsh` reports each plugin row as `uninstalled` / `disabled` / `mounted`. It is the same two-command install, orchestrated; `--no-fallbacks` skips the second row (and with it the seeded roles — see What you get below).
17
+ **One-command CLI entry (recommended)** — `npx @mstar-harness/cli init --target dsh` installs the full capability in one go: it runs the two `dsh plugin --profile web add` installs below in order (the mstar bundle first, then `dsh-llm-fallbacks`), and `npx @mstar-harness/cli doctor --target dsh` reports each plugin row as `uninstalled` / `disabled` / `mounted` / `drifted` (`drifted` = the fallbacks row is installed at a version other than the pinned one; `init --target dsh` re-adds the pin, `doctor` exits non-zero until then). It is the same two-command install, orchestrated; `--no-fallbacks` skips the second row (and with it the seeded roles — see What you get below).
18
18
 
19
19
  **(a) Registry install (published form)** — the npm package carries the built `dist/` (no build step on install):
20
20
 
@@ -74,7 +74,9 @@ The shipped headless template auto-initializes on first use (`@deepseek-ai/dsh-b
74
74
  | `bundledSkillDir` | `string` | packaged `harness-skills/` mirror (package-relative) | Bundled skill root registered with the dsh skill-filesystem provider (`bundledSkillDir` semantics — scanned last, trusted). Defaults to the package's OWN `harness-skills/` mirror (synced by `bundle-assets`; gitignored) — package-relative, NOT cwd-anchored. An explicit value wins. |
75
75
  | `catalogTtlMs` | `number` | `60000` | Pre-step catalog cache refresh interval (ms): how often the per-workspace unified `mstar-engine` catalog row (watermark + iteration gate + workspace-state digest) re-reads `status.json` / the compass / the knowledge index. The hot path is a timestamp compare + cache hit between refreshes; a mid-session plan/compass/residual change lands within one interval. |
76
76
  | `workflowGate` | `'off' \| 'warn' \| 'ask' \| 'hard'` | `'warn'` | Workflow/ralph gate mode (see Gates → Workflow / ralph gate). `off` = pass-through with no verdict row; `warn` = advisory-only; `ask` = first-seen names route through the approval waterfall (P-c); `hard` = policy violations veto before any child starts. Default `warn` changes NO hard behavior — the gate is advisory-only unless the deployment opts into `ask`/`hard`. |
77
- | `workflowNames` | `string[]` | unset | Workflow name allowlist (P-a): `meta.name` values treated as KNOWN by the gate. Empty or absent ⇒ **every** name is unknown (documented — the gate is NOT "allow all" by omission). Ralph calls carry no `meta.name` — P-a never applies to them. |
77
+ | `workflowNames` | `string[]` | unset | Workflow name allowlist (P-a): `meta.name` values treated as KNOWN by the gate. Empty or absent ⇒ **every** name is unknown (documented — the gate is NOT "allow all" by omission). Ralph calls carry no `meta.name` — P-a never applies to them. Recommended for the mstar read-only fan-out path: `['mstar-qc-tri', 'mstar-audit-fanout', 'mstar-pr-seats']`. |
78
+
79
+ **Recommended operator overlay.** The three recommended `workflowNames` above cover the mstar read-only fan-out path (plan QC tri, large-repo `/codebase-audit`, `/amazing-pr-review deep`); listing them is an operator overlay in the profile layer, never an mstar default. With the shipped empty list every name is *unknown*, which under the default `workflowGate: 'warn'` is one `workflow.name.unknown` advisory the run survives. A production deployment that also wants unknown names vetoed sets `workflowGate: 'hard'`; the shipped default stays `'warn'`.
78
80
 
79
81
  `bundledSkillDir` defaults to the package's OWN `harness-skills/` mirror (see Skills mount) — an explicit Config value still wins. A relative override remains **cwd-anchored** (skill-filesystem `join()` semantics against the dsh process cwd), so deployments overriding the default should pass an **absolute path in the profile layer** (see `bundle/README.md`).
80
82
 
@@ -173,7 +175,7 @@ Persona delivery rides dsh's NATIVE `SubagentStartRequest.persona` slot (`@deeps
173
175
 
174
176
  ### Role seeds + adoption advisory
175
177
 
176
- When the optional `dsh-llm-fallbacks` capability is **mounted** (the second install command — see Install paths), the mstar plugin **zero-config declares the 13 `mode: subagent` mstar role seeds** into the fallbacks seed registry: persona = the `harness-agents/` mirror `description` (verbatim) + one mandatory-load guide line (`Load mstar-roles (references/<role-id>.md) first — identity comes before skills; load topic skills only when the Assignment activates them via its Skill presets field.`); a persona carrying the `{{...}}` interpolation hazard is skipped + warned, never declared. The declaration **merge-preserves the currently-seeded non-mstar ids** from the readback — e.g. the 7 omp-style preset roles the upstream package self-declares at its own apply: upstream `declare` REPLACES the whole registry, so without preservation a mstar-only batch would strip preset ids of their seeded annotations (rows remain, unseeded). The declaration re-fires idempotently on every fallbacks (re-)apply (HMR/fiber swap) — never from a one-shot latch — so either boot order (presets first or mstar first) converges to the same 20-id fully-seeded registry. Boot-time convergence is a bounded retry: the provider's seed write channel binds one macrotask after its apply, so a first-attempt reject with `seeds: settings service is unavailable` inside that apply window is retried (3 attempts across the provider's apply window) and a transient reject converges on its own; only if every attempt ultimately fails does the declaration log exactly one terminal error, while the advisory's decision-point re-declare stays available as the retry path. **No manual `roles.list` edit is required.**
178
+ When the optional `dsh-llm-fallbacks` capability is **mounted** (the second install command — see Install paths), the mstar plugin **zero-config declares the 13 `mode: subagent` mstar role seeds** into the fallbacks seed registry: persona = the `harness-agents/` mirror `description` (verbatim) + one mandatory-load guide line (`Load mstar-roles (references/<role-id>.md) first — identity comes before skills; load topic skills only when the Assignment activates them via its Skill presets field.`); a persona carrying the `{{...}}` interpolation hazard is skipped + warned, never declared. The declaration **merge-preserves the currently-seeded non-mstar ids** from the readback — e.g. the 5 upstream preset roles the upstream package self-declares at its own apply: upstream `declare` REPLACES the whole registry, so without preservation a mstar-only batch would strip preset ids of their seeded annotations (rows remain, unseeded). The declaration re-fires idempotently on every fallbacks (re-)apply (HMR/fiber swap) — never from a one-shot latch — so either boot order (presets first or mstar first) converges to the same 18-id fully-seeded registry. Boot-time convergence is a bounded retry: the provider's seed write channel binds one macrotask after its apply, so a first-attempt reject with `seeds: settings service is unavailable` inside that apply window is retried (3 attempts across the provider's apply window) and a transient reject converges on its own; only if every attempt ultimately fails does the declaration log exactly one terminal error, while the advisory's decision-point re-declare stays available as the retry path. **No manual `roles.list` edit is required.**
177
179
 
178
180
  A warn-only advisory pass (logger `mstar/fallbacks-advisory`) runs **once per apply** — attempted at apply and, when the fallbacks row mounts after `dsh` (the loader mounts entries concurrently), once at the first `subagent/start` decision point. With the service present, the pass FIRST awaits the idempotent re-declare (closing the boot race) then reads the EFFECTIVE state (`getEffectiveRoles`) and reports, bounded to **at most one warn per category**:
179
181
 
@@ -245,9 +247,17 @@ An advisory `agent/pre-step` waterfall listener appends ONE **`mstar-engine`** c
245
247
 
246
248
  The row is **digest-gated**: per agent+workspace it is injected once per turn and re-injected only when its rendered text changed — a 20-step turn shows the catalog once, not 20 times. The source shares ONE per-workspace cache entry, built at boot for an explicit `harnessDir` (else on the workspace's first pre-step) and TTL-refreshed (`catalogTtlMs`, default 60 s) — the hot path is a timestamp compare + Map lookup between refreshes, and a mid-session plan/compass/residual change lands within one interval.
247
249
 
248
- ## Agent-flow ledger (workflow rows)
250
+ ## Agent-flow ledger
251
+
252
+ The agent-flow ledger lives in the ACTIVE workflow dir — `{HARNESS_DIR}/workflows/<id>/agent-flow.jsonl` (JSON Lines, bounded to the most recent 500 events) — the same JSONL the catalog's `state.agentFlow` evidence reads. With no active lifecycle the record is SKIPPED with a one-time warn — never the harness root, never a terminal snapshot dir. It records the ACTUAL subagent dispatch and settle evidence (advisory and try/catch-contained — a failing ledger write never blocks a dispatch or a settle):
253
+
254
+ - **Dispatch** — one row per Assignment-shaped dispatch, recording the dispatch identity: `role` (normalized at the write boundary — trimmed, with ONE leading `@` stripped, so `@explore` and `explore` are one actor, not two), `planId`, the Assignment `Task N` tag as `taskId`, `taskCategory`, and the verdict.
255
+ - **Settle** — recorded ONLY for a REAL paired completion. A foreground call settles in the `tools/post-execute` listener; a `{ kind: 'background', jobId }` result is paired on the registry **`jobId`** (a jobs-registry key, never a child session id) and settles when `ctx.inject(['jobs'])` → `jobs.onJobDone` reports the terminal snapshot (`completed → ok` / `killed → denied` / `failed → error`, `durationMs` when available). Every paired settle carries the dispatch identity (the `Task N` tag stays `taskId`; `taskRef` is reserved for the registry job id and rides on a background settle only) plus an OPTIONAL `childId` — a foreground `runId`, or a background child id the catalog join already supplied. A missing or oversized optional id omits the FIELD only: the real completion still records, and an id is never truncated or re-keyed. Unpaired payloads record NOTHING — never a fabricated settle.
256
+ - **`subagent-link` (nonterminal)** — the child session id a dispatch actually started, published upstream as a PARENT-OWNED `subagent/catalog` session event (`label` = the delegation `description`; the continuable path appends it BEFORE the tool returns). A per-dispatch CALL WINDOW join — slot reservation at pre-execute, eligibility on a valid `background` / `continuable` result, an `eventAt(seq)` catch-up scan plus ONE live root-context `session/event` observer — correlates that catalog child back to the dispatch identity (`role` / `planId` / `taskId`, plus `taskRef` for a background one-shot) in the DISPATCH's own workflow dir. It is an IDENTITY record, **not** a completion: no `outcome`, no `verdict`, no `paired` marker. The join is apply-scoped — no whole-history cold scan and no `session/created` backfill, so a catalog written before apply can never label a newer dispatch — and a dispatch whose provider emits no catalog simply has no link row.
257
+
258
+ ### Workflow rows
249
259
 
250
- The agent-flow ledger — `{HARNESS_DIR}/agent-flow.jsonl`, the same JSONL the catalog's `state.agentFlow` evidence reads — also records **workflow / ralph fan-out runs**: a session-event consumer (logger `mstar/workflow-ledger`, registered at apply) maps the FOUR durable `tool-workflow/*` session events into three new ledger kinds. Source of record is the **durable session events** appended into the CALLING PARENT session's log (top-level runs only — nested transport calls record nothing upstream), **not** the in-memory `workflow/*` emits (roadmap §10.4 N4): the session log is the replayable truth, so the consumer covers it with a **cold scan at apply** (constructor-seeded events never hit the firehose — `firstLiveSeq`) plus a live **`session/event` firehose** listener, deduped by ONE **durable per-session watermark** — the session-log `seq` position — persisted to `{HARNESS_DIR}/workflow-ledger-cursors.json` (a small bounded sidecar next to the ledger, written atomically temp-file + rename).
260
+ The same ledger also records **workflow / ralph fan-out runs**: a session-event consumer (logger `mstar/workflow-ledger`, registered at apply) maps the FOUR durable `tool-workflow/*` session events into three new ledger kinds. Source of record is the **durable session events** appended into the CALLING PARENT session's log (top-level runs only — nested transport calls record nothing upstream), **not** the in-memory `workflow/*` emits (roadmap §10.4 N4): the session log is the replayable truth, so the consumer covers it with a **cold scan at apply** (constructor-seeded events never hit the firehose — `firstLiveSeq`) plus a live **`session/event` firehose** listener, deduped by ONE **durable per-session watermark** — the session-log `seq` position — persisted to `{HARNESS_DIR}/workflows/<id>/workflow-ledger-cursors.json` (a small bounded sidecar next to the ledger, written atomically temp-file + rename).
251
261
 
252
262
  | `tool-workflow/*` event | Ledger row | Fields |
253
263
  | --- | --- | --- |
@@ -259,7 +269,7 @@ The agent-flow ledger — `{HARNESS_DIR}/agent-flow.jsonl`, the same JSONL the c
259
269
 
260
270
  A FOURTH kind, **`workflow-verdict`**, is written by the workflow/ralph GATE (not this consumer) — one row per gated call (`tool`, `workflow`/`objective`, `mode`, verdict `ok`/`advisory`/`denied`/`ask`, violation `code`) — see Gates → Workflow / ralph gate. Display identity fields (`workflow` / `objective`) carry the same 1024-char cap; the verdict's violation code is never guessed (P-a `workflow.name.unknown` vs P-b `workflow.lease.uncovered`).
261
271
 
262
- **Dedupe + replay scope.** The durable watermark is the dedupe mechanism: **one row per `(runId, kind, seq)`** across cold+live overlap AND across plugin **re-applies / restarts** — a re-registration consults the persisted watermark instead of starting with empty cursors. A session **created after apply** with a constructor-seeded log (resumed / forked conversation — its seeds never publish on the firehose) is cold-scanned **once** on the upstream `session/created` event, and the watermark keeps that backfill idempotent too. The watermark sidecar is bounded (per-harness session cap, eviction preferring sessions no longer live) and fully contained: an unreadable/unwritable watermark degrades to in-memory-only with one warn — a restart then re-records (honest under-dedupe, never data loss, never gating).
272
+ **Dedupe + replay scope.** The durable watermark is the dedupe mechanism: **one row per `(runId, kind, seq)`** across cold+live overlap AND across plugin **re-applies / restarts** — a re-registration consults the persisted watermark instead of starting with empty cursors. A session **created after apply** with a constructor-seeded log (resumed / forked conversation — its seeds never publish on the firehose) is cold-scanned **once** on the upstream `session/created` event, and the watermark keeps that backfill idempotent too. A **forked** conversation's scan starts at its `inheritedEventCount`: the inherited prefix is the parent session's history, so the child records only its **own** events — never a second copy of the parent's rows attributed to the child. The watermark sidecar is bounded (per-harness session cap, eviction preferring sessions no longer live) and fully contained: an unreadable/unwritable watermark degrades to in-memory-only with one warn — a restart then re-records (honest under-dedupe, never data loss, never gating).
263
273
 
264
274
  **childId linkage + member counts.** The `workflow-agent` row preserves the published member's `childId` (the child session id); the run's display `name` lives on the `workflow-run` row only, and the panel resolves it for agent/end rows via the window lookup (same `runId` — a member row itself carries no name). The panel attaches the member COUNT to the `workflow-run` row (the window's `workflow-agent` rows for that `runId`; window-bound — members truncated out of the ≤50-event window are honestly absent, never a 0 guess).
265
275
 
package/README.zh.md CHANGED
@@ -14,7 +14,7 @@ dsh 应用如何使用本插件——安装路径、配置、挂载时发生什
14
14
 
15
15
  本包以 workspace 包形式发布(`workspaces: ["packages/*"]`),构建时把 engine 打进 `dist/`(`bun run build`;dist 已被 gitignore)。安装途径是 **profile bundle**,装进现成的 `web` profile(`dsh --profile web`——开箱即用的 web 应用 profile,即 `dsh web`),经 `dsh.bundle.patch` 清单——一个叠在 dsh-base 默认层之上的补丁层:
16
16
 
17
- **一条命令的 CLI 入口(推荐)**——`npx @mstar-harness/cli init --target dsh` 一次性装齐全量能力:它按序运行下面两条 `dsh plugin --profile web add` 安装(先 mstar bundle,再 `dsh-llm-fallbacks`),并可用 `npx @mstar-harness/cli doctor --target dsh` 逐行报告 `uninstalled` / `disabled` / `mounted`。它编排的仍是同一条双命令安装;`--no-fallbacks` 跳过第二行(连带跳过 seeded 角色——见下文「零配置获得什么」)。
17
+ **一条命令的 CLI 入口(推荐)**——`npx @mstar-harness/cli init --target dsh` 一次性装齐全量能力:它按序运行下面两条 `dsh plugin --profile web add` 安装(先 mstar bundle,再 `dsh-llm-fallbacks`),并可用 `npx @mstar-harness/cli doctor --target dsh` 逐行报告 `uninstalled` / `disabled` / `mounted` / `drifted`(`drifted` = fallbacks 行的安装版本不等于 pin;`init --target dsh` 会按 pin 重新 add,在此之前 `doctor` 以非零退出)。它编排的仍是同一条双命令安装;`--no-fallbacks` 跳过第二行(连带跳过 seeded 角色——见下文「零配置获得什么」)。
18
18
 
19
19
  **(a)registry 安装(发布形态)**——npm 包自带构建好的 `dist/`(安装时无需构建):
20
20
 
@@ -73,7 +73,9 @@ dsh plugin --profile headless add @mstar-harness/dsh
73
73
  | `bundledSkillDir` | `string` | 打包的 `harness-skills/` 镜像(包相对路径) | 向 dsh skill-filesystem 提供者注册的打包技能根(`bundledSkillDir` 语义——最后扫描、受信任)。默认取包内自带的 `harness-skills/` 镜像(`bundle-assets` 同步;gitignore)——包相对路径,**非** cwd 锚定。显式值优先。 |
74
74
  | `catalogTtlMs` | `number` | `60000` | pre-step catalog 缓存刷新间隔(毫秒):按工作区缓存的统一 `mstar-engine` 行(水印 + 迭代闸门 + 工作区摘要)多久重读一次 `status.json` / compass / 知识索引。刷新间隔之间热路径只是时间戳比较 + Map 命中;会话中 plan/compass/residual 的变化会在一个间隔内落地。 |
75
75
  | `workflowGate` | `'off' \| 'warn' \| 'ask' \| 'hard'` | `'warn'` | workflow/ralph 闸门模式(见 Gates → Workflow / ralph gate)。`off` = 直通且不产生 verdict 行;`warn` = 仅咨询;`ask` = 首见名字走审批瀑布(P-c);`hard` = 策略违规在任何子进程启动前否决。默认 `warn` 不改任何 hard 行为——除非部署显式选入 `ask`/`hard`,闸门仅咨询。 |
76
- | `workflowNames` | `string[]` | 未设置 | workflow 名字白名单(P-a):被闸门视为 KNOWN 的 `meta.name` 值。为空或缺省 ⇒ **每个**名字都 unknown(有文档——闸门**绝不**因缺省而"全放行")。ralph 调用不携带 `meta.name`——P-a 对其永不适用。 |
76
+ | `workflowNames` | `string[]` | 未设置 | workflow 名字白名单(P-a):被闸门视为 KNOWN 的 `meta.name` 值。为空或缺省 ⇒ **每个**名字都 unknown(有文档——闸门**绝不**因缺省而"全放行")。ralph 调用不携带 `meta.name`——P-a 对其永不适用。mstar 只读扇出路径推荐值:`['mstar-qc-tri', 'mstar-audit-fanout', 'mstar-pr-seats']`。 |
77
+
78
+ **推荐的 operator 覆盖层。** 上表三个推荐 `workflowNames` 覆盖 mstar 只读扇出路径(plan QC tri、大型仓库 `/codebase-audit`、`/amazing-pr-review deep`);写入它们是 profile 层的操作者覆盖,绝非 mstar 默认。出厂空列表下每个名字都是 *unknown*,在默认 `workflowGate: 'warn'` 下这只是运行会存活的一条 `workflow.name.unknown` 咨询。生产部署若还要否决 unknown 名字,可设置 `workflowGate: 'hard'`;出厂默认仍为 `'warn'`。
77
79
 
78
80
  `bundledSkillDir` 默认取包内自带的 `harness-skills/` 镜像(见 Skills mount)——显式 Config 值仍然优先。相对覆盖仍是 **cwd 锚定**(skill-filesystem 以 `join()` 语义相对 dsh **进程 cwd** 解析),因此覆盖默认的部署应在 **profile 层传绝对路径**(见 `bundle/README.md`)。
79
81
 
@@ -172,7 +174,7 @@ persona 交付走 dsh 原生的 `SubagentStartRequest.persona` 槽(`@deepseek-
172
174
 
173
175
  ### 角色 seeds 与采纳建议(Adoption advisory)
174
176
 
175
- 当可选的 `dsh-llm-fallbacks` 能力**已挂载**(第二条安装命令——见 Install paths)时,mstar 插件会向 fallbacks seed registry **零配置声明 13 个 `mode: subagent` mstar 角色 seed**:persona = `harness-agents/` 镜像 `description`(原样)+ 一行强制加载引导(`Load mstar-roles (references/<role-id>.md) first — identity comes before skills; load topic skills only when the Assignment activates them via its Skill presets field.`);含 `{{...}}` 插值风险的 persona 跳过并告警,绝不声明。声明会**合并保留 readback 中当前已 seeded 的非 mstar id**——例如上游包在其自身 apply 时自声明的 7 个 omp 风格 preset 角色:上游 `declare` **全量替换** registry,若不保留,mstar-only 批会摘掉 preset id 的 seeded 注记(行仍在,仅失去 seeded)。声明在每次 fallbacks(重新)apply(HMR/纤程切换)时幂等重放——绝不用一次性 latch——因此两种 boot 顺序(presets 先或 mstar 先)都收敛到同一 20-id 全 seeded registry。boot 时收敛经 bounded retry(有界重试):上游的 seed 写通道在其 apply 之后一个 macrotask 才绑定,因此 apply 窗口内首次尝试被 `seeds: settings service is unavailable` 拒绝时会重试(跨上游 apply 窗口的 3 次尝试),暂时性拒绝自行收敛;仅当所有尝试最终失败时,声明才记录恰好一条终态错误,同时 advisory 的决策点 re-declare 仍可用作 retry 路径。**无需手动编辑 `roles.list`。**
177
+ 当可选的 `dsh-llm-fallbacks` 能力**已挂载**(第二条安装命令——见 Install paths)时,mstar 插件会向 fallbacks seed registry **零配置声明 13 个 `mode: subagent` mstar 角色 seed**:persona = `harness-agents/` 镜像 `description`(原样)+ 一行强制加载引导(`Load mstar-roles (references/<role-id>.md) first — identity comes before skills; load topic skills only when the Assignment activates them via its Skill presets field.`);含 `{{...}}` 插值风险的 persona 跳过并告警,绝不声明。声明会**合并保留 readback 中当前已 seeded 的非 mstar id**——例如上游包在其自身 apply 时自声明的 5 个上游 preset 角色:上游 `declare` **全量替换** registry,若不保留,mstar-only 批会摘掉 preset id 的 seeded 注记(行仍在,仅失去 seeded)。声明在每次 fallbacks(重新)apply(HMR/纤程切换)时幂等重放——绝不用一次性 latch——因此两种 boot 顺序(presets 先或 mstar 先)都收敛到同一 18-id 全 seeded registry。boot 时收敛经 bounded retry(有界重试):上游的 seed 写通道在其 apply 之后一个 macrotask 才绑定,因此 apply 窗口内首次尝试被 `seeds: settings service is unavailable` 拒绝时会重试(跨上游 apply 窗口的 3 次尝试),暂时性拒绝自行收敛;仅当所有尝试最终失败时,声明才记录恰好一条终态错误,同时 advisory 的决策点 re-declare 仍可用作 retry 路径。**无需手动编辑 `roles.list`。**
176
178
 
177
179
  一条只告警的采纳建议通道(日志器 `mstar/fallbacks-advisory`)**每次 apply 只跑一遍**——apply 时先尝试一次;当 fallbacks 行在 `dsh` 之后挂载(loader 并发挂载条目)时,改在首个 `subagent/start` 决策点只跑一遍。服务存在时,通道**先 await 幂等 re-declare**(闭合 boot 竞争窗口)再读取**有效状态**(`getEffectiveRoles`),并按**每类至多一条告警**有界报告:
178
180
 
@@ -244,9 +246,17 @@ mstar 技能通过 dsh skill-filesystem 提供者以**单一规范挂载**接入
244
246
 
245
247
  该行是 **digest 门控**的:按 agent+workspace,每个 turn 只注入一次,仅当渲染文本变化时重新注入——20 步的 turn 只显示一次 catalog,而不是 20 次。source 共享**同一**按工作区缓存条目:显式 `harnessDir` 时在 boot 构建(否则在工作区首次 pre-step 构建),并按 TTL 刷新(`catalogTtlMs`,默认 60 秒)——刷新间隔之间热路径只是时间戳比较 + Map 命中,会话中 plan/compass/residual 的变化在一个间隔内落地。
246
248
 
247
- ## Agent-flow ledger(workflow 行)
249
+ ## Agent-flow ledger
250
+
251
+ agent-flow 账本位于**当前活动 workflow 目录**——`{HARNESS_DIR}/workflows/<id>/agent-flow.jsonl`(JSON Lines,保留最近 500 条)——即 catalog 的 `state.agentFlow` 证据所读的同一 JSONL。无活动生命周期时记录被**跳过**并只告警一次——绝不写 harness 根,绝不写终态快照目录。它记录**真实的** subagent 派发与结算证据(咨询式、try/catch 含容——账本写入失败绝不阻断派发或结算):
252
+
253
+ - **派发(dispatch)**——每个 Assignment 形态的派发一行,记录派发身份:`role`(在写入边界归一化——去首尾空白并剥掉**一个**前导 `@`,故 `@explore` 与 `explore` 是同一 actor 而非两个角色)、`planId`、Assignment 的 `Task N` 标签记为 `taskId`、`taskCategory` 与判定。
254
+ - **结算(settle)**——仅为**真实配对**的完成记录。前台调用在 `tools/post-execute` 监听器结算;`{ kind: 'background', jobId }` 结果按注册表 **`jobId`** 配对(jobs 注册表键,绝非子会话 id),待 `ctx.inject(['jobs'])` → `jobs.onJobDone` 报告终态快照时结算(`completed → ok` / `killed → denied` / `failed → error`,可用时附 `durationMs`)。每条配对结算都携带派发身份(`Task N` 标签仍是 `taskId`;`taskRef` 专用于注册表 job id,仅后台结算携带)外加**可选** `childId`——前台 `runId`,或 catalog 关联已提供的后台子会话 id。可选 id 缺失或超长时**只省略该字段**:真实完成照常记录,id 绝不截断、绝不重键。未配对载荷**不记录任何东西**——绝不伪造结算。
255
+ - **`subagent-link`(非终态)**——某次派发实际启动的子会话 id,由上游以**父会话自有**的 `subagent/catalog` 会话事件发布(`label` = 委派 `description`;continuable 路径在工具返回**之前**追加)。逐派发的**调用窗口**关联——pre-execute 预留槽位、有效 `background` / `continuable` 结果使其可选中、`eventAt(seq)` 补扫加**一个**活的根上下文 `session/event` 观察者——把该 catalog 子会话关联回派发身份(`role` / `planId` / `taskId`,后台 one-shot 另带 `taskRef`),写入**该派发自己的** workflow 目录。它是**身份记录**,**不是**完成:无 `outcome`、无 `verdict`、无 `paired` 标记。该关联为 apply 作用域——无全历史冷扫描、无 `session/created` 回填,故 apply 之前写入的 catalog 绝不可能给更晚的派发贴标签——而 provider 不发出 catalog 的派发就是没有 link 行。
256
+
257
+ ### Workflow 行
248
258
 
249
- agent-flow 账本——`{HARNESS_DIR}/agent-flow.jsonl`,即 catalog 的 `state.agentFlow` 证据所读的同一 JSONL——同样记录 **workflow / ralph 扇出运行**:一个会话事件消费者(日志器 `mstar/workflow-ledger`,apply 时注册)把四个持久化的 `tool-workflow/*` 会话事件映射为三种新账本类型。事实来源是**持久化会话事件**——追加进**调用方父会话**的日志(仅顶层运行;嵌套 transport 调用上游不记录任何东西),而**不是**内存中的 `workflow/*` emits(roadmap §10.4 N4):会话日志才是可回放的事实,因此消费者以 **apply 时冷扫描**(构造期种子事件从不进 firehose——`firstLiveSeq`)加实时 **`session/event` firehose** 监听覆盖它,按**持久化逐会话水位线**(会话日志 `seq` 位置)去重——水位线持久化到 `{HARNESS_DIR}/workflow-ledger-cursors.json`(账本旁的小型有界 sidecar,temp 文件 + rename 原子写入)。
259
+ 同一账本还记录 **workflow / ralph 扇出运行**:一个会话事件消费者(日志器 `mstar/workflow-ledger`,apply 时注册)把四个持久化的 `tool-workflow/*` 会话事件映射为三种新账本类型。事实来源是**持久化会话事件**——追加进**调用方父会话**的日志(仅顶层运行;嵌套 transport 调用上游不记录任何东西),而**不是**内存中的 `workflow/*` emits(roadmap §10.4 N4):会话日志才是可回放的事实,因此消费者以 **apply 时冷扫描**(构造期种子事件从不进 firehose——`firstLiveSeq`)加实时 **`session/event` firehose** 监听覆盖它,按**持久化逐会话水位线**(会话日志 `seq` 位置)去重——水位线持久化到 `{HARNESS_DIR}/workflows/<id>/workflow-ledger-cursors.json`(账本旁的小型有界 sidecar,temp 文件 + rename 原子写入)。
250
260
 
251
261
  | `tool-workflow/*` 事件 | 账本行 | 字段 |
252
262
  | --- | --- | --- |
@@ -258,7 +268,7 @@ agent-flow 账本——`{HARNESS_DIR}/agent-flow.jsonl`,即 catalog 的 `state
258
268
 
259
269
  **第四种类型 `workflow-verdict`** 由 workflow/ralph 闸门(而非本消费者)写入——每个被把关的调用一行(`tool`、`workflow`/`objective`、`mode`、判定 `ok`/`advisory`/`denied`/`ask`、违规 `code`)——见 Gates → Workflow / ralph gate。展示身份字段(`workflow` / `objective`)同样带 1024 字符上限;判定的违规码绝不猜测(P-a `workflow.name.unknown` vs P-b `workflow.lease.uncovered`)。
260
270
 
261
- **去重与回放范围。** 持久化水位线即去重机制:**冷热重叠**以及**插件重应用/重启**(重注册读取持久化水位线而非从空游标开始)下每个 `(runId, kind, seq)` 只产一行。**apply 之后创建**、带构造期种子日志(恢复/分叉会话——其种子从不进 firehose)的会话会在上游 `session/created` 事件上**冷扫描一次**,水位线同样保证该回填幂等。水位线 sidecar 有界(每 harness 会话数上限,驱逐优先已不在线的会话)且完全受控:水位线不可读/不可写时降级为仅内存并告警一次——重启后会重录(诚实的去重欠录,绝不丢数据、绝不阻塞)。
271
+ **去重与回放范围。** 持久化水位线即去重机制:**冷热重叠**以及**插件重应用/重启**(重注册读取持久化水位线而非从空游标开始)下每个 `(runId, kind, seq)` 只产一行。**apply 之后创建**、带构造期种子日志(恢复/分叉会话——其种子从不进 firehose)的会话会在上游 `session/created` 事件上**冷扫描一次**,水位线同样保证该回填幂等。**分叉**会话的扫描从其 `inheritedEventCount` 开始:继承来的前缀属于父会话的历史,因此子会话只记录**自己**的事件——绝不会把父会话的行以子会话身份再记一份。水位线 sidecar 有界(每 harness 会话数上限,驱逐优先已不在线的会话)且完全受控:水位线不可读/不可写时降级为仅内存并告警一次——重启后会重录(诚实的去重欠录,绝不丢数据、绝不阻塞)。
262
272
 
263
273
  **childId 关联 + 成员计数。** `workflow-agent` 行保留已发布成员的 `childId`(子会话 id);运行的展示 `name` 只存在于 `workflow-run` 行,面板为 agent/end 行经窗口查找解析(同一 `runId`——成员行本身不带名称)。面板把成员 COUNT 挂到 `workflow-run` 行(窗口内该 `runId` 的 `workflow-agent` 行数;窗口有界——被 ≤50 事件窗口截掉的成员如实缺席,绝不猜 0)。
264
274
 
@@ -22,7 +22,13 @@
22
22
  * on arrival ({@link MstarEngineStatusClient.write}) and the next render
23
23
  * re-issues it. Without the fence an in-flight pre-reconnect answer would
24
24
  * repopulate the cache and then suppress the repull the invalidation exists to
25
- * force.
25
+ * force. A PICKER COMMIT uses the same fence PER SESSION
26
+ * ({@link MstarEngineStatusClient.invalidateSession}): the durable binding
27
+ * changed, so an in-flight pre-pick answer must not land — and the session's
28
+ * cached entry, which still answers the anchor row (a pick writes no row),
29
+ * must be re-pulled for its `binding` while the last emitted `payload` stays on
30
+ * screen (the plans / Event Log are the last MODEL emission until the next real
31
+ * one).
26
32
  *
27
33
  * DEADLINES: the gateway is a network hop, so one request is bounded
28
34
  * ({@link MstarEngineStatusClientOptions.timeoutMs}) and a request that
@@ -41,9 +47,10 @@
41
47
  * @module @mstar-harness/dsh/client/panel/engine-status-client
42
48
  */
43
49
  import type { ConnectionRpcResult } from '@deepseek-ai/dsh-client-connection/client';
44
- import { ENGINE_STATUS_CHANNEL, ENGINE_STATUS_ENDPOINT } from '../../engine-status-wire.ts';
45
- import { type MstarEngineStatusFetch } from './guards.ts';
46
- export { ENGINE_STATUS_CHANNEL, ENGINE_STATUS_ENDPOINT };
50
+ import { ENGINE_STATUS_CHANNEL, ENGINE_STATUS_ENDPOINT, SELECT_WORKFLOW_ENDPOINT } from '../../engine-status-wire.ts';
51
+ import { type MstarEngineStatusFetch, type MstarSelectWorkflowFetch } from './guards.ts';
52
+ export { ENGINE_STATUS_CHANNEL, ENGINE_STATUS_ENDPOINT, SELECT_WORKFLOW_ENDPOINT };
53
+ export type { MstarSelectWorkflowFetch };
47
54
  /** Deadline for one `/api/mstar/engineStatus` request (a hanging gateway must not pin `loading`). */
48
55
  export declare const ENGINE_STATUS_REQUEST_TIMEOUT_MS = 5000;
49
56
  /** How often a served snapshot is re-pulled (D3's "explicit refresh", modest interval). */
@@ -84,9 +91,28 @@ export interface MstarEngineStatusEntry {
84
91
  readonly fetchedAt: number;
85
92
  readonly fetch: MstarEngineStatusFetch;
86
93
  }
87
- /** The observable store value: at most one entry per session. */
94
+ /**
95
+ * The picker state of ONE session (D4 control feedback, never a selection
96
+ * source of truth): idle, a submission awaiting the host, the acknowledged
97
+ * workflow id, or the explicit failure reason. The panel disables a second
98
+ * submission while `pending` and shows `failed` beside the picker instead of a
99
+ * fake selection.
100
+ */
101
+ export type MstarSelectionState = {
102
+ readonly state: 'idle';
103
+ } | {
104
+ readonly state: 'pending';
105
+ } | {
106
+ readonly state: 'selected';
107
+ readonly workflowId: string;
108
+ } | {
109
+ readonly state: 'failed';
110
+ readonly reason: string;
111
+ };
112
+ /** The observable store value: at most one entry per session (+ its picker state). */
88
113
  export interface MstarEngineStatusSnapshot {
89
114
  readonly entries: ReadonlyMap<string, MstarEngineStatusEntry>;
115
+ readonly selections: ReadonlyMap<string, MstarSelectionState>;
90
116
  }
91
117
  /**
92
118
  * The panel's engine-status client: one `/api/mstar/engineStatus` request per
@@ -106,6 +132,19 @@ export declare class MstarEngineStatusClient {
106
132
  private generationDisposer;
107
133
  /** Connection generation: bumped by {@link invalidate}; a mismatched answer is dropped. */
108
134
  private generation;
135
+ /**
136
+ * Per-session request generation: bumped by {@link invalidateSession} (a
137
+ * picker commit). A fetch captures the session's generation when it is
138
+ * issued, and its answer is dropped when that generation moved — the fence
139
+ * that keeps a late pre-pick answer from overwriting post-pick state.
140
+ */
141
+ private readonly sessionGenerations;
142
+ /**
143
+ * Sessions whose cached entry no longer describes their durable binding: the
144
+ * next `ensure` for that entry's own anchor re-pulls it instead of serving it.
145
+ * Set by {@link invalidateSession}, cleared when the answer lands.
146
+ */
147
+ private readonly stale;
109
148
  private disposed;
110
149
  /**
111
150
  * @param connection - the client `connection` service, or undefined in a
@@ -135,6 +174,41 @@ export declare class MstarEngineStatusClient {
135
174
  * repull this invalidation exists to force would be suppressed by them.
136
175
  */
137
176
  invalidate(): void;
177
+ /**
178
+ * Mark ONE session's cached answer stale and fence off the requests already on
179
+ * the wire (a picker commit changed that session's durable binding).
180
+ *
181
+ * The entry is KEPT — the last emitted payload is still the last model
182
+ * emission, and spec §Picker contract 2 keeps plans / Event Log on it until
183
+ * the next real `agent/pre-step` replaces it. What must change is `binding`,
184
+ * the host's current control state, which the next {@link ensure} re-pulls for
185
+ * the same anchor ({@link stale}), because an entry that already answers the
186
+ * anchor would otherwise short-circuit the request and the panel would keep
187
+ * rendering the pre-pick selection.
188
+ *
189
+ * The fence is per session: a pre-pick answer issued before this call belongs
190
+ * to the superseded control state and is dropped on arrival, and no other
191
+ * session's cache is touched.
192
+ */
193
+ invalidateSession(sessionId: string): void;
194
+ /**
195
+ * Commit this session's workflow pick over the host's shared gateway (the
196
+ * `selectWorkflow` UI control). Never optimistic: the returned state is
197
+ * `pending` until the host acknowledges, then either `selected` (with the
198
+ * acknowledged id) or `failed` (with the host's reason) — and a success
199
+ * invalidates THIS session only, so the next `ensure()` re-reads the
200
+ * acknowledged binding while every other session's cache is untouched.
201
+ * @param sessionId - the session the panel is showing.
202
+ * @param cwd - the session's workspace directory (the host cross-checks it).
203
+ * @param workflowId - the chosen ACTIVE workflow id.
204
+ */
205
+ selectWorkflow(sessionId: string, cwd: string, workflowId: string): Promise<MstarSelectWorkflowFetch>;
206
+ /** One `selectWorkflow` round trip (never throws — every fault is a reason). */
207
+ private callSelect;
208
+ /** Publish one session's picker state (store write outside a render). */
209
+ private setSelection;
210
+ /** One session's current request generation. */
211
+ private sessionGen;
138
212
  /** Stop accepting writes, abort in-flight requests and end the refresh interval (plugin teardown). */
139
213
  dispose(): void;
140
214
  /**
@@ -170,7 +244,11 @@ export declare class MstarEngineStatusClient {
170
244
  * An answer issued under a superseded connection generation is DROPPED: it
171
245
  * describes the connection that is gone, and publishing it would both render
172
246
  * pre-reconnect data as `ok` and suppress the repull `invalidate()` forces
173
- * (the stale answer would satisfy `ensure`'s anchor check).
247
+ * (the stale answer would satisfy `ensure`'s anchor check). An answer issued
248
+ * under a superseded SESSION generation is dropped for the same reason after a
249
+ * picker commit ({@link invalidateSession}): it describes the pre-pick control
250
+ * state and would otherwise repopulate the very entry the acknowledgement
251
+ * invalidated.
174
252
  *
175
253
  * An answer for a SUPERSEDED anchor is dropped for the same class of reason:
176
254
  * a refresh issues its request for the anchor it saw, and a newer anchor row
@@ -100,15 +100,15 @@ export interface FlowEventView {
100
100
  /** `${ts}-${kind}-${index}` — stable id (index = position in the projected window). */
101
101
  id: string;
102
102
  ts: number;
103
- /** The ledger kind verbatim: dispatch | settle | the three workflow kinds | any unknown kind string (generic row). */
104
- kind: 'dispatch' | 'settle' | 'workflow-run' | 'workflow-agent' | 'workflow-run-end' | (string & {});
103
+ /** The ledger kind verbatim: dispatch | settle | subagent-link | the three workflow kinds | any unknown kind string (generic row). */
104
+ kind: 'dispatch' | 'settle' | 'subagent-link' | 'workflow-run' | 'workflow-agent' | 'workflow-run-end' | (string & {});
105
105
  /** `Execute as`; '' for settle rows without a paired identity and for workflow/unknown rows. */
106
106
  role: string;
107
107
  planId: string | null;
108
108
  taskId: string | null;
109
109
  taskCategory: string | null;
110
110
  agent: string | null;
111
- /** dispatch → dispatched|advisory|denied; settle → ok|error|denied; workflow/unknown → unknown (spec §2.4). */
111
+ /** dispatch → dispatched|advisory|denied; settle → ok|error|denied; link/workflow/unknown → unknown (spec §2.4). */
112
112
  status: FlowEventStatus;
113
113
  /** `role` ∈ the EXPECTED_ROLE_FLOW role union (spec §2.3 exact-string match). */
114
114
  expected: boolean;
@@ -134,8 +134,19 @@ export interface FlowEventView {
134
134
  readonly label?: string;
135
135
  /** Run-member phase (workflow-agent rows only, when carried). */
136
136
  readonly phase?: string;
137
- /** The published member's child session identity (workflow-agent rows only). */
137
+ /**
138
+ * Child session identity when the source supplied one: workflow-agent —
139
+ * the published member; settle — the foreground `runId` or a background
140
+ * catalog join; `subagent-link` — the required catalog child id. Omitted
141
+ * when the row carried none. Never a registry job id (that is `taskRef`).
142
+ */
138
143
  readonly childId?: string;
144
+ /**
145
+ * Settle + `subagent-link` rows: the registry background-job id, when
146
+ * carried. A jobs-registry key, never a child session id. Omitted when
147
+ * the row carried none.
148
+ */
149
+ readonly taskRef?: string;
139
150
  /** Terminal workflow run reason (workflow-run-end rows only). */
140
151
  readonly stopReason?: string;
141
152
  }
@@ -293,13 +304,15 @@ export type AgentEntityStatus = 'running' | 'settled' | 'error' | 'denied'
293
304
  /**
294
305
  * The canvas degradation-note classification (spec §8): the projection
295
306
  * decides the note from the RAW ledger (never a UI-side heuristic on the
296
- * entity list): `empty` = 0 events; `settle-only` = events present but NO
297
- * dispatch row (all settle / garbage rows — genuinely no dispatch evidence);
298
- * `null` = dispatch evidence present (incl. anonymous dispatch rows — they
299
- * are evidence, not settle-only). The unreadable-ledger case is the SEPARATE
300
- * `degraded` flag, not a note value.
307
+ * entity list): `empty` = 0 events; `link-only` = the readable window
308
+ * contains ONLY `subagent-link` rows (identity records — neither dispatch
309
+ * nor settlement evidence); `settle-only` = events present but NO dispatch
310
+ * and not link-only (settles / garbage / workflow — genuinely no dispatch
311
+ * evidence); `null` = dispatch evidence present (incl. anonymous dispatch
312
+ * rows — they are evidence, not settle-only). The unreadable-ledger case
313
+ * is the SEPARATE `degraded` flag, not a note value.
301
314
  */
302
- export type AgentZoneNote = 'empty' | 'settle-only' | null;
315
+ export type AgentZoneNote = 'empty' | 'settle-only' | 'link-only' | null;
303
316
  /**
304
317
  * The projected agents zone (spec §4 + §6.2): the EXPECTED_ROLE_FLOW stage
305
318
  * skeleton plus the dispatch-derived entity cards. Total function — NEVER
@@ -430,9 +443,9 @@ export declare function projectGraph(source: MstarEngineStatusPayload | null): Z
430
443
  * collides.
431
444
  *
432
445
  * Canvas note: `note` classifies the readable ledger in
433
- * the projection ('empty' / 'settle-only' / null — see `AgentZoneNote`); the
434
- * UI consumes it directly and never infers settle-only from the entity list
435
- * (garbage rows would fake it).
446
+ * the projection ('empty' / 'link-only' / 'settle-only' / null — see
447
+ * `AgentZoneNote`); the UI consumes it directly and never infers
448
+ * settle-only from the entity list (garbage rows would fake it).
436
449
  *
437
450
  * Current-plan note: `activePlanId` / `activePlanCount` ride the
438
451
  * `state.plans[]` InProgress rows (catalog order) — the Phase 2 group label
@@ -10,7 +10,7 @@
10
10
  * payload is a remote answer the panel never authored, so a malformed
11
11
  * envelope degrades to `unavailable` — never to a half-parsed view.
12
12
  */
13
- import type { MstarEngineStatusPayload } from '../../types.ts';
13
+ import type { MstarEngineStatusPayload, WorkflowSelectionView } from '../../types.ts';
14
14
  /** String field: non-empty string, else null (missing → `unknown`). */
15
15
  export declare function str(value: unknown): string | null;
16
16
  /** Boolean field: real boolean, else null. */
@@ -33,10 +33,35 @@ export type MstarEngineStatusFetch = {
33
33
  readonly at: string;
34
34
  /** The agent turn the row was emitted for. */
35
35
  readonly turn: number;
36
+ /**
37
+ * The session's CURRENT control-state selection, when the host served one
38
+ * (D4). It is deliberately separate from `payload.state.selection`, which
39
+ * keeps recording the LAST MODEL EMISSION — the picker's acknowledgement
40
+ * shows here immediately while plans / Event Log stay on the last snapshot.
41
+ * Null when the response carries none (an older host, or a malformed one).
42
+ */
43
+ readonly binding: WorkflowSelectionView | null;
36
44
  } | {
37
45
  readonly status: 'unavailable';
38
46
  readonly reason: string;
39
47
  };
48
+ /** One workflow-pick acknowledgement (the `selectWorkflow` wire result). */
49
+ export type MstarSelectWorkflowFetch = {
50
+ readonly status: 'selected';
51
+ readonly sessionId: string;
52
+ readonly workflowId: string;
53
+ } | {
54
+ readonly status: 'unavailable';
55
+ readonly reason: string;
56
+ };
57
+ /**
58
+ * Narrow one untrusted workflow-selection view. Every arm must carry its
59
+ * discriminants as non-empty strings; the picker rows (`activeWorkflowIds`)
60
+ * survive only as a string array. Anything else is null — the caller then
61
+ * falls back to the emitted payload's own selection rather than rendering a
62
+ * guessed one.
63
+ */
64
+ export declare function selectionView(value: unknown): WorkflowSelectionView | null;
40
65
  /**
41
66
  * Validate one `/api/mstar/engineStatus` response into the panel's state.
42
67
  *
@@ -59,3 +84,18 @@ export type MstarEngineStatusFetch = {
59
84
  * @returns the validated snapshot state, or the explicit unavailable state.
60
85
  */
61
86
  export declare function parseEngineStatusResult(raw: unknown, sessionId: string, cwd?: string): MstarEngineStatusFetch;
87
+ /**
88
+ * Validate one `/api/mstar/selectWorkflow` response into the picker's state.
89
+ *
90
+ * Like {@link parseEngineStatusResult} the whole envelope is untrusted: the
91
+ * transport result, the endpoint result and the acknowledged identity are all
92
+ * checked. A success MUST name the session AND the workflow this client asked
93
+ * for — a foreign acknowledgement is refused rather than rendered as this
94
+ * session's pick.
95
+ *
96
+ * @param raw - the value returned by `connection.rpc.call` (untrusted).
97
+ * @param sessionId - the session this client ASKED for.
98
+ * @param workflowId - the workflow id this client asked to select.
99
+ * @returns the acknowledged pick, or the explicit unavailable state.
100
+ */
101
+ export declare function parseSelectWorkflowResult(raw: unknown, sessionId: string, workflowId: string): MstarSelectWorkflowFetch;
@@ -122,7 +122,7 @@ import type { LocaleDictOf } from '@deepseek-ai/dsh-client-ui-slots';
122
122
  /** Locale namespace id for the workflow-viz panel. */
123
123
  export declare const NS = "mstar-panel";
124
124
  /** Panel dictionary keys (union of every translatable string the panel renders). */
125
- export type PanelKey = 'view.mstar-workflow' | 'guide.description' | 'tab.tasks' | 'tab.agents' | 'tab.events' | 'page.iteration.not-started' | 'page.iteration.expand' | 'page.iteration.collapse' | 'event-log.section.events' | 'event-log.section.violations' | 'event-log.empty' | 'event-log.empty.events' | 'event-log.empty.violations' | 'event-log.field.role' | 'event-log.field.agent' | 'event-log.field.stage' | 'event-log.field.plan' | 'event-log.field.task' | 'event-log.field.category' | 'event-log.field.time' | 'event-log.field.kind' | 'event-log.field.status' | 'event-log.field.expected' | 'event-log.field.settled' | 'event-log.field.duration' | 'event-log.field.run-id' | 'event-log.field.name' | 'event-log.field.members' | 'event-log.field.stop-reason' | 'event-log.field.severity' | 'event-log.field.code' | 'event-log.field.message' | 'event-log.kind.dispatch' | 'event-log.kind.settle' | 'event-log.yes' | 'event-log.no' | 'empty.waiting' | 'empty.loading' | 'empty.unavailable' | 'empty.no-harness' | 'empty.no-harness-hint' | 'watermark.version' | 'watermark.harness' | 'watermark.none' | 'panel.unknown' | 'graph.pass' | 'graph.fail' | 'zone.legend.title' | 'zone.legend.agent-running' | 'zone.legend.agent-settled' | 'zone.legend.agent-idle' | 'zone.iteration.step-label' | 'zone.iteration.step-badge' | 'zone.iteration.step.current' | 'zone.iteration.step.next' | 'zone.iteration.step.done' | 'zone.iteration.step.idle' | 'zone.phase.iteration-start' | 'zone.phase.autonomous-execute' | 'zone.phase.iteration-close' | 'zone.phase.pr-delivery' | 'zone.phase.merge-ready' | 'zone.branches.title' | 'zone.branches.iteration-base' | 'zone.branches.target' | 'zone.branches.spec-integration' | 'zone.tasks.title' | 'zone.tasks.total' | 'zone.tasks.no-plans' | 'zone.tasks.more' | 'zone.tasks.collapse' | 'zone.project.title' | 'zone.project.milestones' | 'zone.project.residuals' | 'zone.project.none' | 'zone.state.Todo' | 'zone.state.InProgress' | 'zone.state.InReview' | 'zone.state.Done' | 'zone.state.blocked-unknown' | 'zone.agents.title' | 'zone.agents.summary' | 'zone.agents.on-demand' | 'zone.agents.unknown-sub' | 'zone.agents.bucket.implementor' | 'zone.agents.bucket.reviewer' | 'zone.agents.group.phase-1' | 'zone.agents.group.phase-2' | 'zone.agents.group.phase-n' | 'zone.agents.group.plan' | 'zone.agents.group.no-plan' | 'zone.agents.group.plan-more' | 'flow.empty' | 'flow.settle-only' | 'flow.degraded' | 'flow.unexpected' | 'flow.in-flight' | 'flow.settled-ok' | 'flow.error' | 'flow.advisory' | 'flow.denied' | 'state.title' | 'state.selection' | 'state.selection.history' | 'state.plans' | 'state.residuals' | 'state.policy' | 'state.leases' | 'state.knowledge' | 'state.direction' | 'state.none' | 'state.enforcement' | 'state.enforcement.hard' | 'state.enforcement.soft' | 'state.plans.more' | 'state.residual.more' | 'state.policy.push' | 'state.policy.worktree' | 'state.policy.control-worktree' | 'state.knowledge.docs' | 'freshness.last-updated' | 'freshness.refresh-note';
125
+ export type PanelKey = 'view.mstar-workflow' | 'guide.description' | 'tab.tasks' | 'tab.agents' | 'tab.events' | 'page.iteration.not-started' | 'page.iteration.expand' | 'page.iteration.collapse' | 'event-log.section.events' | 'event-log.section.violations' | 'event-log.empty' | 'event-log.empty.events' | 'event-log.empty.violations' | 'event-log.field.role' | 'event-log.field.agent' | 'event-log.field.stage' | 'event-log.field.plan' | 'event-log.field.task' | 'event-log.field.category' | 'event-log.field.time' | 'event-log.field.kind' | 'event-log.field.status' | 'event-log.field.expected' | 'event-log.field.settled' | 'event-log.field.duration' | 'event-log.field.child-id' | 'event-log.field.run-id' | 'event-log.field.name' | 'event-log.field.members' | 'event-log.field.stop-reason' | 'event-log.field.severity' | 'event-log.field.code' | 'event-log.field.message' | 'event-log.kind.dispatch' | 'event-log.kind.settle' | 'event-log.yes' | 'event-log.no' | 'empty.waiting' | 'empty.loading' | 'empty.unavailable' | 'empty.no-harness' | 'empty.no-harness-hint' | 'watermark.version' | 'watermark.harness' | 'watermark.none' | 'panel.unknown' | 'graph.pass' | 'graph.fail' | 'zone.legend.title' | 'zone.legend.agent-running' | 'zone.legend.agent-settled' | 'zone.legend.agent-idle' | 'zone.iteration.step-label' | 'zone.iteration.step-badge' | 'zone.iteration.step.current' | 'zone.iteration.step.next' | 'zone.iteration.step.done' | 'zone.iteration.step.idle' | 'zone.phase.iteration-start' | 'zone.phase.autonomous-execute' | 'zone.phase.iteration-close' | 'zone.phase.pr-delivery' | 'zone.phase.merge-ready' | 'zone.branches.title' | 'zone.branches.iteration-base' | 'zone.branches.target' | 'zone.branches.spec-integration' | 'zone.tasks.title' | 'zone.tasks.total' | 'zone.tasks.no-plans' | 'zone.tasks.more' | 'zone.tasks.collapse' | 'zone.project.title' | 'zone.project.milestones' | 'zone.project.residuals' | 'zone.project.none' | 'zone.state.Todo' | 'zone.state.InProgress' | 'zone.state.InReview' | 'zone.state.Done' | 'zone.state.blocked-unknown' | 'zone.agents.title' | 'zone.agents.summary' | 'zone.agents.on-demand' | 'zone.agents.unknown-sub' | 'zone.agents.bucket.implementor' | 'zone.agents.bucket.reviewer' | 'zone.agents.group.phase-1' | 'zone.agents.group.phase-2' | 'zone.agents.group.phase-n' | 'zone.agents.group.plan' | 'zone.agents.group.no-plan' | 'zone.agents.group.plan-more' | 'flow.empty' | 'flow.settle-only' | 'flow.link-only' | 'flow.degraded' | 'flow.unexpected' | 'flow.in-flight' | 'flow.settled-ok' | 'flow.error' | 'flow.advisory' | 'flow.denied' | 'state.title' | 'state.selection' | 'state.selection.history' | 'state.selection.unbound' | 'state.selection.pick' | 'state.selection.pending' | 'state.selection.failed' | 'state.plans' | 'state.residuals' | 'state.policy' | 'state.leases' | 'state.knowledge' | 'state.direction' | 'state.none' | 'state.enforcement' | 'state.enforcement.hard' | 'state.enforcement.soft' | 'state.plans.more' | 'state.residual.more' | 'state.policy.push' | 'state.policy.worktree' | 'state.policy.control-worktree' | 'state.knowledge.docs' | 'freshness.last-updated' | 'freshness.refresh-note';
126
126
  declare module '@deepseek-ai/dsh-client-ui-slots' {
127
127
  interface LocaleNamespaceMap {
128
128
  'mstar-panel': PanelKey;
@@ -40,7 +40,7 @@
40
40
  *
41
41
  * Shared iteration section: the page renders the SAME `IterationInfoSection`
42
42
  * the tasks tab uses, from the SAME `view.iteration` data — 两个 tab 显示同一
43
- * 迭代信息块. The degradation note (degraded / empty / settle-only) is
43
+ * 迭代信息块. The degradation note (degraded / empty / settle-only / link-only) is
44
44
  * PROJECTED metadata, never inferred from the entity list, and the `Legend`
45
45
  * (the 3 role-card status treatments) sits in flow BELOW the list.
46
46
  *
@@ -14,9 +14,10 @@
14
14
  import * as React from 'react';
15
15
  import type { TranslateNS } from '@deepseek-ai/dsh-client-ui-slots';
16
16
  import type { MstarEngineStatusPayload, MstarHarnessState } from '../../types.ts';
17
- export interface SidebarProps {
17
+ import { type MstarSelectionSeat } from './state-section.tsx';
18
+ export interface SidebarProps extends MstarSelectionSeat {
18
19
  t: TranslateNS<'mstar-panel'>;
19
20
  state: MstarHarnessState | null;
20
21
  source: MstarEngineStatusPayload;
21
22
  }
22
- export declare function Sidebar({ t, state, source }: SidebarProps): React.JSX.Element;
23
+ export declare function Sidebar({ t, state, source, selection, pick, select }: SidebarProps): React.JSX.Element;
@@ -13,11 +13,33 @@
13
13
  */
14
14
  import * as React from 'react';
15
15
  import type { TranslateNS } from '@deepseek-ai/dsh-client-ui-slots';
16
- import type { MstarEngineStatusPayload, MstarHarnessState } from '../../types.ts';
17
- export interface StateSectionProps {
16
+ import type { MstarEngineStatusPayload, MstarHarnessState, WorkflowSelectionView } from '../../types.ts';
17
+ import type { MstarSelectionState } from './engine-status-client.ts';
18
+ /**
19
+ * The picker seat (D4): the selection the panel SHOWS, this session's own
20
+ * commit state, and the commit seat itself. Handed down as one unit from the
21
+ * hook result to the section that renders it.
22
+ */
23
+ export interface MstarSelectionSeat {
24
+ /**
25
+ * This session's CURRENT selection: the host's live control-state `binding`
26
+ * when the response carries one, else the last emission's own record
27
+ * (`state.selection`). `null` renders the unknown arm rather than throwing.
28
+ */
29
+ readonly selection: WorkflowSelectionView | null;
30
+ /** This session's picker state (control feedback, never a selection source). */
31
+ readonly pick: MstarSelectionState;
32
+ /**
33
+ * Commit a workflow pick for this session, or null when the panel cannot
34
+ * (no client / no session / unknown cwd). Null renders an error selection
35
+ * WITHOUT a picker: a row that cannot be committed must not look clickable.
36
+ */
37
+ readonly select: ((workflowId: string) => void) | null;
38
+ }
39
+ export interface StateSectionProps extends MstarSelectionSeat {
18
40
  t: TranslateNS<'mstar-panel'>;
19
41
  state: MstarHarnessState;
20
42
  /** Top-level enforcement flag (spec §2.1) — NOT part of the state digest. */
21
43
  enforcement: MstarEngineStatusPayload['enforcement'];
22
44
  }
23
- export declare function StateSection({ t, state, enforcement }: StateSectionProps): React.JSX.Element;
45
+ export declare function StateSection({ t, state, enforcement, selection, pick, select }: StateSectionProps): React.JSX.Element;
@@ -37,8 +37,8 @@ import type { ChatSnapshot } from '@deepseek-ai/dsh-client-ui-chat/client';
37
37
  import type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots';
38
38
  import type { SessionListState } from '@deepseek-ai/dsh-api-session-controller/client';
39
39
  import type { SessionId } from '@deepseek-ai/dsh-session';
40
- import type { MstarEngineStatusPayload } from '../../types.ts';
41
- import type { MstarEngineStatusClient } from './engine-status-client.ts';
40
+ import type { MstarEngineStatusPayload, WorkflowSelectionView } from '../../types.ts';
41
+ import type { MstarEngineStatusClient, MstarSelectionState } from './engine-status-client.ts';
42
42
  /** Selector hook over the Host session list (the `useSessions` standard seat). */
43
43
  export type UseSessions = SnapshotSelectorHook<SessionListState>;
44
44
  /** The panel's render state — a closed set: no shape can be both data and empty. */
@@ -66,6 +66,12 @@ export type MstarEngineStatusView =
66
66
  * identity (`at` timestamp and the agent turn it was written for). Both are
67
67
  * the host's records of the stored entry, so the panel can name which
68
68
  * emission it is showing rather than implying it is the newest one.
69
+ *
70
+ * `binding` is the host's CURRENT control-state selection for this session
71
+ * (`null` when it served none), deliberately separate from
72
+ * `payload.state.selection` — which keeps recording the last MODEL EMISSION,
73
+ * so the picker's acknowledgement shows immediately while plans / Event Log
74
+ * stay on the last emitted snapshot.
69
75
  */
70
76
  | {
71
77
  readonly state: 'ok';
@@ -73,6 +79,7 @@ export type MstarEngineStatusView =
73
79
  readonly payload: MstarEngineStatusPayload;
74
80
  readonly at: string;
75
81
  readonly turn: number;
82
+ readonly binding: WorkflowSelectionView | null;
76
83
  readonly reason: null;
77
84
  }
78
85
  /** An explicit degraded answer — always WITH a reason, never silently empty. */
@@ -100,6 +107,21 @@ export interface MstarEngineStatusSeats {
100
107
  /** The plugin's engine-status client (absent in a composition without one). */
101
108
  engineStatus: MstarEngineStatusClient | undefined;
102
109
  }
110
+ /**
111
+ * The panel hook's result: the render state plus this session's picker seat.
112
+ * `selection` is the served binding (absent until an `ok` render); `pick` is the
113
+ * local commit state for THIS session only; `select` is null exactly when no
114
+ * commit can be made (no client, no session, or unknown cwd).
115
+ */
116
+ export interface MstarEngineStatusHook {
117
+ readonly view: MstarEngineStatusView;
118
+ /** The served selection of the rendered payload (null unless `view.state === 'ok'`). */
119
+ readonly selection: WorkflowSelectionView | null;
120
+ /** This session's picker state, `idle` until a pick is committed. */
121
+ readonly pick: MstarSelectionState;
122
+ /** Commit a pick for this session; null when the panel cannot commit one. */
123
+ readonly select: ((workflowId: string) => void) | null;
124
+ }
103
125
  /** Latest engine-status anchor row (either accepted identity) in snapshot order, or null. */
104
126
  export declare function latestEngineStatusRow(nodes: readonly ConversationNode[]): ContextMessageNode | null;
105
127
  /** Anchor row message time, or null when the log carries no anchor row. */
@@ -110,6 +132,8 @@ export declare function selectSessionCwd(sessionId: SessionId | undefined): (sta
110
132
  * The panel's data hook (spec §5).
111
133
  *
112
134
  * @param seats - the session standard kit + the plugin's engine-status client.
113
- * @returns the explicit render state (never a throw, never a half-parsed view).
135
+ * @returns the explicit render state (never a throw, never a half-parsed view)
136
+ * plus this session's picker triple: the served `selection`, the local commit
137
+ * `pick`, and `select` (null when no pick can be committed).
114
138
  */
115
- export declare function useMstarEngineStatus(seats: MstarEngineStatusSeats): MstarEngineStatusView;
139
+ export declare function useMstarEngineStatus(seats: MstarEngineStatusSeats): MstarEngineStatusHook;