@mstar-harness/dsh 3.7.0 → 3.7.1

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 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: a791ed87500a5164387bbb72277bb1d09406937b
6
- README.zh.md: 9c9cc7399c34045c52ab72167e4a40779c9f917e
5
+ README.md: 189bec515fe5fce73de5afad7e2d1678e0f55ea5
6
+ README.zh.md: 05affb493e99da9b45d0b8dd2743ccedaecc0b11
package/README.md CHANGED
@@ -69,7 +69,7 @@ The shipped headless template auto-initializes on first use (`@deepseek-ai/dsh-b
69
69
  | `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). |
70
70
  | `dispatchBinding` | `string` | unset → fail-closed `empty-binding` under hard | The dispatching agent's own harness role (the anti-recursion CALLER); an Assignment whose `Execute as` equals it is self-recursion. |
71
71
  | `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 persona channel (see LLM fallbacks integration). |
72
- | `rolePersonas` | `Record<string, string>` | unset (bundled mirror default) | mstar role id (`Execute as`) → persona text; the native subagent persona channel's **override** source — a role-matched start (one-shot `start` or the opt-in continuable `startContinuable`) merges the persona into the native request `persona` slot (the child embodies the role persona INSTEAD OF the deployment persona; persisted + reapplied on resume); when unset for a role, the bundled `harness-agents/` mirror default is used (see LLM fallbacks integration). |
72
+ | `rolePersonas` | `Record<string, string>` | unset (bundled mirror default) | mstar role id (`Execute as`) → persona text; the native subagent persona channel's **override** source — a role-matched start (one-shot `start` or the opt-in continuable `startContinuable`) merges the persona into the native request `persona` slot (the child embodies the role persona INSTEAD OF the deployment persona; persisted + reapplied on resume). Merge order: the request's own `persona` wins AS-IS (a caller-set persona is never overridden — no role merge); otherwise a non-empty entry beats the bundled `harness-agents/` mirror default, an **empty-string** entry is treated as unset and falls through to the mirror default, and an absent entry uses the mirror default (see LLM fallbacks integration). |
73
73
  | `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. |
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-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. |
@@ -169,9 +169,11 @@ Persona delivery rides dsh's NATIVE `SubagentStartRequest.persona` slot (`@deeps
169
169
 
170
170
  **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.
171
171
 
172
+ **Seam probe (fail-loud)**: the channel probes the cordis `internal/get` seam once per apply (temporary canary listener + one controlled proxied `ctx.subagents` read — never `ctx.get`); a missing or unrecognized seam logs ONE warn — `role persona channel not installed — cordis 'internal/get' seam missing or unrecognized (rolePersonas will not be merged into subagent starts)` + ` (reason: seam-absent | wrap-skipped)` — instead of failing silently. A healthy boot produces NO warn (the locked outcome table): on today's composition the apply ctx does not resolve `subagents`, so each apply emits one no-warn `service-absent` debug — cordis resolves the service in dispatch scopes, where reads are intercepted per read; probe-internal errors degrade fail-open (one debug). `wrap-skipped` is pinned but not reachable from the shipped apply wiring — it fires where `subagents` is resolvable at apply, a path covered by the harness pins. The seam name is pinned by the dedicated probe family `tests/persona-seam-probe.spec.ts` (via the exported `PERSONA_SEAM_EVENT` constant); the installed-artifact assertion arms once the release carrying the probe publishes (surface-vintage guard in `tests/install-e2e.spec.ts`).
173
+
172
174
  ### Role seeds + adoption advisory
173
175
 
174
- 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.
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.**
175
177
 
176
178
  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**:
177
179
 
package/README.zh.md CHANGED
@@ -68,7 +68,7 @@ dsh plugin --profile headless add @mstar-harness/dsh
68
68
  | `dispatchTools` | `string[]` | `['subagent', 'subagent_fork']` | 派发闸门匹配的委派工具名——dsh preset 的**两个**委派工具:`subagent` 及其 fork 兄弟 `subagent_fork`(两者都携带 Assignment 形态的 `{ description, prompt }` 参数;`toolName` 配置可重命名实例)。 |
69
69
  | `dispatchBinding` | `string` | 未设置 → hard 下 fail-closed `empty-binding` | 派发方 agent 自身的 harness 角色(反递归 caller);Assignment 的 `Execute as` 等于它即自我递归。 |
70
70
  | `roleMap` | `Record<string, string>` | 未设置 | mstar 角色 id(`Execute as`)→ dsh-llm-fallbacks 角色 id。**仅**作日志与未来规则驱动互操作的分类桥——persona 通道从不读取它(见 LLM fallbacks integration)。 |
71
- | `rolePersonas` | `Record<string, string>` | 未设置(打包镜像默认) | mstar 角色 id(`Execute as`)→ persona 文本;原生 subagent persona 通道的**覆盖**来源——角色匹配的 start(一次性 `start` 或可选的 continuable `startContinuable`)会把 persona 合入原生请求的 `persona` 槽(子会话体现角色 persona 而**非**部署 persona;持久化并在 resume 时重放);某角色未设置时使用打包的 `harness-agents/` 镜像默认值(见 LLM fallbacks integration)。 |
71
+ | `rolePersonas` | `Record<string, string>` | 未设置(打包镜像默认) | mstar 角色 id(`Execute as`)→ persona 文本;原生 subagent persona 通道的**覆盖**来源——角色匹配的 start(一次性 `start` 或可选的 continuable `startContinuable`)会把 persona 合入原生请求的 `persona` 槽(子会话体现角色 persona 而**非**部署 persona;持久化并在 resume 时重放)。合并次序:请求自身已携带 `persona` 时**原样生效**(调用方意图绝不被覆盖——不做角色合并);否则非空条目优先于打包的 `harness-agents/` 镜像默认值,**空字符串**条目视为未设置并回落到镜像默认值,无条目时使用镜像默认值(见 LLM fallbacks integration)。 |
72
72
  | `skillRoots` | `string[]` | 未设置(不注册自定义根) | 向 dsh skill-filesystem 提供者注册的额外技能根(`customSkillDirs` 语义——先于用户根扫描)。开发期:镜像 `<repo-root>/skills` 的绝对路径。 |
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` 行(水印 + 迭代闸门 + 工作区摘要)多久重读一次 `status.json` / compass / 知识索引。刷新间隔之间热路径只是时间戳比较 + Map 命中;会话中 plan/compass/residual 的变化会在一个间隔内落地。 |
@@ -168,9 +168,11 @@ persona 交付走 dsh 原生的 `SubagentStartRequest.persona` 槽(`@deepseek-
168
168
 
169
169
  **零配置默认值**:当 `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。
170
170
 
171
+ **Seam 探针(fail-loud)**:通道每次 apply 对 cordis `internal/get` seam 探测一次(临时 canary 监听器 + 一次受控代理式 `ctx.subagents` 读取——绝不 `ctx.get`);seam 缺失或未被识别时记录恰一条 warn——`role persona channel not installed — cordis 'internal/get' seam missing or unrecognized (rolePersonas will not be merged into subagent starts)` + ` (reason: seam-absent | wrap-skipped)`——而非静默失败。健康 boot 不产生任何 warn(锁定的判定表):在今天的组合形态下 apply ctx 不解析 `subagents`,因此每次 apply 发出一条无 warn 的 `service-absent` debug——cordis 在 dispatch 作用域解析该服务,读取在那时逐次被拦截;探针内部错误按 fail-open 降级(一条 debug)。`wrap-skipped` 已被钉死但无法从当前发布的 apply 布线触达——它只在 apply 时即可解析 `subagents` 的场景触发,该路径由 harness pins 覆盖。seam 名由专用探针家族 `tests/persona-seam-probe.spec.ts` 钉死(经导出的 `PERSONA_SEAM_EVENT` 常量);安装产物断言在携带探针的版本发布后自动启用(`tests/install-e2e.spec.ts` 的 surface-vintage guard)。
172
+
171
173
  ### 角色 seeds 与采纳建议(Adoption advisory)
172
174
 
173
- 当可选的 `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。
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`。**
174
176
 
175
177
  一条只告警的采纳建议通道(日志器 `mstar/fallbacks-advisory`)**每次 apply 只跑一遍**——apply 时先尝试一次;当 fallbacks 行在 `dsh` 之后挂载(loader 并发挂载条目)时,改在首个 `subagent/start` 决策点只跑一遍。服务存在时,通道**先 await 幂等 re-declare**(闭合 boot 竞争窗口)再读取**有效状态**(`getEffectiveRoles`),并按**每类至多一条告警**有界报告:
176
178
 
@@ -79,13 +79,24 @@ export interface Config {
79
79
  roleMap?: Record<string, string>;
80
80
  /**
81
81
  * mstar role id → persona text — the native persona channel's only
82
- * payload source . A role-matched
83
- * one-shot start merges the persona into the request's native `persona`
84
- * slot — dsh composes it as the scoped shadowing `deployment:persona`
85
- * section on the child, persists it in the child descriptor, and reapplies
86
- * it on resume. Lookup is DIRECT — never gated on `roleMap` or on the
87
- * fallbacks mounted state (persona delivery is fallbacks-independent).
88
- * Absent → no merge.
82
+ * payload source. A role-matched start (one-shot `start` or the opt-in
83
+ * continuable `startContinuable`) merges the persona into the request's
84
+ * native `persona` slot — dsh composes it as the scoped shadowing
85
+ * `deployment:persona` section on the child, persists it in the child
86
+ * descriptor, and reapplies it on resume. Lookup is DIRECT — never gated
87
+ * on `roleMap` or on the fallbacks mounted state (persona delivery is
88
+ * fallbacks-independent).
89
+ *
90
+ * MERGE ORDER (doc-only statement of the decision chain in
91
+ * `role-persona.ts` / `agent-personas.ts` — no behavior change): the
92
+ * request's own `persona` wins AS-IS — a start that already carries one
93
+ * is returned untouched, with no role merge at all (caller intent is
94
+ * never overridden). Otherwise the per-role lookup is config over mirror:
95
+ * a non-empty `rolePersonas[roleId]` beats the bundled `harness-agents/`
96
+ * mirror default; an EMPTY-STRING value is treated as unset and falls
97
+ * through to the mirror default (parity with the pre-channel decoration's
98
+ * config check); an absent entry uses the mirror default; a lookup miss
99
+ * (no config value and no mirror default) → no merge.
89
100
  *
90
101
  * INTERPOLATION CONSTRAINT: dsh renders persona text with STRICT
91
102
  * `{{variable}}` interpolation (the native persona has the same template
@@ -27,12 +27,18 @@
27
27
  *
28
28
  * Unreadable row config (absent field / non-object, or an unreadable
29
29
  * `roles.list`) → skip + one debug log. Unmounted → the pass is not invoked
30
- * (returns `false`, no logs). The advisory NEVER writes the fallbacks config
31
- * — the read is read-only over the deployment's config layer (never the
32
- * fallbacks plugin's module internals); the only write path is the
33
- * idempotent seeds re-declare through the released seeds surface (no-delta
34
- * → no settings write upstream). The advisory never throws (the caller's
35
- * dispatch/apply flow is never affected).
30
+ * (`{ ran: false, converged: false }`, no logs). An aborted pass (any caught
31
+ * error, including a rejected re-declare) reports `{ ran: true, converged:
32
+ * false }` — the honest latch: the caller arms its one-shot latch only on
33
+ * `ran && converged`, so a failed pass never suppresses the decision-point
34
+ * retry. The degraded-abort warn is deduplicated to at most ONE per apply
35
+ * (module flag; the entry resets it via {@link resetAdvisoryAbortWarn}).
36
+ * The advisory NEVER writes the fallbacks config — the read is read-only
37
+ * over the deployment's config layer (never the fallbacks plugin's module
38
+ * internals); the only write path is the idempotent seeds re-declare
39
+ * through the released seeds surface (no-delta → no settings write
40
+ * upstream). The advisory never throws (the caller's dispatch/apply flow is
41
+ * never affected).
36
42
  *
37
43
  * Module boundary: no barrel — the entry imports this module by explicit
38
44
  * relative path (the role-persona module pattern).
@@ -59,18 +65,50 @@ export declare function setAdvisoryLogger(sink: AdvisoryLogSink): AdvisoryLogSin
59
65
  */
60
66
  export declare const ADVISORY_ID_LIST_CAP = 20;
61
67
  /**
62
- * One advisory pass: unmounted → not invoked (`false`, no logs); mounted →
63
- * report the taxonomy adoption state (bounded: ≤1 warn per category). With
64
- * the service present the pass is ASYNC: it awaits the idempotent re-declare
65
- * before the effective-state readback (report determinism — the boot
66
- * dual-inject-child race window is closed). Never throws — every failure
67
- * mode degrades to skip + one debug/warn. Never writes the fallbacks config.
68
+ * One advisory pass outcome (the honest-latch contract):
69
+ * - `ran` — the pass executed (the capability is mounted); `false` means the
70
+ * pass was not invoked (unmounted). Carries the one-pass-per-apply
71
+ * bookkeeping.
72
+ * - `converged` — the pass reached its converged end. Per return path:
73
+ * unmounted → `false`; aborted pass (any caught error, including a
74
+ * rejected re-declare) → `false`; a no-re-declare path (loader-fallback
75
+ * structural read, unreadable config, or a service-present pass that
76
+ * never reaches the re-declare) → `true`; service-present path → reflects
77
+ * the re-declare resolving.
78
+ *
79
+ * The caller (entry `apply`) arms its one-shot latch only on
80
+ * `ran && converged` — a rejected re-declare must never suppress the
81
+ * `subagent/start` decision-point retry.
82
+ */
83
+ export interface AdvisoryPassReport {
84
+ ran: boolean;
85
+ converged: boolean;
86
+ }
87
+ /**
88
+ * Reset the degraded-abort warn dedup flag (one warn per apply budget).
89
+ * Called by the entry at `apply` (next to the advisory sink binding) and in
90
+ * the `llm-fallbacks` inject child's teardown (a fiber swap re-opens the
91
+ * budget for the re-applied fiber). Exported for the suite's dedup case.
92
+ */
93
+ export declare function resetAdvisoryAbortWarn(): void;
94
+ /**
95
+ * One advisory pass: unmounted → not invoked (`{ ran: false, converged:
96
+ * false }`, no logs); mounted → report the taxonomy adoption state (bounded:
97
+ * ≤1 warn per category). With the service present the pass is ASYNC: it
98
+ * awaits the idempotent re-declare before the effective-state readback
99
+ * (report determinism — the boot dual-inject-child race window is closed).
100
+ * Never throws — every failure mode degrades to skip + one debug/warn, and
101
+ * an aborted pass reports `converged: false` (the honest latch). Never
102
+ * writes the fallbacks config.
68
103
  *
69
104
  * @param ctx - the plugin's registrant context (the app composition root).
70
105
  * @param agentsDir - the `harness-agents/` mirror root the mstar role-id set
71
106
  * is derived from; absent → the taxonomy checks are skipped (one debug
72
107
  * log; the legacy-keys check is mirror-independent and still runs).
73
- * @returns `true` when the pass ran (mounted), `false` when unmounted — the
74
- * caller (entry `apply`) uses the boolean for the one-pass-per-apply latch.
108
+ * @returns the {@link AdvisoryPassReport} — `ran` marks a mounted pass (the
109
+ * one-pass-per-apply bookkeeping), `converged` marks a pass that reached
110
+ * its converged end; the caller (entry `apply`) arms the one-shot latch
111
+ * only on `ran && converged`, so an aborted pass (e.g. a rejected
112
+ * re-declare) never suppresses the decision-point retry.
75
113
  */
76
- export declare function runFallbacksAdvisory(ctx: Context, agentsDir: string | undefined): Promise<boolean>;
114
+ export declare function runFallbacksAdvisory(ctx: Context, agentsDir: string | undefined): Promise<AdvisoryPassReport>;
@@ -43,7 +43,13 @@
43
43
  * `roleMap` is a taxonomy bridge for logging + future rule-driven interop
44
44
  * only. The mirror root is bound at apply (`setRolePersonaAgentsDir` ←
45
45
  * `packagedAgentsDir()`), package-relative so the shipped bundle works from
46
- * any launch cwd.
46
+ * any launch cwd. Lifetime: the root is a module-level binding with ONE
47
+ * writer — the per-apply `setRolePersonaAgentsDir` call — and it is read
48
+ * per start, so every start observes an apply-constant value; re-calling
49
+ * the setter (an HMR re-apply) IS the re-bind, and that re-bind is the
50
+ * intended reset (it also re-arms the mirror-absent latch below). The
51
+ * per-apply payload `Config.rolePersonas` is the contrast: closed over per
52
+ * apply in `registerRolePersonaChannel`.
47
53
  *
48
54
  * Capability gates (native fail-loud contracts, per surface): one-shot
49
55
  * `SubagentRuntime.start` REJECTS a request carrying `persona` for a
@@ -68,6 +74,22 @@
68
74
  * per apply (S-002 latch); a throwing merge aborts the merge only — the
69
75
  * ORIGINAL request reaches the service and the start is never affected.
70
76
  *
77
+ * Seam probe (apply-time, observation only): a future cordis rename/removal
78
+ * of `internal/get` would stop the listener from ever firing — persona
79
+ * delivery would silently degrade to the raw service with no runtime
80
+ * signal. {@link probeRolePersonaSeam} runs ONCE per apply right after
81
+ * {@link registerRolePersonaChannel}: a temporary canary listener + ONE
82
+ * controlled proxied read (`ctx.subagents` — NEVER `ctx.get`, whose accessor
83
+ * bypasses the waterfall and would false-warn every healthy boot) assert
84
+ * that the seam dispatched AND the returned value carries the wrapper brand.
85
+ * A broken seam warns ONCE per apply (fail-loud); an unresolved service is
86
+ * `service-absent` (`ok: true` + one debug — the apply ctx does not resolve
87
+ * `subagents`; cordis resolves the service in dispatch scopes, where reads
88
+ * are intercepted per read); any probe-internal error fails
89
+ * OPEN (`ok` + one debug) — the probe never throws and never blocks a
90
+ * dispatch. The decision core is the pure {@link evaluateSeamProbe}, so the
91
+ * whole outcome table is unit-pinnable without cordis internals.
92
+ *
71
93
  * Persona text is rendered by dsh system-prompt's STRICT `{{...}}`
72
94
  * interpolation (the native persona has the same template semantics as the
73
95
  * deployment persona), so persona values MUST NOT contain `{{` paired with
@@ -77,7 +99,12 @@
77
99
  * boot throw).
78
100
  *
79
101
  * Module boundary: no barrel — the entry imports this module by explicit
80
- * relative path and re-exports the public names verbatim. No dsh-subagent
102
+ * relative path and re-exports the public names verbatim, EXCEPT the four
103
+ * probe exports (`PERSONA_SEAM_EVENT`, `ROLE_PERSONA_WRAPPER_BRAND`,
104
+ * `evaluateSeamProbe`, `probeRolePersonaSeam`), which are deliberately NOT
105
+ * re-exported from the entry: the frozen entry surface keeps the probe
106
+ * observable only through this module (tests import it directly; the
107
+ * shipped bundle exports no probe symbol). No dsh-subagent
81
108
  * dependency: the runtime surface is consumed structurally (same pattern as
82
109
  * the probe's `LoaderEntryView` and T2's `fallbacks-structural.ts`).
83
110
  */
@@ -85,6 +112,23 @@ import type { Context } from '@deepseek-ai/cordis';
85
112
  import type { Config } from './_shared.ts';
86
113
  /** Logger label for the role-persona channel (dsh logger naming: `<scope>/<subject>`). */
87
114
  export declare const ROLE_PERSONA_LOGGER = "mstar/role-persona";
115
+ /**
116
+ * The interception seam: the cordis service-read waterfall event the channel
117
+ * listener registers on. The registration and the apply-time seam probe both
118
+ * reference THIS constant — a future cordis rename of `internal/get` becomes
119
+ * a one-line, probe-family-caught edit here instead of a silent delivery
120
+ * stop (the probe family pins the literal; see `probeRolePersonaSeam`).
121
+ */
122
+ export declare const PERSONA_SEAM_EVENT = "internal/get";
123
+ /**
124
+ * Wrapper brand — the non-enumerable symbol own property every persona
125
+ * wrapper carries (`wrapSubagentsService` stamps it at creation). The apply-
126
+ * time seam probe reads it to assert the wrapper was actually installed on
127
+ * the controlled read. Non-enumerable + symbol keeps the wrapper's
128
+ * key/spread/JSON surface identical to the wrapped service's (behavior-
129
+ * neutral by construction).
130
+ */
131
+ export declare const ROLE_PERSONA_WRAPPER_BRAND: symbol;
88
132
  /** One consumed prompt content block (`@deepseek-ai/dsh-llm` `ContentBlock` text members). */
89
133
  interface PromptBlockView {
90
134
  readonly type: string;
@@ -166,8 +210,14 @@ export type RolePersonaLogSink = (level: RolePersonaLogLevel, message: string) =
166
210
  */
167
211
  export declare function setRolePersonaLogger(sink: RolePersonaLogSink): RolePersonaLogSink;
168
212
  /**
169
- * Bind the persona-defaults mirror root. Returns the PRIOR binding so a
170
- * caller can restore it (test pattern: {@link setRolePersonaLogger}).
213
+ * Bind the persona-defaults mirror root — the module sink's only writer,
214
+ * invoked once per apply from the entry with `packagedAgentsDir()`, so an
215
+ * HMR re-apply re-binds the root instead of inheriting the previous
216
+ * apply's binding. That re-bind is the intended reset: beyond swapping the
217
+ * root it re-arms the S-002 mirror-absent latch (`mirrorAbsentDebugged`),
218
+ * keeping the "no mirror" debug at most once per apply, and the returned
219
+ * PRIOR binding lets a caller restore the previous root (test pattern:
220
+ * {@link setRolePersonaLogger}).
171
221
  * @param dir - the mirror root, or `undefined` to disable mirror defaults.
172
222
  */
173
223
  export declare function setRolePersonaAgentsDir(dir: string | undefined): string | undefined;
@@ -187,4 +237,71 @@ export declare function setRolePersonaAgentsDir(dir: string | undefined): string
187
237
  * only payload source; `roleMap` is never consulted for the merge).
188
238
  */
189
239
  export declare function registerRolePersonaChannel(ctx: Context, config: Config): void;
240
+ /** Outcome of one apply-time seam probe ({@link probeRolePersonaSeam}). */
241
+ export interface PersonaSeamProbeResult {
242
+ /** `true` = the channel is healthy OR the probe failed open (never block apply). */
243
+ ok: boolean;
244
+ /**
245
+ * Why the probe classified the channel the way it did. `ok: false` ALWAYS
246
+ * carries a reason; `service-absent` may appear with `ok: true` (the
247
+ * sanctioned no-warn unresolved-service classification).
248
+ */
249
+ reason?: 'seam-absent' | 'wrap-skipped' | 'service-absent';
250
+ }
251
+ /** One probe observation — the inputs of the pure decision core. */
252
+ export interface SeamProbeInputs {
253
+ /** Whether the temporary canary listener fired during the controlled read. */
254
+ dispatched: boolean;
255
+ /** The value the controlled read threw (`undefined` = the read resolved). */
256
+ readError: unknown;
257
+ /** The controlled read's resolved value (`undefined` when the read threw). */
258
+ value: unknown;
259
+ }
260
+ /**
261
+ * Pure decision core of the seam probe — the ENTIRE outcome table, unit-
262
+ * pinnable without cordis internals:
263
+ *
264
+ * - canary silent → `{ ok: false, reason: 'seam-absent' }` — cordis no
265
+ * longer dispatches the seam; our listener can never run. Dominates the
266
+ * other inputs (a silent canary means the delivery channel is gone).
267
+ * - canary fired + read threw → `{ ok: true, reason: 'service-absent' }` —
268
+ * the seam works but the reading ctx does not resolve `subagents` (on the
269
+ * real composition the apply ctx is inject-guarded; cordis resolves the
270
+ * service in dispatch scopes, where reads are intercepted per read). Never
271
+ * a warn.
272
+ * - canary fired + branded value → `{ ok: true }` — healthy.
273
+ * - canary fired + any other resolved value (unbranded object, primitive,
274
+ * undefined) → `{ ok: false, reason: 'wrap-skipped' }` — the listener ran
275
+ * but the shape was unrecognized (the `wrapSubagentsService` pass-through).
276
+ */
277
+ export declare function evaluateSeamProbe({ dispatched, readError, value }: SeamProbeInputs): PersonaSeamProbeResult;
278
+ /**
279
+ * Apply-time seam probe — runs EXACTLY ONCE per apply, immediately after
280
+ * {@link registerRolePersonaChannel} (entry wiring), and classifies the
281
+ * channel's install state per {@link evaluateSeamProbe} (observation only —
282
+ * it never changes delivery semantics):
283
+ *
284
+ * 1. register a TEMPORARY {@link PERSONA_SEAM_EVENT} canary listener
285
+ * (`(ctx, name, error, next) => { dispatched = true; return next() }`),
286
+ * keeping the disposer `ctx.on` returns;
287
+ * 2. perform ONE controlled proxied read `ctx.subagents` inside try/catch —
288
+ * the exact read path the per-call `ctx.subagents.start(...)` dispatch
289
+ * takes. NEVER `ctx.get('subagents')`: the accessor reads the service
290
+ * store directly and bypasses the waterfall, which would false-warn
291
+ * `seam-absent` on every healthy boot;
292
+ * 3. dispose the canary SYNCHRONOUSLY (`finally` — also on the throwing
293
+ * read path);
294
+ * 4. classify via {@link evaluateSeamProbe} and log: `ok === false` → ONE
295
+ * warn ({@link SEAM_WARN} + reason); `service-absent` → one debug; a
296
+ * healthy probe stays silent.
297
+ *
298
+ * Contained failure: the body is fully try/catch-wrapped — any probe-
299
+ * internal error (e.g. a rejected listener registration) fails OPEN with
300
+ * `{ ok: true }` plus ONE debug naming the error. Never throws out of
301
+ * `apply`; never affects a subagent start.
302
+ *
303
+ * @param ctx - the plugin's registrant context (a runtime-bearing context —
304
+ * proxied property reads on it dispatch the seam waterfall).
305
+ */
306
+ export declare function probeRolePersonaSeam(ctx: Context): PersonaSeamProbeResult;
190
307
  export {};
package/dist/index.d.ts CHANGED
@@ -29,7 +29,7 @@ export type { DispatchGateAdvisory } from './gates/dispatch.ts';
29
29
  export { ROLE_PERSONA_LOGGER, registerRolePersonaChannel, setRolePersonaAgentsDir, setRolePersonaLogger, } from './gates/role-persona.ts';
30
30
  export type { RolePersonaLogLevel, RolePersonaLogSink, SubagentStartRequestView, SubagentsServiceView } from './gates/role-persona.ts';
31
31
  export { ADVISORY_LOGGER, runFallbacksAdvisory, setAdvisoryLogger } from './gates/fallbacks-advisory.ts';
32
- export type { AdvisoryLogLevel, AdvisoryLogSink } from './gates/fallbacks-advisory.ts';
32
+ export type { AdvisoryLogLevel, AdvisoryLogSink, AdvisoryPassReport } from './gates/fallbacks-advisory.ts';
33
33
  export { DshHostAdapter } from './gates/adapter.ts';
34
34
  export type { DshHostAdapterOptions } from './gates/adapter.ts';
35
35
  /** Cordis function-plugin name registered by the Loader. */