@mstar-harness/dsh 2.2.0 → 2.3.0
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/README.i18n.yaml +2 -3
- package/README.md +118 -3
- package/README.zh.md +118 -3
- package/bundle/README.md +11 -1
- package/dist/client/panel/graph/event-log.d.ts +18 -3
- package/dist/client/panel/graph/project-graph.d.ts +40 -14
- package/dist/client/panel/locale.d.ts +1 -1
- package/dist/client.js +131 -17
- package/dist/gates/_shared.d.ts +104 -5
- package/dist/gates/adapter.d.ts +34 -1
- package/dist/gates/agent-flow.d.ts +186 -4
- package/dist/gates/agent-personas.d.ts +44 -0
- package/dist/gates/dispatch.d.ts +64 -2
- package/dist/gates/fallbacks-advisory.d.ts +77 -0
- package/dist/gates/fallbacks-decoration.d.ts +108 -0
- package/dist/gates/fallbacks-probe.d.ts +63 -0
- package/dist/gates/fallbacks-seeds.d.ts +98 -0
- package/dist/gates/goal-bridge.d.ts +151 -0
- package/dist/gates/plan-mode-bridge.d.ts +70 -0
- package/dist/gates/skill-lint.d.ts +4 -3
- package/dist/gates/system-prompt.d.ts +121 -0
- package/dist/gates/workflow-ledger.d.ts +149 -0
- package/dist/gates/workflow-policy.d.ts +203 -0
- package/dist/index.d.ts +10 -4
- package/dist/index.js +1833 -60
- package/dist/types.d.ts +30 -5
- package/harness-agents/architect.md +35 -0
- package/harness-agents/code-reviewer.md +98 -0
- package/harness-agents/frontend-dev.md +33 -0
- package/harness-agents/fullstack-dev-2.md +33 -0
- package/harness-agents/fullstack-dev.md +33 -0
- package/harness-agents/ops-engineer.md +33 -0
- package/harness-agents/product-manager.md +35 -0
- package/harness-agents/project-manager.md +32 -0
- package/harness-agents/prompt-engineer.md +33 -0
- package/harness-agents/qa-engineer.md +35 -0
- package/harness-agents/qc-specialist-2.md +62 -0
- package/harness-agents/qc-specialist-3.md +62 -0
- package/harness-agents/qc-specialist.md +62 -0
- package/harness-agents/writing-specialist.md +35 -0
- package/harness-commands/codebase-audit.md +2 -2
- package/harness-skills/mstar-audit/SKILL.md +20 -0
- package/harness-skills/mstar-audit/references/audit-playbook.md +27 -0
- package/harness-skills/mstar-branch-worktree/SKILL.md +7 -0
- package/harness-skills/mstar-coding-behavior/SKILL.md +5 -0
- package/harness-skills/mstar-compound/references/compound-workflow.md +16 -0
- package/harness-skills/mstar-compound-refresh/SKILL.md +5 -0
- package/harness-skills/mstar-iteration/references/phase-4-5-pr-delivery.md +1 -0
- package/harness-skills/mstar-plan-artifacts/SKILL.md +1 -0
- package/harness-skills/mstar-plan-artifacts/references/status-and-residuals.md +8 -0
- package/harness-skills/mstar-review-qc/SKILL.md +6 -0
- package/harness-skills/mstar-roles/references/project-manager/qc-and-residuals.md +2 -1
- package/harness-skills/mstar-roles/references/qc-specialist/deep-review-lenses.md +16 -7
- package/harness-skills/mstar-roles/references/qc-specialist/report-template.md +17 -1
- package/harness-skills/mstar-roles/references/qc-specialist-shared.md +3 -1
- package/harness-skills/mstar-roles/references/writing-specialist.md +33 -0
- package/harness-skills/mstar-sdd/SKILL.md +5 -1
- package/harness-skills/mstar-sdd/references/file-handoffs.md +2 -0
- package/harness-skills/mstar-skill-authoring/SKILL.md +4 -2
- package/harness-skills/mstar-skill-authoring/references/regression-fixation.md +46 -0
- package/harness-skills/mstar-skill-authoring/references/skillsbench-authoring.md +37 -0
- package/package.json +7 -4
package/README.i18n.yaml
CHANGED
|
@@ -2,6 +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:
|
|
6
|
-
README.zh.md:
|
|
7
|
-
|
|
5
|
+
README.md: ebaf2a22fa9965e40e006ff311fafdc3971c6376
|
|
6
|
+
README.zh.md: a520154b43d50ef20c2fcf300af74571ff1b0f85
|
package/README.md
CHANGED
|
@@ -27,17 +27,29 @@ dsh plugin --profile web add .
|
|
|
27
27
|
|
|
28
28
|
`dsh plugin --profile <name> add <spec>` initializes the profile on first use (`web` starts from the shipped template: `@deepseek-ai/dsh-base` + `@deepseek-ai/dsh-web-app`), forwards `<spec>` to pnpm in the profile directory, and reconciles the profile's `dsh.profile.bundles` layer list from the installed state: any dependency whose package.json declares `dsh.bundle` joins the layer stack. Relative specs (`.`, `file:`/`link:`) anchor to the invoking directory, so `add .` runs from the package checkout; pnpm must be on PATH. A local checkout needs a prior `bun run build` (the `prepare` script is intentionally NOT used — the monorepo builds packages explicitly, matching cli/opencode).
|
|
29
29
|
|
|
30
|
+
**(c) Optional capability: `dsh-llm-fallbacks` (second command)** — the role-based subagent configuration capability (see LLM fallbacks integration) is a SEPARATE plugin row and must be installed with its own command:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
dsh plugin --profile web add dsh-llm-fallbacks
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The **two-command install is the contract** — folding a `dsh-llm-fallbacks` row into this bundle's patch is explicitly rejected (roadmap §8.3 F4): the loader has no insert-if-absent semantics, so a same-`id` insert is a `duplicate loader entry id` boot failure (the whole dsh session fails to start), and a different-`id` insert mounts the plugin twice — two `apply()` runs with split fallback state (per-context state stores, double listeners, config-override lottery) for anyone who also installs the package directly. Layer order is the reconcile append order: `dsh-llm-fallbacks` lands **after `dsh-base`/`llm-retry`** (its hard ordering requirement) and after the mstar row. Single-command multi-activation is an upstream feature gap (reconcile dedup or insert-if-absent patch semantics), not actionable from this repo.
|
|
37
|
+
|
|
30
38
|
### Configuration
|
|
31
39
|
|
|
32
40
|
| Key | Type | Default | Meaning |
|
|
33
41
|
| --- | --- | --- | --- |
|
|
34
42
|
| `harnessDir` | `string` | per-session workspace probe (`.mstar/` → `.agents/` → `.plans/` → `plans/`, from the session workspace root — **never the launch cwd**) | Explicit harness root; wins over engine probing. **Required for repos whose harness root is not a probed name** — e.g. this mstar-workflow repo itself uses `.harness/` (maintenance root, deliberately NOT probed); the probe starts from the session workspace root (never the launch cwd) and STOPS there — it never walks above the session workspace, so a harness dir above it (e.g. a global `~/.mstar`) is never adopted. |
|
|
35
43
|
| `enforcement` | `'hard' \| 'soft'` | compass, else warn-only | Per-deployment override. Precedence: Config wins; else the Assignment's own `**Enforcement**: hard` header flag (dispatch gate only); else the iteration compass frontmatter; else warn-only. Config `soft` is the ONLY local rollback — an Assignment-level `soft` does NOT override a hard compass. |
|
|
36
|
-
| `dispatchTools` | `string[]` | `['subagent']` | Delegation tool names the dispatch gate matches
|
|
44
|
+
| `dispatchTools` | `string[]` | `['subagent', 'subagent_fork']` | Delegation tool names the dispatch gate matches — the dsh preset's TWO delegation tools, `subagent` and its fork sibling `subagent_fork` (both carry Assignment-shaped `{ description, prompt }` args; a `toolName` config may rename instances). |
|
|
37
45
|
| `dispatchBinding` | `string` | unset (precheck skipped) | The dispatching agent's own harness role; an Assignment whose `Execute as` equals it is self-recursion. |
|
|
46
|
+
| `roleMap` | `Record<string, string>` | unset | mstar role id (`Execute as`) → dsh-llm-fallbacks role id. A taxonomy bridge for logging + future rule-driven interop ONLY — never consulted by the decoration (see LLM fallbacks integration). |
|
|
47
|
+
| `rolePersonas` | `Record<string, string>` | unset (bundled mirror default) | mstar role id (`Execute as`) → persona text; the role-based subagent decoration's **override** source — a role-matched `subagent/start` registers the persona as the child's `mstar:role-persona` system-prompt section; when unset for a role, the bundled `harness-agents/` mirror default is used (see LLM fallbacks integration). |
|
|
38
48
|
| `skillRoots` | `string[]` | unset (no custom-root registration) | Additional skill roots registered with the dsh skill-filesystem provider (`customSkillDirs` semantics — scanned before user roots). Dev-time: the mirror `<repo-root>/skills` absolute path. |
|
|
39
49
|
| `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. |
|
|
40
50
|
| `catalogTtlMs` | `number` | `60000` | Pre-step catalog cache refresh interval (ms): how often the per-workspace unified `mstar-engine-status` 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. |
|
|
51
|
+
| `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`. |
|
|
52
|
+
| `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. |
|
|
41
53
|
|
|
42
54
|
`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`).
|
|
43
55
|
|
|
@@ -86,6 +98,75 @@ Additive beyond the opencode field set: for writable dispatches whose Assignment
|
|
|
86
98
|
|
|
87
99
|
`fs/write-intent` listener scoped to `SKILL.md` files under the configured skill roots runs the engine skill-authoring lints (`lintFrontmatter` + `lintFiveQuestion` — the CLI `mstar skill lint` combination) on the pre-write on-disk document. The slot is **content-blind** (the intent waterfall carries only `(target, actor)`), so: missing file = first create = pass; clean on-disk doc = silent pass; violations in warn mode = advisory + delegate; violations in hard mode = **repair escape** — the document is ALREADY invalid, so this write may BE the repair (error-level log + `hard: true, repair: true` advisory with the enforced `hardBlocked` verdict). Enforcement resolves like the other gates (Config override, else the iteration compass, else warn-only). The gate never throws; read failures and unexpected errors degrade to allow with a `degraded: true` advisory. The typed hard veto (`SkillLintVetoError`, code `skill-lint.veto`) lives on the incoming-document branch (`lintSkillWrite`) — see Known Limitations for its current wiring.
|
|
88
100
|
|
|
101
|
+
### Workflow / ralph gate
|
|
102
|
+
|
|
103
|
+
A `tools/pre-execute` branch (BEFORE the subagent prompt branch) gates the **`workflow`** and **`ralph`** tool calls — the remaining model-reachable fan-out that carries no Assignment text. It matches the FIXED tool names (`workflow` / `ralph`); a renamed `workflow` instance is out of scope (the name guard is the fixed default). Non-workflow tools are untouched — the subagent branch owns them, semantics unchanged.
|
|
104
|
+
|
|
105
|
+
**Four-tier mode** (Config `workflowGate`, default `warn`): `off` (pass-through, no verdict row), `warn` (advisory-only), `ask` (first-seen names route through dsh's approval waterfall — `{kind:'ask'}`, fail-closed upstream; this gate invents no answerer), `hard` (policy violations veto before any child starts). The policy is the SINGLE decision point — P-b lease attribution runs FIRST and preempts P-a/P-c, then the P-a name allowlist, then P-c first-seen ask.
|
|
106
|
+
|
|
107
|
+
| Policy | `off` | `warn` (default) | `ask` | `hard` |
|
|
108
|
+
| --- | --- | --- | --- | --- |
|
|
109
|
+
| **P-b**: workspace has an `InProgress` plan without `execution_lease` coverage | allow (gate short-circuits `off`) | **warn** — allowed + advisory (`workflow.lease.uncovered`) + one warn | **warn** — allowed + advisory + one warn (the ask channel is for first-seen NAMES, never the workspace red line) | **deny** — veto before any child starts (`workflow.lease.uncovered`), reason cites the plan id |
|
|
110
|
+
| **P-a**: workflow name ∈ `workflowNames` (non-empty list) | allow (short-circuit) | allow — no advisory (P-a passes under every mode) | allow — no ask | allow |
|
|
111
|
+
| **P-a**: workflow name unknown (empty/absent list ⇒ **every** name unknown) | allow (short-circuit) | **warn** — allowed + advisory (`workflow.name.unknown`) + one warn | **ask** (first-seen) → `{kind:'ask'}`; the cached decision (allow/deny) is reused afterwards — never a re-ask for a resolved name | **deny** — veto before any child starts (`workflow.name.unknown`), reason names the name |
|
|
112
|
+
| **ralph** (no `meta.name` — no allowlist identity) | allow (short-circuit) | allow — P-a/P-c NEVER apply | allow — P-a/P-c NEVER apply | allow — P-a/P-c NEVER apply; P-b still applies (deny when uncovered) |
|
|
113
|
+
|
|
114
|
+
**Default-`warn` rationale.** `warn` is the default so the gate **never surprises a deployment into a hard block**: it is advisory-only unless the operator opts into `ask` (human ask channel) or `hard` (veto). An empty/absent `workflowNames` makes every name unknown — the gate is **not** "allow all" by omission, but the default mode turns that into an advisory, not a block.
|
|
115
|
+
|
|
116
|
+
**Interaction with `Enforcement: hard`.** The workflow gate's mode is its OWN Config knob — the cross-cutting `Enforcement: hard` resolution (compass / Assignment header / Config `enforcement`) does **NOT** escalate `workflowGate`. A hard-enforcement deployment still runs the workflow gate in its configured mode (default `warn` = advisory-only) unless it also sets `workflowGate: 'ask'` or `'hard'`; conversely `workflowGate: 'hard'` vetoes regardless of the cross-cutting resolution. The two must not be confused: the workflow gate closes the "ungated fan-out under Enforcement: hard" gap **only when the deployment opts its mode in**.
|
|
117
|
+
|
|
118
|
+
**Fail-open edges (documented, never crash a compliant call).** (1) Malformed args — a `workflow` call without a non-empty string `meta.name` (after control-char normalization), or a `ralph` call without a string `objective` → pass-through + ONE warn under **every** mode (hard included), and NO verdict row (no policy verdict was produced). A name that is only control characters normalizes to empty → malformed. (2) Unreadable `status.json` — the P-b status read through the contained resolver path throws → P-b is degraded for that call only with ONE warn; P-a/P-c (name-based, no status dependency) still run. The gate NEVER throws: every read is structural.
|
|
119
|
+
|
|
120
|
+
**Verdict ledger rows.** Every gated call records ONE durable `workflow-verdict` row in the agent-flow ledger (the P2 ledger plan's record path, fully contained — a failing ledger write never reaches the gate): `tool` (`workflow` | `ralph`), `workflow` (normalized `meta.name`) or `objective`, `mode` (never `off` — off short-circuits before the policy), and the verdict vocabulary **`ok` / `advisory` / `denied` / `ask`** (the `ask` verdict is the extension: a first-seen ask is itself a gated call, so its row carries `ask` until the approval waterfall resolves it — "one ledger row per gated call"). Violation codes come from the verdict, never guessed: `workflow.name.unknown` (P-a) vs `workflow.lease.uncovered` (P-b). Fail-open paths (malformed args / unreadable status) record nothing; calls with no resolved harness dir skip the row (same silent no-op as the dispatch record path).
|
|
121
|
+
|
|
122
|
+
**P-c answer-observation seam.** The gate cannot observe the ask outcome — the tool registry's `serviceAsk` consumes the approval result internally. The **run-start observation IS the answer seam**: an ALLOWED ask executes the call → the durable `tool-workflow/run-start` session event lands in the parent session log → the workflow-ledger consumer records the W-B2 `workflow-run` row AND caches `allow` for the run's name into the apply-scoped `WorkflowAskCache`. A DENIED answer produces no run → no observation → the next same-name call under `ask` **re-asks** (fail-closed — no grant evidence, never an invented allow). Cache keys are the **normalized** (ASCII control chars stripped) **uncapped** name at BOTH seams — the gate composes `meta.name` and the observation records `runName` through the SAME `normalizeWorkflowName`, so a control-char name (`au\u0000dit`) can never wedge the cache (asks once, observes under the same key), and a >1024-char name still keys on the full name (the ledger ROW display name is capped separately; the identity axis is never truncated). The cache is apply-scoped — a fresh apply (HMR reload) starts empty, so an unresolved first-seen re-asks per call until an observation (or an explicit `record()`) lands. A throwing cache record degrades the observation with one warn — the ledger row is already appended, the run is never affected.
|
|
123
|
+
|
|
124
|
+
**P-b preemption.** The lease red line runs FIRST: an uncovered `InProgress` plan in the calling workspace means NO writable fan-out should start children until the plan is recovered — independent of the workflow name (the same red line as the Assignment-keyed lease gate), and it applies to ralph too. Under `warn`/`ask` it is advisory-only (allowed + one warn); the ask channel never substitutes for the workspace red line.
|
|
125
|
+
|
|
126
|
+
## LLM fallbacks integration
|
|
127
|
+
|
|
128
|
+
The optional `dsh-llm-fallbacks` plugin (installed with the second command — see Install paths) powers **role-based subagent configuration**: a role-matched subagent dispatch injects the configured persona into the child session's system prompt. The mstar plugin declares it as a registry `dependencies` entry and imports it **type-only** — `dist/` carries **no bundled runtime import** of the package (only three string literals naming it — the probe's loader-entry match and the two decoration log lines — never an import; `--external dsh-llm-fallbacks` in the build stays as the guard for any future library-form import); the interop is a decision-point **capability probe**, never a module-internals read.
|
|
129
|
+
|
|
130
|
+
### Capability probe
|
|
131
|
+
|
|
132
|
+
Two views over the mounted state (point-in-time reads at decision points, no cache — loader mounts entries concurrently):
|
|
133
|
+
|
|
134
|
+
- `fallbacksService(ctx)` — the named cordis service (`ctx.get('llm-fallbacks')`) while the plugin is applied; `undefined` during HMR/fiber-swap windows even when the loader entry lives (the entry is declarative and outlives a fiber swap).
|
|
135
|
+
- `fallbacksMounted(ctx)` — capability view, **service-first with a loader-entries fallback**: the loader entry named `dsh-llm-fallbacks` is present, enabled (respecting `entry.disabled` and group rows), and has a live fiber.
|
|
136
|
+
|
|
137
|
+
Distinct states: **mounted** (service applied — full capability), **unmounted** (no entry: the fallbacks plugin was not installed — the mstar capability degrades, never breaks), **disabled** (entry present but disabled/grouped — capability off), and the HMR window (entry lives, service absent — the loader fallback covers it).
|
|
138
|
+
|
|
139
|
+
### Role-based decoration
|
|
140
|
+
|
|
141
|
+
Decoration rides the `subagent/start` EMIT — not `tools/pre-execute` (tool args are deep-frozen snapshots; persona/`agentOptions` come from tool-subagent's own Config, never call args). The synchronous listener resolves the published child via `ctx.get('agents')?.get(info.id)` and, when the child's seeded task prompt is Assignment-shaped, registers the persona as the child's **agent-scoped `mstar:role-persona` system-prompt section** (order 1 — right after the deployment persona slot; unwinds on child disposal). Role identity uses the SAME engine Assignment header grammar as the dispatch gate. Persona lookup is the single `personaFor` chain — `rolePersonas[executeAs]` → bundled mirror default → skip — **never gated on `roleMap` or on the fallbacks mounted state**.
|
|
142
|
+
|
|
143
|
+
**Zero-config defaults**: when `rolePersonas` has no entry for a role, the persona comes from the bundled `harness-agents/` mirror — the repo-root `agents/` shells synced by `bundle-assets` at build (shipped in the published tarball; package-relative resolution, so the bundle works from any launch cwd). The shell file stem is the role id; the default is its frontmatter `description` block scalar. A shell is eligible when its frontmatter `mode` is absent or `subagent` — the `primary` shell (`project-manager`) is never offered as a subagent persona default. A default whose description carries the interpolation hazard (`{{` paired with a later `}}`) is warned + skipped at extraction (never a boot throw); a shell edit (mtime change) re-extracts on the next decision-point read. With the mirror absent (`bundle-assets` not run) lookups are config-only, and a config miss logs one debug per apply.
|
|
144
|
+
|
|
145
|
+
**Unmounted degradation is same-channel + one debug log**: no fallbacks entry → the persona is still injected through the identical decoration channel (from the mstar Config or the harness-agents mirror default), with exactly one source-aware debug log; mounted → one info-level interop log carrying the service version. The listener never throws — `agents` service absent, child unresolvable, non-Assignment or role-unmatched prompts are skip/no-op (the dispatch itself is never affected).
|
|
146
|
+
|
|
147
|
+
**Persona text constraints**: dsh system-prompt renders persona text with STRICT `{{variable}}` interpolation, so persona values MUST NOT contain `{{` paired with a later `}}` (the renderer throws on unknown/malformed references at child prompt assembly — breaking every role-matched dispatch). The Config schema rejects such `rolePersonas` values at plugin mount with a clear error. A lone `{{` with no later `}}` renders as literal prose; the escape rule is single braces or rewording. Keep persona text concise (a few sentences); bound its length at deployment.
|
|
148
|
+
|
|
149
|
+
### Role seeds + adoption advisory
|
|
150
|
+
|
|
151
|
+
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) and the role's Required Skill Dependencies before acting.`); 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.
|
|
152
|
+
|
|
153
|
+
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**:
|
|
154
|
+
|
|
155
|
+
- **missing mstar roles** — an mstar id with no effective row (one warn listing them; the id set is derived from the `harness-agents/` mirror — never hardcoded; no mirror → the check is skipped with one debug);
|
|
156
|
+
- **persona overrides** — an mstar role whose row persona differs from the seed default (one warn naming them + the revert entry: the `fallbacks/revert-seed` gateway / the fallbacks settings-card rollback button — the operator override is retained until reverted);
|
|
157
|
+
- **empty personas** — rows with a missing/blank persona, only when non-seeded or overridden-empty (one warn naming them);
|
|
158
|
+
- **legacy keys** — `chains`, `roles.default`, `roles.list[].label`/`.description`, dangling `roles.rules[].role` references, via the applied service's own `detectLegacyKeys` (one warn citing its semantics);
|
|
159
|
+
- **declare skips/conflicts** — local skips (`interpolation` / `no-persona`) + upstream skips/conflicts (code `persona-source` — operator override retained) merge into ONE warn; seeded-at-default is silent (one debug naming the ids).
|
|
160
|
+
|
|
161
|
+
On the loader-fallback path (no service) the structural `roles.list` read is preserved (missing ids / empty personas; no revert entry — no seeds surface; the legacy-keys check is skipped — never reimplemented). A row config that is absent or not an object, or an unreadable `roles.list`, skips the pass with one debug log. The advisory **never writes the fallbacks config** — the only write path is the idempotent seeds re-declare through the released seeds surface (no-delta → no settings write upstream) — never throws, and is **not invoked when fallbacks is unmounted**: it is a signal, not a gate.
|
|
162
|
+
|
|
163
|
+
### Config surface
|
|
164
|
+
|
|
165
|
+
| Key | Type | Default | Meaning |
|
|
166
|
+
| --- | --- | --- | --- |
|
|
167
|
+
| `roleMap` | `Record<string, string>` | unset | mstar role id → fallbacks role id. **Taxonomy bridge** for logging + future rule-driven interop only — unused in decoration by design (persona injection is `rolePersonas`/mirror-sourced). |
|
|
168
|
+
| `rolePersonas` | `Record<string, string>` | unset (bundled mirror default) | mstar role id → persona text; the decoration's **override** source — a mirror default is used when a role has no entry. |
|
|
169
|
+
|
|
89
170
|
## Service
|
|
90
171
|
|
|
91
172
|
`apply` constructs `ctx.dshMstar` (engine-backed: `validateStatus`, `validateResidual`, `findingsCleanupGate`, `resolveCompassEnforcement`, `resolveHarnessDir`, `readHarnessVersion`, `applyEnforcement`). Layering: the P1 gates are co-located engine wrappers in this package importing the engine directly (same plugin, engine bundled at build time); `ctx.dshMstar` is the composition/test façade for inject consumers; the host adapter (below) is the host-facing facade. The engine is the single grammar for both paths. The companion entry `@mstar-harness/dsh/invariant` reserves package ownership with a documented no-op installer.
|
|
@@ -139,6 +220,32 @@ An advisory `agent/pre-step` waterfall listener appends ONE **`mstar-engine-stat
|
|
|
139
220
|
|
|
140
221
|
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.
|
|
141
222
|
|
|
223
|
+
## Agent-flow ledger (workflow rows)
|
|
224
|
+
|
|
225
|
+
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).
|
|
226
|
+
|
|
227
|
+
| `tool-workflow/*` event | Ledger row | Fields |
|
|
228
|
+
| --- | --- | --- |
|
|
229
|
+
| `run-start` | `workflow-run` | `runId`, `name`, `agent?` (the carrying parent session id) |
|
|
230
|
+
| `agent-start` | `workflow-agent` | `runId`, `seq` (1-based member sequence), `label`, `phase?`, `childId` |
|
|
231
|
+
| `run-end` | `workflow-run-end` | `runId`, `stopReason` (`completed` / `cancelled` / `error`) |
|
|
232
|
+
|
|
233
|
+
`tool-workflow/agent-end` is upstream member bookkeeping with **no ledger kind** (the member `outcome` is intentionally not persisted) and is filtered out. Optional fields (`agent` / `phase`) are omitted from the serialized line when absent (lossless-JSON discipline); the three kinds share the ledger's `AGENT_FLOW_MAX_EVENTS` truncation + size gate, and malformed lines narrow to `undefined` on read (never re-serialized). Display fields (`name` / `label` / `phase`) are length-capped deterministically at the boundary (`WORKFLOW_LEDGER_MAX_NAME_LENGTH` 1024, `WORKFLOW_LEDGER_MAX_LABEL_LENGTH` 512 — oversized values truncate with a `…` marker); id-sized fields (`runId` / `childId`, cap 512) SKIP the row when oversized — never truncated into collisions.
|
|
234
|
+
|
|
235
|
+
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`).
|
|
236
|
+
|
|
237
|
+
**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).
|
|
238
|
+
|
|
239
|
+
**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).
|
|
240
|
+
|
|
241
|
+
**Depth advisory (observe-time).** On `agent-start`, the consumer resolves the child session via `sessions.get(childId)` and warns when its `header.delegationDepth` is ≥ 2 — ONCE per run (per-runId latch), logger `mstar/workflow-ledger`. Observe-time only, **never a refusal path**: a throwing child read degrades the advisory, never the row or the run.
|
|
242
|
+
|
|
243
|
+
**No-behavior-change guarantee.** The consumer is observe-only: ZERO gating — every read and append is try/catch-contained; a failing ledger write never crashes or alters a workflow run; a throwing session read logs one warn and the pass continues. The `sessions` service is read STRUCTURALLY via `ctx.get('sessions')` — no runtime dependency on `@deepseek-ai/dsh-session`.
|
|
244
|
+
|
|
245
|
+
**Mount-order note.** The consumer activates only when the `sessions` service is available at apply — the **dsh-session row must mount BEFORE the mstar row** (the standard `web` profile order does). A composition where dsh-session mounts after the plugin (or is absent) degrades **silently**: ONE debug log (`sessions service absent — workflow-ledger consumer disabled`) and no rows are recorded — never an error, never a broken run.
|
|
246
|
+
|
|
247
|
+
**Panel visibility.** The three workflow rows render in the **事件记录 (Event Log) tab**'s Agent 流转事件 partition through the existing event-row chrome (no redesign): the summary identity is the run NAME (agent/end rows resolve it via the window lookup; fallback runId → 「未知」), the detail body adds four workflow fields — run-id / name / members / stop-reason (missing → 「—」) — and the expected/settled seats render 「—」 (a workflow row is not a role dispatch — no settle pairing exists, same precedent as settle rows). Unknown kind strings render as GENERIC rows (verbatim kind, no workflow fields) — never dropped, never guessed. The catalog summary counts workflow rows as a DISTINCT `workflow` bucket (`by role: workflow N` in the model-facing line) — never folded into dispatch-role counts.
|
|
248
|
+
|
|
142
249
|
## Web client plugin (workflow panel)
|
|
143
250
|
|
|
144
251
|
The package ships a browser client half for the dsh **web** profile, discovered
|
|
@@ -389,7 +496,7 @@ closed and archived 2026-08-10.
|
|
|
389
496
|
|
|
390
497
|
## Development
|
|
391
498
|
|
|
392
|
-
Commands (from `packages/dsh`): the coverage gate is per-file 100% on `src/` (dsh testing policy); the build bun-bundles the src entries into `dist/` (engine + schemastery inlined; `@deepseek-ai/cordis` and the runtime seam imports — `@deepseek-ai/dsh-skill-filesystem`, `@deepseek-ai/dsh-tools` (`defineTool`), `@deepseek-ai/dsh-llm` — external), runs `build-client` (`scripts/build-client-bundle.ts` — the closure-factory CJS browser bundle per spec §6.2, `dist/client.js`) and emits tsc declarations.
|
|
499
|
+
Commands (from `packages/dsh`): the coverage gate is per-file 100% on `src/` (dsh testing policy); the build bun-bundles the src entries into `dist/` (engine + schemastery inlined; `@deepseek-ai/cordis` and the runtime seam imports — `@deepseek-ai/dsh-skill-filesystem`, `@deepseek-ai/dsh-tools` (`defineTool`), `@deepseek-ai/dsh-llm` — external, plus the type-only `dsh-llm-fallbacks`), runs `build-client` (`scripts/build-client-bundle.ts` — the closure-factory CJS browser bundle per spec §6.2, `dist/client.js`) and emits tsc declarations.
|
|
393
500
|
|
|
394
501
|
```sh
|
|
395
502
|
bun test --coverage
|
|
@@ -443,6 +550,14 @@ The catalog row is appended at the END of the composed step messages, after dele
|
|
|
443
550
|
- **Profile-bundle install into the `web` profile: registry and local checkout** — `dsh plugin --profile web add @mstar-harness/dsh` (registry) and `add <local checkout>` are the supported paths; both run through the same pnpm + reconcile mechanism (the reconcile step joins `@mstar-harness/dsh` to `dsh.profile.bundles`). A local checkout needs a prior `bun run build` — the package has no `prepare` script (the monorepo builds packages explicitly), so an unbuilt checkout installs an empty `dist/`.
|
|
444
551
|
- **`lintSkillWrite` typed veto not production-wired** — the incoming-document hard veto (`SkillLintVetoError`, code `skill-lint.veto`) is exported and test-covered, but has no production caller yet: the engine `HostAdapter` has no content-carrying skill-write hook (only `beforeStatusWrite`/`beforeDispatch`/`beforeMerge`), and the fs intent slot is content-blind. Wiring lands with a future content-carrying hook; until then the listener path enforces only via the repair-escape advisory (never a veto).
|
|
445
552
|
- **CLI `HOST_SIGNALS` lacks the `subagent` token** — the engine `ToolSignal` union includes it and `detectHost` handles it, but `packages/cli` `HOST_SIGNALS` is not updated yet, so `mstar host detect --signals subagent` would reject until the CLI list is updated on upstreaming.
|
|
446
|
-
- **Entry is a module index over `src/gates/*`** — the split shipped: `src/index.ts` (371 lines) re-exports the frozen
|
|
553
|
+
- **Entry is a module index over `src/gates/*`** — the split shipped: `src/index.ts` (371 lines) re-exports the frozen 47-name export surface (28 value + 19 type-only names; `Config` counts once) from the gate modules (`_shared` / `status` / `skill-lint` / `seams` / `dispatch` / `catalog` / `tools` / `adapter`) and keeps the plugin manifest, the single cordis augmentation point, the command registration, and the `apply()` startup wiring. The surface is frozen by `tests/export-surface.spec.ts` — the runtime value-export set plus, under `typecheck:tests` (`bunx tsc --noEmit -p tests/tsconfig.json`), the value-namespace identity and the per-name type-only probes.
|
|
447
554
|
- **Engine dsh rows are upstreaming-destined** — the dsh changes to engine `host.ts` (`DetectResult`, `ToolSignal`, `resolveSkillRoot`) live in the mstar-workflow engine mirror and are intended for a user-authorized upstream PR into mstar-harness; the `mstar-host` skill mirror (§ Detect / § Resolve loaded skill root / `references/dsh.md`) updates with it.
|
|
448
555
|
- **Iteration stepper: Step 1 is compass-driven, Step 5 is schema-driven** — the zone dashboard's Step 1 (iteration-start) is the current step while the steering compass is `status: active` (Phase 1 in flight — no gate verdict, so no PASS/FAIL badge); Step 5 (merge-ready) is a schema constant the engine gate never lights as current (transition covers Phase 2→3→4 only), so it always renders idle — recorded in the iteration guide, not a defect. The full panel-limitation list lives in the Web client plugin section.
|
|
556
|
+
- **`dsh-llm-fallbacks` is a registry `dependencies` entry with type-only imports** — declared `^0.2.0` (caret range admits 0.2.1+; the probe shape-assertion test is the executable drift gate) and `--external` in the build, so `dist/` carries no bundled runtime import (only three string literals naming the package — the probe's loader-entry match and the two decoration log lines; the advisory logs say `fallbacks`); activation is a SEPARATE explicit install (two-command contract), never transitive. The library-form dependency exists so future value imports resolve without a manifest change; `--external` remains the guard.
|
|
557
|
+
- **Role→model override NOT delivered this batch** — routing a role to a fallbacks `model` (or persona via fallbacks rules) would require rewriting the child's `agentOptions` on the start request, but start-request options are caller-controlled (tool-subagent's own Config; call args are `description`/`prompt`/`run_in_background` only, deep-frozen). Awaits upstream `fallbacks-explicit-role-tool` or the N-B1 systemPrompt adoption (roadmap §10.4).
|
|
558
|
+
- **Decoration is a minimal per-child section, not the N-B1 systemPrompt adoption** — `mstar:role-persona` is one agent-scoped section on the child's context; no harness-rules sections, no PromptContext, no variables. N-B1 (roadmap §10.4) may absorb or replace this channel later without changing the observable (AC-3).
|
|
559
|
+
- **Persona injection is fallbacks-independent** — `dsh-llm-fallbacks` only routes LLM failures; it is never required for decoration. Unmounted → the same persona lands from the mstar Config through the same channel with one debug log (AC-4). If `ctx.get('agents')` is absent in a composition (no dsh-agent), decoration is skipped with one debug log.
|
|
560
|
+
- **Fork gating is default-only; explicit `dispatchTools` can omit `subagent_fork`** — a custom `dispatchTools` list overrides the default wholesale (pre-existing rename pattern), so a deployment that declares its own list must include `subagent_fork` to keep fork dispatches gated.
|
|
561
|
+
- **Persona values must not contain `{{...}}`** — dsh system-prompt renders persona text with strict `{{variable}}` interpolation and throws on a `{{` paired with a later `}}` (unknown/malformed/undefined reference), which would break child prompt assembly for every role-matched dispatch. The Config schema rejects such `rolePersonas` values at plugin mount with a clear error; the escape rule is single braces or rewording (a lone `{{` with no later `}}` renders as literal prose).
|
|
562
|
+
- **Seed re-convergence after a fallbacks HMR re-mount is bounded by the seeded-only preservation design** — the seed registry is per-apply in-memory state, so a fiber swap (HMR / settings edit) drops it and both declarers re-declare from scratch. The mstar re-declare merge-preserves ONLY ids that are already seeded (seeded-only by design), so in the preset-last commit ordering the preset rows' seeded annotations are not restored by the re-declare — they recover at the NEXT fallbacks apply (the upstream preset self-declare re-seeds them). The advisory one-shot latch re-arms when the `llm-fallbacks` service disappears (an inject teardown), so the next decision point re-converges the mstar side; the preset side is a documented transient of the seeded-only preservation design.
|
|
563
|
+
- **Advisory skip reason `no-persona` also covers extraction failures** — a mirror default rejected at extraction (e.g. a `{{...}}` interpolation hazard) surfaces in the consolidated declare-outcome line as `no-persona` (extraction returns no usable persona before the hazard gate runs); per-id extraction diagnostics stay on the debug channel (`mstar/fallbacks-advisory` / `mstar/fallbacks-seeds`).
|
package/README.zh.md
CHANGED
|
@@ -27,17 +27,29 @@ dsh plugin --profile web add .
|
|
|
27
27
|
|
|
28
28
|
`dsh plugin --profile <name> add <spec>` 首次使用时初始化 profile(`web` 从出厂模板起步:`@deepseek-ai/dsh-base` + `@deepseek-ai/dsh-web-app`),把 `<spec>` 转发给 profile 目录中的 pnpm,并按已安装状态对账 `dsh.profile.bundles` 层列表:任何 package.json 声明 `dsh.bundle` 的依赖都会加入层栈。相对 spec(`.`、`file:`/`link:`)锚定调用目录,因此 `add .` 须在包检出目录内执行;pnpm 须在 PATH 上。local checkout 需要先执行过 `bun run build`(本包**不使用** `prepare` 脚本——monorepo 与 cli/opencode 一致,显式构建各包)。细节、层位置与出厂默认见 [`bundle/README.md`](bundle/README.md)——registry 与 local checkout 形态均走同一 pnpm + reconcile 机制。`cordis` 与 `@deepseek-ai/dsh-*` 各 seam 均为 peerDependencies——由组合后的 dsh 应用提供。
|
|
29
29
|
|
|
30
|
+
**(c)可选能力:`dsh-llm-fallbacks`(第二条命令)**——基于角色的 subagent 配置能力(见 LLM fallbacks integration)是**独立的插件行**,须以单独命令安装:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
dsh plugin --profile web add dsh-llm-fallbacks
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**双命令安装即契约**——把 `dsh-llm-fallbacks` 行折叠进本 bundle 的补丁**明确否决**(roadmap §8.3 F4):loader 没有 insert-if-absent 语义,因此同 `id` 插入是 `duplicate loader entry id` 启动失败(整个 dsh 会话无法启动);异 `id` 插入则插件被挂载两次——对同时直接安装该包的人,会出现两次 `apply()` 与分裂的 fallback 状态(各自独立的 state store、双份监听器、配置覆盖抽签)。层序为 reconcile 追加序:`dsh-llm-fallbacks` 落在 **`dsh-base`/`llm-retry` 之后**(其硬性排序要求)并位于 mstar 行之后。单命令多激活是上游功能缺口(reconcile 去重或 insert-if-absent 补丁语义),本仓库无法实施。
|
|
37
|
+
|
|
30
38
|
### Configuration
|
|
31
39
|
|
|
32
40
|
| Key | Type | Default | Meaning |
|
|
33
41
|
| --- | --- | --- | --- |
|
|
34
42
|
| `harnessDir` | `string` | 按会话工作区探测(`.mstar/` → `.agents/` → `.plans/` → `plans/`,从会话工作区根目录开始——**绝不从启动 cwd**) | 显式 harness 根目录;优先于 engine 探测。**harness 根不在探测名列表中的仓库必须配置**——例如本 mstar-workflow 仓库自身用 `.harness/`(维护根,刻意不探测);探测从会话工作区根开始(绝不从启动 cwd)并在那里**停止**——永不越过会话工作区向上,因此其上方的 harness 目录(如全局 `~/.mstar`)永远不会被采纳。 |
|
|
35
43
|
| `enforcement` | `'hard' \| 'soft'` | compass,否则仅告警 | 按部署覆盖。优先级:Config 优先;否则取 Assignment 自身的 `**Enforcement**: hard` 头字段(仅派发闸门);否则取迭代 compass frontmatter;否则仅告警。Config `soft` 是唯一的本地回滚——Assignment 级 `soft` 不能覆盖 hard compass。 |
|
|
36
|
-
| `dispatchTools` | `string[]` | `['subagent']` |
|
|
44
|
+
| `dispatchTools` | `string[]` | `['subagent', 'subagent_fork']` | 派发闸门匹配的委派工具名——dsh preset 的**两个**委派工具:`subagent` 及其 fork 兄弟 `subagent_fork`(两者都携带 Assignment 形态的 `{ description, prompt }` 参数;`toolName` 配置可重命名实例)。 |
|
|
37
45
|
| `dispatchBinding` | `string` | 未设置(跳过预检) | 派发方 agent 自身的 harness 角色;Assignment 的 `Execute as` 等于它即自我递归。 |
|
|
46
|
+
| `roleMap` | `Record<string, string>` | 未设置 | mstar 角色 id(`Execute as`)→ dsh-llm-fallbacks 角色 id。**仅**作日志与未来规则驱动互操作的分类桥——装饰从不读取它(见 LLM fallbacks integration)。 |
|
|
47
|
+
| `rolePersonas` | `Record<string, string>` | 未设置(打包镜像默认) | mstar 角色 id(`Execute as`)→ persona 文本;基于角色的 subagent 装饰的**覆盖**来源——角色匹配的 `subagent/start` 会把 persona 注册为子会话的 `mstar:role-persona` system-prompt 段;某角色未设置时使用打包的 `harness-agents/` 镜像默认值(见 LLM fallbacks integration)。 |
|
|
38
48
|
| `skillRoots` | `string[]` | 未设置(不注册自定义根) | 向 dsh skill-filesystem 提供者注册的额外技能根(`customSkillDirs` 语义——先于用户根扫描)。开发期:镜像 `<repo-root>/skills` 的绝对路径。 |
|
|
39
49
|
| `bundledSkillDir` | `string` | 打包的 `harness-skills/` 镜像(包相对路径) | 向 dsh skill-filesystem 提供者注册的打包技能根(`bundledSkillDir` 语义——最后扫描、受信任)。默认取包内自带的 `harness-skills/` 镜像(`bundle-assets` 同步;gitignore)——包相对路径,**非** cwd 锚定。显式值优先。 |
|
|
40
50
|
| `catalogTtlMs` | `number` | `60000` | pre-step catalog 缓存刷新间隔(毫秒):按工作区缓存的统一 `mstar-engine-status` 行(水印 + 迭代闸门 + 工作区摘要)多久重读一次 `status.json` / compass / 知识索引。刷新间隔之间热路径只是时间戳比较 + Map 命中;会话中 plan/compass/residual 的变化会在一个间隔内落地。 |
|
|
51
|
+
| `workflowGate` | `'off' \| 'warn' \| 'ask' \| 'hard'` | `'warn'` | workflow/ralph 闸门模式(见 Gates → Workflow / ralph gate)。`off` = 直通且不产生 verdict 行;`warn` = 仅咨询;`ask` = 首见名字走审批瀑布(P-c);`hard` = 策略违规在任何子进程启动前否决。默认 `warn` 不改任何 hard 行为——除非部署显式选入 `ask`/`hard`,闸门仅咨询。 |
|
|
52
|
+
| `workflowNames` | `string[]` | 未设置 | workflow 名字白名单(P-a):被闸门视为 KNOWN 的 `meta.name` 值。为空或缺省 ⇒ **每个**名字都 unknown(有文档——闸门**绝不**因缺省而"全放行")。ralph 调用不携带 `meta.name`——P-a 对其永不适用。 |
|
|
41
53
|
|
|
42
54
|
`bundledSkillDir` 默认取包内自带的 `harness-skills/` 镜像(见 Skills mount)——显式 Config 值仍然优先。相对覆盖仍是 **cwd 锚定**(skill-filesystem 以 `join()` 语义相对 dsh **进程 cwd** 解析),因此覆盖默认的部署应在 **profile 层传绝对路径**(见 `bundle/README.md`)。
|
|
43
55
|
|
|
@@ -86,6 +98,75 @@ profile bundle 组合出以下行——注册表行来自 `@deepseek-ai/dsh-base
|
|
|
86
98
|
|
|
87
99
|
作用于已配置技能根下 `SKILL.md` 文件的 `fs/write-intent` 监听器,对写入前的磁盘文档运行 engine 技能撰写 lint(`lintFrontmatter` + `lintFiveQuestion`——与 CLI `mstar skill lint` 组合一致)。该槽位**内容盲**(intent 瀑布链只携带 `(target, actor)`):文件缺失 = 首次创建 = 放行;磁盘文档干净 = 静默放行;告警模式下有违规 = 咨询 + 委托;hard 模式下有违规 = **修复逃生**——文档**已经**非法,本次写入可能就是修复本身(error 级日志 + `hard: true, repair: true` 咨询,携带强制执行后的 `hardBlocked` 判定)。强制执行解析方式与其他闸门相同(Config 覆盖优先,否则取迭代 compass,否则仅告警)。闸门从不抛出;读取失败与意外错误降级为放行并发出 `degraded: true` 咨询。类型化 hard 否决(`SkillLintVetoError`,码 `skill-lint.veto`)位于传入文档分支(`lintSkillWrite`)——当前接线见 Known Limitations。
|
|
88
100
|
|
|
101
|
+
### Workflow / ralph gate
|
|
102
|
+
|
|
103
|
+
`tools/pre-execute` 的一个分支(位于 subagent prompt 分支**之前**)把关 **`workflow`** 与 **`ralph`** 工具调用——这是剩余的不携带 Assignment 文本、模型可达的扇出路径。它匹配**固定**工具名(`workflow` / `ralph`);重命名后的 `workflow` 实例不在范围(名字守卫是固定默认)。非 workflow 工具不受影响——subagent 分支照旧拥有它们,语义不变。
|
|
104
|
+
|
|
105
|
+
**四级模式**(Config `workflowGate`,默认 `warn`):`off`(直通,无 verdict 行)、`warn`(仅咨询)、`ask`(首见名字走 dsh 审批瀑布——`{kind:'ask'}`,上游 fail-closed;本闸门不自造应答器)、`hard`(策略违规在任何子进程启动前否决)。策略是**单一**决策点——P-b 租约归属**最先**运行并抢占 P-a/P-c,然后才是 P-a 名字白名单,最后 P-c 首见 ask。
|
|
106
|
+
|
|
107
|
+
| 策略 | `off` | `warn`(默认) | `ask` | `hard` |
|
|
108
|
+
| --- | --- | --- | --- | --- |
|
|
109
|
+
| **P-b**:调用工作区存在 `InProgress` 且无 `execution_lease` 覆盖的 plan | allow(闸门短路 `off`) | **warn**——放行 + 咨询(`workflow.lease.uncovered`)+ 一条 warn | **warn**——放行 + 咨询 + 一条 warn(ask 通道只服务首见**名字**,绝不替代工作区红线) | **deny**——在任何子进程启动前否决(`workflow.lease.uncovered`),reason 引用 plan id |
|
|
110
|
+
| **P-a**:workflow 名字 ∈ `workflowNames`(非空列表) | allow(短路) | allow——无咨询(P-a 在任何模式下都放行) | allow——无 ask | allow |
|
|
111
|
+
| **P-a**:workflow 名字 unknown(空/缺省列表 ⇒ **每个**名字都 unknown) | allow(短路) | **warn**——放行 + 咨询(`workflow.name.unknown`)+ 一条 warn | **ask**(首见)→ `{kind:'ask'}`;之后复用缓存决策(allow/deny)——已解析名字**绝不**再 ask | **deny**——在任何子进程启动前否决(`workflow.name.unknown`),reason 点名该名字 |
|
|
112
|
+
| **ralph**(无 `meta.name`——无白名单身份) | allow(短路) | allow——P-a/P-c 永不适用 | allow——P-a/P-c 永不适用 | allow——P-a/P-c 永不适用;P-b 仍适用(uncovered 时 deny) |
|
|
113
|
+
|
|
114
|
+
**默认 `warn` 的理由。** 默认 `warn` 使闸门**绝不**让部署意外吃硬阻断:除非操作者显式选入 `ask`(人工 ask 通道)或 `hard`(否决),闸门仅咨询。`workflowNames` 空/缺省使每个名字都 unknown——闸门**绝不**因缺省而"全放行",但默认模式把这一点变成咨询而非阻断。
|
|
115
|
+
|
|
116
|
+
**与 `Enforcement: hard` 的交互。** workflow 闸门的模式是它**自己**的 Config 旋钮——跨切面的 `Enforcement: hard` 解析(compass / Assignment 头字段 / Config `enforcement`)**不会**升级 `workflowGate`。hard-enforcement 部署仍按已配置模式运行 workflow 闸门(默认 `warn` = 仅咨询),除非同时设置 `workflowGate: 'ask'` 或 `'hard'`;反之 `workflowGate: 'hard'` 与跨切面解析无关地否决。二者不可混淆:workflow 闸门**只在部署把模式选入**时才关闭 "Enforcement: hard 下的未把关扇出" 缺口。
|
|
117
|
+
|
|
118
|
+
**Fail-open 边缘(有文档,绝不崩溃合规调用)。**(1)畸形参数——`workflow` 调用缺少非空字符串 `meta.name`(控制字符归一化之后),或 `ralph` 调用缺少字符串 `objective` → 在**每个**模式下(hard 亦然)直通 + 一条 warn,且**无** verdict 行(未产生策略判定)。只含控制字符的名字归一化为空 → 视为畸形。(2)`status.json` 不可读——经含容解析器路径的 P-b 状态读取抛出 → 仅本次调用的 P-b 降级 + 一条 warn;P-a/P-c(基于名字,无状态依赖)照常运行。闸门**从不抛出**:每次读取都是结构化的。
|
|
119
|
+
|
|
120
|
+
**Verdict 账本行。** 每个被把关的调用都在 agent-flow 账本记录**一条**持久化 `workflow-verdict` 行(P2 账本 plan 的记录路径,完全含容——账本写入失败绝不波及闸门):`tool`(`workflow` \| `ralph`)、`workflow`(归一化后的 `meta.name`)或 `objective`、`mode`(绝不为 `off`——off 在策略前短路)、判定词汇 **`ok` / `advisory` / `denied` / `ask`**(`ask` 判定是本次扩展:首见 ask 本身也是被把关的调用,其行携带 `ask` 直到审批瀑布解析——"每次被把关的调用一行")。违规码来自判定、绝不猜测:`workflow.name.unknown`(P-a)vs `workflow.lease.uncovered`(P-b)。fail-open 路径(畸形参数 / 状态不可读)不记录;未解析出 harness 目录的调用跳过该行(与派发记录路径相同的静默 no-op)。
|
|
121
|
+
|
|
122
|
+
**P-c 答案观测 seam。** 闸门无法观测 ask 结果——工具注册表的 `serviceAsk` 在内部消费审批结果。**run-start 观测就是答案 seam**:被 ALLOW 的 ask 执行调用 → 持久化 `tool-workflow/run-start` 会话事件落入父会话日志 → workflow-ledger 消费者记录 W-B2 `workflow-run` 行**并**把 `allow` 按运行名缓存进 apply 作用域的 `WorkflowAskCache`。被 DENY 的答案不产生运行 → 无观测 → `ask` 模式下下一次同名调用**重新 ask**(fail-closed——无授权证据,绝不发明 allow)。缓存键在两个 seam 都是**归一化**(剥离 ASCII 控制字符)**不截断**的名字——闸门合成 `meta.name` 与观测记录 `runName` 都走同一个 `normalizeWorkflowName`,因此含控制字符的名字(`au\u0000dit`)永远无法卡死缓存(ask 一次、同一键观测),>1024 字符的名字仍以完整名字为键(账本行的展示名字单独截断;身份轴从不截断)。缓存是 apply 作用域的——新 apply(HMR 重载)从空开始,因此未解析的首见名字每次调用都会重新 ask,直到一次观测(或显式 `record()`)落地。缓存记录抛错时观测降级为一条 warn——账本行已追加,运行不受影响。
|
|
123
|
+
|
|
124
|
+
**P-b 抢占。** 租约红线最先运行:调用工作区存在 uncovered 的 `InProgress` plan 意味着在 plan 恢复前**不应**启动任何可写扇出子进程——与 workflow 名字无关(与 Assignment 键控的租约闸门同一条红线),对 ralph 同样适用。`warn`/`ask` 下仅咨询(放行 + 一条 warn);ask 通道绝不替代工作区红线。
|
|
125
|
+
|
|
126
|
+
## LLM fallbacks integration
|
|
127
|
+
|
|
128
|
+
可选的 `dsh-llm-fallbacks` 插件(以第二条命令安装——见 Install paths)驱动**基于角色的 subagent 配置**:角色匹配的 subagent 派发会把配置的 persona 注入子会话的 system prompt。mstar 插件将其声明为 registry `dependencies` 条目并**仅作类型导入**——`dist/` 对该包**无打包运行时导入**(仅 3 处命名该包的字符串字面量——探测的 loader 条目匹配与两条装饰日志——绝非导入;构建中的 `--external dsh-llm-fallbacks` 仍是对未来库形态导入的护栏);互操作是决策点**能力探测**,绝不读取其他插件的模块内部。
|
|
129
|
+
|
|
130
|
+
### 能力探测
|
|
131
|
+
|
|
132
|
+
挂载状态的两个视图(决策点即时读取,无缓存——loader 并发挂载条目):
|
|
133
|
+
|
|
134
|
+
- `fallbacksService(ctx)`——插件被 apply 期间命名的 cordis 服务(`ctx.get('llm-fallbacks')`);HMR/纤程切换窗口内即使 loader 条目仍在,服务也为 `undefined`(条目是声明式的,比纤程活得久)。
|
|
135
|
+
- `fallbacksMounted(ctx)`——能力视图,**服务优先 + loader 条目回退**:名为 `dsh-llm-fallbacks` 的 loader 条目存在、启用(尊重 `entry.disabled` 与 group 行)且纤程存活。
|
|
136
|
+
|
|
137
|
+
状态区分:**mounted**(服务已 apply——完整能力)、**unmounted**(无条目:未安装 fallbacks 插件——mstar 能力降级,绝不中断)、**disabled**(条目存在但禁用/分组——能力关闭),以及 HMR 窗口(条目在、服务缺——loader 回退覆盖)。
|
|
138
|
+
|
|
139
|
+
### 基于角色的装饰
|
|
140
|
+
|
|
141
|
+
装饰挂在 `subagent/start` EMIT 上——而非 `tools/pre-execute`(工具参数是深度冻结快照;persona/`agentOptions` 来自 tool-subagent 自己的 Config,绝不来自调用参数)。同步监听器经 `ctx.get('agents')?.get(info.id)` 解析已发布的子会话,当子会话的种子任务提示为 Assignment 形态时,把 persona 注册为子会话的 **agent 作用域 `mstar:role-persona` system-prompt 段**(order 1——紧随部署 persona 槽之后;子会话销毁时自动卸载)。角色身份使用与派发闸门**相同**的 engine Assignment 头语法。persona 查找是单一 `personaFor` 链——`rolePersonas[executeAs]` → 打包镜像默认值 → 跳过——**绝不**以 `roleMap` 或 fallbacks 挂载状态为前提。
|
|
142
|
+
|
|
143
|
+
**零配置默认值**:当 `rolePersonas` 未为某角色配置条目时,persona 取自打包的 `harness-agents/` 镜像——构建时由 `bundle-assets` 从仓库根 `agents/` 同步(随发布 tarball 携带;包相对路径解析,任意启动 cwd 均可用)。镜像文件名主干即角色 id;默认值为其 frontmatter `description` 块标量。镜像 shell 在 frontmatter `mode` 缺失或为 `subagent` 时才有资格——`primary` shell(`project-manager`)绝不作为 subagent persona 默认值。默认值 description 若含插值风险(配对的 `{{`/`}}`)则在提取时告警并跳过(绝非启动抛错);shell 改动(mtime 变化)会在下一次决策点读取时重新提取。镜像缺失(未运行 `bundle-assets`)时查找仅走配置,配置也未命中时每次 apply 记一条 debug。
|
|
144
|
+
|
|
145
|
+
**未挂载的降级为同通道 + 一条 debug 日志**:无 fallbacks 条目 → persona 仍经完全相同的装饰通道注入(来自 mstar Config 或 harness-agents 镜像默认值),仅多一条来源可辨的 debug 日志;已挂载 → 一条携带服务版本的 info 级互操作日志。监听器绝不抛出——`agents` 服务缺失、子会话无法解析、非 Assignment 或角色未匹配的提示均跳过/无操作(派发本身永不受影响)。
|
|
146
|
+
|
|
147
|
+
**persona 文本约束**:dsh system-prompt 以**严格 `{{variable}}` 插值**渲染 persona 文本,因此 persona 值**绝不能**包含与后文 `}}` 配对的 `{{`(渲染器在子会话提示组装时对未知/畸形引用直接抛错——会破坏每一次角色匹配的派发)。Config schema 会在插件挂载时以清晰报错拒绝此类 `rolePersonas` 值。不带后续 `}}` 的孤立 `{{` 按字面散文渲染(安全);转义规则是改用单花括号或改写措辞。persona 文本保持简短(几句话);长度在部署侧设限。
|
|
148
|
+
|
|
149
|
+
### 角色 seeds 与采纳建议(Adoption advisory)
|
|
150
|
+
|
|
151
|
+
当可选的 `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) and the role's Required Skill Dependencies before acting.`);含 `{{...}}` 插值风险的 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。
|
|
152
|
+
|
|
153
|
+
一条只告警的采纳建议通道(日志器 `mstar/fallbacks-advisory`)**每次 apply 只跑一遍**——apply 时先尝试一次;当 fallbacks 行在 `dsh` 之后挂载(loader 并发挂载条目)时,改在首个 `subagent/start` 决策点只跑一遍。服务存在时,通道**先 await 幂等 re-declare**(闭合 boot 竞争窗口)再读取**有效状态**(`getEffectiveRoles`),并按**每类至多一条告警**有界报告:
|
|
154
|
+
|
|
155
|
+
- **缺失 mstar 角色**——无有效行的 mstar id(一条告警列出全部;id 集合派生自 `harness-agents/` 镜像——绝不硬编码;无镜像 → 检查跳过并记一条 debug);
|
|
156
|
+
- **persona 覆盖**——行 persona 与 seed 默认值不同的 mstar 角色(一条告警点名 + revert 入口:`fallbacks/revert-seed` gateway / fallbacks 设置卡片回滚按钮——操作者覆盖在 revert 前保留);
|
|
157
|
+
- **空 persona**——`persona` 缺失/空白的行,仅限非 seeded 或 overridden-empty(一条告警点名);
|
|
158
|
+
- **遗留键**——`chains`、`roles.default`、`roles.list[].label`/`.description`、悬空 `roles.rules[].role` 引用,经已应用服务自带的 `detectLegacyKeys`(一条告警引用其语义);
|
|
159
|
+
- **declare 跳过/冲突**——本地跳过(`interpolation` / `no-persona`)+ 上游跳过/冲突(码 `persona-source`——操作者覆盖保留)合并为**一条**告警;默认已 seeded 静默(一条 debug 点名)。
|
|
160
|
+
|
|
161
|
+
loader 回退路径(无服务)保留结构化 `roles.list` 读取(缺失 id / 空 persona;无 revert 入口——无 seeds 面;遗留键检查跳过——绝不重新实现)。行配置缺失或非对象、或 `roles.list` 不可读 → 跳过并记一条 debug。该建议**绝不写入** fallbacks 配置——唯一写路径是经已发布 seeds 面的幂等 seeds re-declare(无差异 → 上游零设置写入)——绝不抛出,且 **fallbacks 未挂载时不调用**:它是信号,不是闸门。
|
|
162
|
+
|
|
163
|
+
### 配置面
|
|
164
|
+
|
|
165
|
+
| Key | Type | Default | Meaning |
|
|
166
|
+
| --- | --- | --- | --- |
|
|
167
|
+
| `roleMap` | `Record<string, string>` | 未设置 | mstar 角色 id → fallbacks 角色 id。**仅**作日志与未来规则驱动互操作的分类桥——装饰按设计不使用(persona 注入以 `rolePersonas`/镜像为源)。 |
|
|
168
|
+
| `rolePersonas` | `Record<string, string>` | 未设置(打包镜像默认) | mstar 角色 id → persona 文本;装饰的**覆盖**来源——某角色无条目时使用镜像默认值。 |
|
|
169
|
+
|
|
89
170
|
## Service
|
|
90
171
|
|
|
91
172
|
`apply` 构造 `ctx.dshMstar`(engine 支撑:`validateStatus`、`validateResidual`、`findingsCleanupGate`、`resolveCompassEnforcement`、`resolveHarnessDir`、`readHarnessVersion`、`applyEnforcement`)。分层:P1 各闸门是本包内与 engine 同置的包装器,直接导入 engine(同一插件,构建时打包 engine);`ctx.dshMstar` 是供 inject 消费者使用的组合/测试外观;宿主适配器(下节)是面向宿主的门面。两条路径共用 engine 这唯一语法源。伴随入口 `@mstar-harness/dsh/invariant` 以文档化的空安装器保留包所有权。
|
|
@@ -139,6 +220,32 @@ mstar 技能通过 dsh skill-filesystem 提供者以**单一规范挂载**接入
|
|
|
139
220
|
|
|
140
221
|
该行是 **digest 门控**的:按 agent+workspace,每个 turn 只注入一次,仅当渲染文本变化时重新注入——20 步的 turn 只显示一次 catalog,而不是 20 次。source 共享**同一**按工作区缓存条目:显式 `harnessDir` 时在 boot 构建(否则在工作区首次 pre-step 构建),并按 TTL 刷新(`catalogTtlMs`,默认 60 秒)——刷新间隔之间热路径只是时间戳比较 + Map 命中,会话中 plan/compass/residual 的变化在一个间隔内落地。
|
|
141
222
|
|
|
223
|
+
## Agent-flow ledger(workflow 行)
|
|
224
|
+
|
|
225
|
+
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 原子写入)。
|
|
226
|
+
|
|
227
|
+
| `tool-workflow/*` 事件 | 账本行 | 字段 |
|
|
228
|
+
| --- | --- | --- |
|
|
229
|
+
| `run-start` | `workflow-run` | `runId`、`name`、`agent?`(承载的父会话 id) |
|
|
230
|
+
| `agent-start` | `workflow-agent` | `runId`、`seq`(1 起始的成员序号)、`label`、`phase?`、`childId` |
|
|
231
|
+
| `run-end` | `workflow-run-end` | `runId`、`stopReason`(`completed` / `cancelled` / `error`) |
|
|
232
|
+
|
|
233
|
+
`tool-workflow/agent-end` 是上游成员簿记,**没有账本类型**(成员 `outcome` 有意不持久化),被过滤掉。可选字段(`agent` / `phase`)缺席时从序列化行省略(lossless-JSON 纪律);三种类型共用账本的 `AGENT_FLOW_MAX_EVENTS` 截断 + 大小门禁,畸形行读取时收敛为 `undefined`(绝不重序列化)。展示字段(`name` / `label` / `phase`)在边界确定性限长(`WORKFLOW_LEDGER_MAX_NAME_LENGTH` 1024、`WORKFLOW_LEDGER_MAX_LABEL_LENGTH` 512——超长值以 `…` 标记截断);id 尺寸字段(`runId` / `childId`,上限 512)超长时**整行跳过**——绝不截断成碰撞。
|
|
234
|
+
|
|
235
|
+
**第四种类型 `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`)。
|
|
236
|
+
|
|
237
|
+
**去重与回放范围。** 持久化水位线即去重机制:**冷热重叠**以及**插件重应用/重启**(重注册读取持久化水位线而非从空游标开始)下每个 `(runId, kind, seq)` 只产一行。**apply 之后创建**、带构造期种子日志(恢复/分叉会话——其种子从不进 firehose)的会话会在上游 `session/created` 事件上**冷扫描一次**,水位线同样保证该回填幂等。水位线 sidecar 有界(每 harness 会话数上限,驱逐优先已不在线的会话)且完全受控:水位线不可读/不可写时降级为仅内存并告警一次——重启后会重录(诚实的去重欠录,绝不丢数据、绝不阻塞)。
|
|
238
|
+
|
|
239
|
+
**childId 关联 + 成员计数。** `workflow-agent` 行保留已发布成员的 `childId`(子会话 id);运行的展示 `name` 只存在于 `workflow-run` 行,面板为 agent/end 行经窗口查找解析(同一 `runId`——成员行本身不带名称)。面板把成员 COUNT 挂到 `workflow-run` 行(窗口内该 `runId` 的 `workflow-agent` 行数;窗口有界——被 ≤50 事件窗口截掉的成员如实缺席,绝不猜 0)。
|
|
240
|
+
|
|
241
|
+
**深度咨询(观察时)。** `agent-start` 时,消费者经 `sessions.get(childId)` 解析子会话,当其 `header.delegationDepth` ≥ 2 时告警——每个运行**至多一次**(per-runId 闩锁),日志器 `mstar/workflow-ledger`。仅观察时,**绝不是拒绝通道**:子会话读取抛出只降级咨询本身,绝不影响行或运行。
|
|
242
|
+
|
|
243
|
+
**零行为变更保证。** 消费者仅观察:**零门禁**——每次读取与追加都 try/catch 包裹;账本写入失败绝不崩溃或改变 workflow 运行;会话读取抛出只记一条 warn 并继续。`sessions` 服务经 `ctx.get('sessions')` **结构化**读取——对 `@deepseek-ai/dsh-session` 无运行时依赖。
|
|
244
|
+
|
|
245
|
+
**挂载顺序说明。** 消费者只在 apply 时 `sessions` 服务可用时激活——**dsh-session 行必须先于 mstar 行挂载**(标准 `web` profile 顺序即如此)。dsh-session 后于插件挂载(或缺失)的组合**静默**降级:一条 debug 日志(`sessions service absent — workflow-ledger consumer disabled`)且不记录任何行——绝不是错误,绝不是运行损坏。
|
|
246
|
+
|
|
247
|
+
**面板可见性。** 三种 workflow 行经现有事件行样式(无重设计)渲染在 **事件记录(Event Log)tab** 的 Agent 流转事件分区:摘要身份是运行 NAME(agent/end 行经窗口查找解析;回退 runId → 「未知」),详情体新增四个 workflow 字段——run-id / name / members / stop-reason(缺失 → 「—」)——expected/settled 席位渲染「—」(workflow 行不是角色派发——不存在 settle 配对,与 settle 行同先例)。未知 kind 字符串渲染为**通用行**(kind 原文、无 workflow 字段)——绝不丢弃、绝不猜测。catalog 摘要把 workflow 行计为**独立** `workflow` 桶(模型行 `by role: workflow N`)——绝不并入派发角色计数。
|
|
248
|
+
|
|
142
249
|
## Web 客户端插件(工作流面板)
|
|
143
250
|
|
|
144
251
|
本包为 dsh **web** profile 提供浏览器客户端半体,在**已安装的 `mstar` bundle 行**上被自动发现(package.json 的 `dsh.client` 声明 + `exports["./client"]` → `dist/client.js`——上游 web `dsh.client` 发现逻辑扫描 loader entries,并把每个客户端的 `exports["./client"]` 解析进 boot 图)——**无需独立 profile 层或安装步骤**(spec §6.1)。web 应用在 `/plugins/@mstar-harness/dsh/client.js` 提供该 bundle,并经 closure-factory loader 握手加载(`window.__ModuleLoader__.load({ id, factory })`)。
|
|
@@ -165,7 +272,7 @@ dsh web # 启动 → 服务 /plugins/@mstar-harness/dsh/clie
|
|
|
165
272
|
|
|
166
273
|
## Development
|
|
167
274
|
|
|
168
|
-
命令(在 `packages/dsh` 下执行):覆盖率门禁为 `src/` 逐文件 100%(dsh 测试策略);构建命令把 src 条目 bun 打包进 `dist/`(内联 engine 与 schemastery;`@deepseek-ai/cordis` 与运行时 seam 导入——`@deepseek-ai/dsh-skill-filesystem`、`@deepseek-ai/dsh-tools`(`defineTool`)、`@deepseek-ai/dsh-llm
|
|
275
|
+
命令(在 `packages/dsh` 下执行):覆盖率门禁为 `src/` 逐文件 100%(dsh 测试策略);构建命令把 src 条目 bun 打包进 `dist/`(内联 engine 与 schemastery;`@deepseek-ai/cordis` 与运行时 seam 导入——`@deepseek-ai/dsh-skill-filesystem`、`@deepseek-ai/dsh-tools`(`defineTool`)、`@deepseek-ai/dsh-llm`——保持外部,外加仅类型导入的 `dsh-llm-fallbacks`),运行 `build-client`(`scripts/build-client-bundle.ts`——按 spec §6.2 产出的 closure-factory CJS 浏览器 bundle `dist/client.js`)并输出 tsc 声明。
|
|
169
276
|
|
|
170
277
|
```sh
|
|
171
278
|
bun test --coverage
|
|
@@ -216,6 +323,14 @@ catalog 行在委托之后追加到组合步骤消息的**末尾**——请求
|
|
|
216
323
|
- **profile-bundle 安装到 `web` profile:registry 与 local checkout**——`dsh plugin --profile web add @mstar-harness/dsh`(registry)与 `add <本地检出>` 为受支持途径,均走同一 pnpm + reconcile 机制(reconcile 步骤把 `@mstar-harness/dsh` 并入 `dsh.profile.bundles`)。local checkout 需要先执行 `bun run build`——本包没有 `prepare` 脚本(monorepo 显式构建各包),未构建的检出会装入空的 `dist/`。
|
|
217
324
|
- **`lintSkillWrite` 类型化否决尚未接入生产**——传入文档分支的 hard 否决(`SkillLintVetoError`,码 `skill-lint.veto`)已导出并测试覆盖,但尚无生产调用方:engine `HostAdapter` 没有携带内容的技能写入钩子(只有 `beforeStatusWrite`/`beforeDispatch`/`beforeMerge`),且 fs intent 槽位内容盲。接线随未来携带内容的钩子落地;在此之前监听器路径只通过修复逃生咨询执行(从不否决)。
|
|
218
325
|
- **CLI `HOST_SIGNALS` 缺少 `subagent` token**——engine `ToolSignal` 联合已包含它且 `detectHost` 能处理,但 `packages/cli` 的 `HOST_SIGNALS` 尚未更新,`mstar host detect --signals subagent` 会拒绝,直到上游化时更新 CLI 列表。
|
|
219
|
-
- **入口是 `src/gates/*` 之上的模块索引**——拆分已交付:`src/index.ts`(371 行)从各 gate 模块(`_shared` / `status` / `skill-lint` / `seams` / `dispatch` / `catalog` / `tools` / `adapter`)原样 re-export 冻结的
|
|
326
|
+
- **入口是 `src/gates/*` 之上的模块索引**——拆分已交付:`src/index.ts`(371 行)从各 gate 模块(`_shared` / `status` / `skill-lint` / `seams` / `dispatch` / `catalog` / `tools` / `adapter`)原样 re-export 冻结的 47 名导出面(28 值导出 + 19 type-only 名;`Config` 计一次),并保留插件 manifest、单一 cordis augmentation 点、命令注册与 `apply()` 启动接线。导出面由 `tests/export-surface.spec.ts` 冻结——运行时值导出集 + `typecheck:tests`(`bunx tsc --noEmit -p tests/tsconfig.json`)下的值命名空间恒等与逐名类型探测。
|
|
220
327
|
- **engine dsh 行待上游化**——engine `host.ts` 的 dsh 改动(`DetectResult`、`ToolSignal`、`resolveSkillRoot`)位于 mstar-workflow engine 镜像,计划经用户授权的上游 PR 合入 mstar-harness;`mstar-host` 技能镜像(§ Detect / § Resolve loaded skill root / `references/dsh.md`)随之一并更新。
|
|
221
328
|
- **迭代 stepper:Step 1 为 compass 驱动,Step 5 为 schema 驱动**——zone dashboard 的 Step 1(iteration-start)在 steering compass `status: active`(Phase 1 进行中)时为当前步(无 gate 判定 → 无 PASS/FAIL 徽标);Step 5(merge-ready)是 engine 闸门永不点亮为当前的 schema 常量(transition 只覆盖 Phase 2→3→4,merge-ready 从不是 gate transition);仅当 Step 4 为当前步时作为 `next` 渲染,其余为 idle——已记录于迭代 guide,非缺陷。完整面板限制清单见 Web 客户端插件一节。
|
|
329
|
+
- **`dsh-llm-fallbacks` 为 registry `dependencies` 条目且仅类型导入**——声明 `^0.2.0`(caret 范围允许 0.2.1+;探测形状断言测试是可执行的漂移闸门)并在构建中 `--external`,因此 `dist/` 无打包运行时导入(仅 3 处命名该包的字符串字面量——探测的 loader 条目匹配与两条装饰日志;建议日志写作 `fallbacks`);激活是**单独显式安装**(双命令契约),绝不传递。库形态依赖存在是为未来值导入无需改 manifest 即可解析;`--external` 仍是护栏。
|
|
330
|
+
- **本批次未交付角色→模型覆盖**——把角色路由到 fallbacks `model`(或经 fallbacks 规则路由 persona)需要改写启动请求上的子会话 `agentOptions`,但启动请求选项由调用方控制(tool-subagent 自己的 Config;调用参数仅为 `description`/`prompt`/`run_in_background`,且深度冻结)。等待上游 `fallbacks-explicit-role-tool` 或 N-B1 systemPrompt 采纳(roadmap §10.4)。
|
|
331
|
+
- **装饰是最小化的每子会话段,而非 N-B1 systemPrompt 采纳**——`mstar:role-persona` 是子上下文上的一个 agent 作用域段;无 harness 规则段、无 PromptContext、无变量。N-B1(roadmap §10.4)日后可吸收或替换该通道而不改变可观察行为(AC-3)。
|
|
332
|
+
- **persona 注入与 fallbacks 无关**——`dsh-llm-fallbacks` 只路由 LLM 失败;装饰从不依赖它。未挂载 → 同一 persona 经同一通道来自 mstar Config,仅多一条 debug 日志(AC-4)。若组合中缺 `ctx.get('agents')`(无 dsh-agent),装饰以一条 debug 日志跳过。
|
|
333
|
+
- **fork 门禁仅默认开启;显式 `dispatchTools` 可省略 `subagent_fork`**——自定义 `dispatchTools` 列表整体覆盖默认(既有重命名模式),因此自行声明列表的部署须包含 `subagent_fork` 才能继续门禁 fork 派发。
|
|
334
|
+
- **persona 值绝不能包含 `{{...}}`**——dsh system-prompt 以严格 `{{variable}}` 插值渲染 persona 文本,对与后文 `}}` 配对的 `{{`(未知/畸形/未定义引用)直接抛错,会破坏每一次角色匹配派发的子会话提示组装。Config schema 在插件挂载时以清晰报错拒绝此类 `rolePersonas` 值;转义规则是改用单花括号或改写措辞(不带后续 `}}` 的孤立 `{{` 按字面散文渲染)。
|
|
335
|
+
- **fallbacks HMR 重挂后的 seeds 再收敛受 seeded-only preservation 设计边界限制**——seed registry 是每次 apply 的内存态,纤程切换(HMR / 设置编辑)会丢弃它并让双方 declarer 从头重放。mstar re-declare 只合并保留**已 seeded** 的 id(seeded-only,设计使然),因此在 preset-last 提交顺序下,preset 行的 seeded 注记不会被 re-declare 恢复——它们在下一次 fallbacks apply 时恢复(上游 preset 自声明重新播种)。`llm-fallbacks` 服务消失时 advisory 一次性 latch 会重新武装(inject teardown),因此下一个决策点会重新收敛 mstar 侧;preset 侧是 seeded-only preservation 设计的有文档说明的暂时现象。
|
|
336
|
+
- **咨询跳过原因 `no-persona` 亦涵盖 extraction 失败**——在 extraction 期被拒绝的镜像默认(例如含 `{{...}}` 插值风险)会在合并的 declare-outcome 行中以 `no-persona` 呈现(extraction 在风险门之前就返回无可用的 persona);逐 id 的 extraction 诊断保留在 debug 通道(`mstar/fallbacks-advisory` / `mstar/fallbacks-seeds`)。
|
package/bundle/README.md
CHANGED
|
@@ -37,6 +37,16 @@ pnpm must be on PATH. The package has no `prepare` script (the monorepo
|
|
|
37
37
|
builds packages explicitly, matching cli/opencode), so a local checkout must
|
|
38
38
|
be built before `add .`.
|
|
39
39
|
|
|
40
|
+
The optional `dsh-llm-fallbacks` capability plugin is a SECOND install
|
|
41
|
+
(`dsh plugin --profile web add dsh-llm-fallbacks`) — the two-command install
|
|
42
|
+
is the contract. This bundle's patch will NEVER fold a fallbacks row in: the
|
|
43
|
+
loader has no insert-if-absent semantics, so a same-`id` insert is a
|
|
44
|
+
`duplicate loader entry id` boot failure and a different-`id` insert mounts
|
|
45
|
+
the plugin twice (two `apply()` runs, split fallback state) for anyone who
|
|
46
|
+
also installs the package directly (roadmap §8.3 F4). Reconcile append order
|
|
47
|
+
places `dsh-llm-fallbacks` after `dsh-base`/`llm-retry` (its hard ordering
|
|
48
|
+
requirement) and after this bundle's `mstar` row.
|
|
49
|
+
|
|
40
50
|
Bundle resolution is two-anchored: a bundle name resolves from the dsh
|
|
41
51
|
installation first, then from the profile directory. During local
|
|
42
52
|
development `@mstar-harness/dsh` is not installed into the dsh installation,
|
|
@@ -68,7 +78,7 @@ The `mstar` row accepts the plugin `Config` (see `src/index.ts`):
|
|
|
68
78
|
|---|---|---|
|
|
69
79
|
| `harnessDir` | unset (resolved per session workspace) | explicit `{HARNESS_DIR}` root — **required for repos whose harness root is not a probed name** (`.mstar/` → `.agents/` → `.plans/` → `plans/`): e.g. the mstar-workflow maintenance repo itself uses `.harness/` (deliberately not probed), so set `harnessDir: <repo>/.harness` in the profile layer. Without the config the probe starts from the SESSION workspace root (the session cwd — **never the process/launch cwd**) and **stops there** — it never walks above the session workspace, so a global `~/.mstar` is never adopted |
|
|
70
80
|
| `enforcement` | **unset — default OFF** | `hard` / `soft` override; absent → the iteration compass decides, warn-only when no compass hardens (never a global always-on hard gate) |
|
|
71
|
-
| `dispatchTools` | unset (plugin default `['subagent']`) | delegation tool names the dispatch gate matches |
|
|
81
|
+
| `dispatchTools` | unset (plugin default `['subagent', 'subagent_fork']`) | delegation tool names the dispatch gate matches — the dsh preset's TWO delegation tools (`subagent` + its fork sibling `subagent_fork`, both Assignment-shaped); a custom list overrides the default wholesale, so it must include `subagent_fork` to keep fork dispatches gated |
|
|
72
82
|
| `dispatchBinding` | unset | the dispatching agent's role for the anti-recursion precheck |
|
|
73
83
|
| `skillRoots` | unset | additional skill roots (custom mirrors) |
|
|
74
84
|
| `bundledSkillDir` | unset → plugin resolves its OWN packaged `harness-skills/` mirror package-relative | bundled skill mount — the repo-root `skills/` mirror synced by `bundle-assets` at build/postinstall (gitignored), resolved package-relative (NOT cwd-anchored). An explicit value wins; a RELATIVE override stays cwd-anchored, so pass an absolute path in the profile layer |
|
|
@@ -17,19 +17,24 @@
|
|
|
17
17
|
* re-implemented). Off-pipeline (unexpected) DISPATCHES fold in via
|
|
18
18
|
* `expected: false`: `view.unexpected` is a RE-LIST of rows already inside
|
|
19
19
|
* `view.events`, so reading it here would double-append — the page decides
|
|
20
|
-
* how to section/badge them.
|
|
20
|
+
* how to section/badge them. Workflow rows (plan `20260815-dsh-workflow-ledger`
|
|
21
|
+
* Task 4) carry the run identity (`runId` / `name` — agent/end rows resolve
|
|
22
|
+
* the run's name from the window's workflow-run row); unknown kinds pass
|
|
23
|
+
* through as generic rows (`name` '' — never fabricated).
|
|
21
24
|
* - `violation` rows — `view.violations` (gate violations, str()-guarded)
|
|
22
25
|
* after the event rows; the page renders them in their own 违规记录
|
|
23
26
|
* partition.
|
|
24
27
|
*/
|
|
25
28
|
import type { FlowEventStatus, FlowEventView, ZoneView } from './project-graph.ts';
|
|
29
|
+
/** The three workflow ledger kinds (plan `20260815-dsh-workflow-ledger` W-B2). */
|
|
30
|
+
export declare function isWorkflowKind(kind: FlowEventView['kind']): boolean;
|
|
26
31
|
/** One agent-flow event row: the log's 流转事件 partition (spec §5). */
|
|
27
32
|
export interface EventLogEventEntry {
|
|
28
33
|
/** Entry discriminator — this row is an agent-flow event. */
|
|
29
34
|
kind: 'event';
|
|
30
35
|
/** Stable per-projection id (reuses the projected event's window id — React key). */
|
|
31
36
|
id: string;
|
|
32
|
-
/**
|
|
37
|
+
/** The ledger kind verbatim — the page distinguishes settle rows (✓ glyph, no role) and workflow rows (run name). */
|
|
33
38
|
eventKind: FlowEventView['kind'];
|
|
34
39
|
/** `Execute as`; '' for settle rows and missing roles (never fabricated). */
|
|
35
40
|
role: string;
|
|
@@ -41,7 +46,7 @@ export interface EventLogEventEntry {
|
|
|
41
46
|
task: string;
|
|
42
47
|
/** Event timestamp; 0 when missing (the render shows「—」— never a guessed time). */
|
|
43
48
|
ts: number;
|
|
44
|
-
/** Dispatch → dispatched|advisory|denied; settle → ok|error|denied (token-colored). */
|
|
49
|
+
/** Dispatch → dispatched|advisory|denied; settle → ok|error|denied; workflow/unknown → unknown (token-colored). */
|
|
45
50
|
status: FlowEventStatus;
|
|
46
51
|
/** Dispatch with an exact-identity-paired settle (an unpaired settle stays unpaired — honest); settle rows are never "settled". */
|
|
47
52
|
settled: boolean;
|
|
@@ -49,6 +54,16 @@ export interface EventLogEventEntry {
|
|
|
49
54
|
durationMs: number | null;
|
|
50
55
|
/** role ∈ EXPECTED_ROLE_FLOW union — false = off-pipeline (unexpected) dispatch. */
|
|
51
56
|
expected: boolean;
|
|
57
|
+
/** Workflow run id (workflow-* + unknown rows, when carried); '' otherwise. */
|
|
58
|
+
runId: string;
|
|
59
|
+
/**
|
|
60
|
+
* The workflow run's display name (plan `20260815-dsh-workflow-ledger`
|
|
61
|
+
* Task 4): the workflow-run row carries its own name; workflow-agent /
|
|
62
|
+
* workflow-run-end rows resolve the run's name from the window's
|
|
63
|
+
* workflow-run row (same runId — honest, the ledger row itself carries no
|
|
64
|
+
* name); '' when not a workflow row or the run row is truncated out.
|
|
65
|
+
*/
|
|
66
|
+
name: string;
|
|
52
67
|
}
|
|
53
68
|
/** One gate violation row: the log's 违规记录 partition (spec §5). */
|
|
54
69
|
export interface EventLogViolationEntry {
|