dsh-advisor 0.4.0 → 0.5.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/lib/config.js CHANGED
@@ -4,10 +4,19 @@
4
4
  * The exported schemastery `Config` schema is what the cordis Loader uses to
5
5
  * validate the plugin row config: it applies defaults (`enabled` false,
6
6
  * `immuneTurns` 3, `maxDeltaMessages` 60, `systemPrompt` "") and enforces
7
- * types/bounds (integers ≥ 0). `resolveAdvisorConfig(raw)` additionally
8
- * enforces the explicit model gate: when `enabled` is true but `provider` or
9
- * `model` is missing or empty, it resolves to a disabled-with-reason config —
10
- * the advisor never starts a model call (hard gate, not a warning).
7
+ * types/bounds (integers ≥ 0). All six live fields are declared `.volatile()`:
8
+ * the Loader commits edits to them into the running fiber's references WITHOUT
9
+ * a remount (dsh 0.1.7-rc.1 — the settings.yaml user layer is gone), so
10
+ * `apply` receives each field as a `{ get() }` reference and every read must
11
+ * unwrap it first — {@link unwrapAdvisorConfig}, which tolerates plain values
12
+ * too (integration harnesses pass plain objects).
13
+ *
14
+ * `resolveAdvisorConfig(raw)` additionally enforces the explicit model gate:
15
+ * when `enabled` is true but `provider` or `model` is missing or empty, it
16
+ * resolves to a disabled-with-reason config — the advisor never starts a model
17
+ * call (hard gate, not a warning). The volatile unwrap happens BEFORE the gate:
18
+ * an unwrapped reference object is truthy, so the enabled/provider/model reads
19
+ * would silently pass the gate on the reference objects themselves.
11
20
  *
12
21
  * @module dsh-advisor/config
13
22
  */
@@ -32,17 +41,26 @@ const CONFIG_KEYS = new Set([
32
41
  * stay optional so an enabled-without-pair config validates and then resolves
33
42
  * to disabled-with-reason instead of failing to load.
34
43
  *
44
+ * Volatile (dsh 0.1.7-rc.1): every field is a LIVE field. The Loader commits
45
+ * edits into the running fiber's references without remounting the plugin
46
+ * (same pattern as dsh `agent-default-model`), so flat, always-present fields
47
+ * are exactly what the settings forms surface. `.volatile()` requires a fixed
48
+ * object path with no enclosing volatile field — this flat schema qualifies.
49
+ *
35
50
  * Type note: left to inference (`Schema<ObjectS, ObjectT>`), so calling the
36
51
  * schema accepts partial input (each key optional, `| null`) and yields the
37
- * fully-defaulted output — matching schemastery's runtime semantics.
52
+ * fully-defaulted output — matching schemastery's runtime semantics. With
53
+ * `.volatile()` the output type of each field is the reference shape; the
54
+ * resolver reads through {@link unwrapAdvisorConfig}, keeping this module's
55
+ * exported contracts plain-valued.
38
56
  */
39
57
  export const Config = z.object({
40
- enabled: z.boolean().default(false),
41
- provider: z.string(),
42
- model: z.string(),
43
- systemPrompt: z.string().default(''),
44
- immuneTurns: z.number().step(1).min(0).default(3),
45
- maxDeltaMessages: z.number().step(1).min(0).default(60),
58
+ enabled: z.boolean().default(false).volatile(),
59
+ provider: z.string().volatile(),
60
+ model: z.string().volatile(),
61
+ systemPrompt: z.string().default('').volatile(),
62
+ immuneTurns: z.number().step(1).min(0).default(3).volatile(),
63
+ maxDeltaMessages: z.number().step(1).min(0).default(60).volatile(),
46
64
  });
47
65
  function isNonEmptyString(value) {
48
66
  // Trim before checking: a whitespace-only value (" ") is empty in effect
@@ -50,6 +68,44 @@ function isNonEmptyString(value) {
50
68
  // qc3 I-3 — a strict superset of dsh's own `length === 0` check).
51
69
  return typeof value === 'string' && value.trim().length > 0;
52
70
  }
71
+ /**
72
+ * Read one volatile field: unwrap the `{ get() }` reference the Loader hands
73
+ * over, pass plain values through. Duck-typed on purpose — cosmokit's
74
+ * reference protocol is structural (`{ get }`), and importing the cosmokit
75
+ * types would add an undeclared dependency for one member.
76
+ */
77
+ function unwrapReference(value) {
78
+ if (typeof value === 'object' && value !== null && typeof value.get === 'function') {
79
+ return value.get();
80
+ }
81
+ return value;
82
+ }
83
+ /**
84
+ * Snapshot the live entry config into a plain {@link AdvisorConfig}: every
85
+ * schema-declared field is read through its volatile reference (or taken as
86
+ * the plain value it is on non-Loader paths), and `null` — which schemastery
87
+ * passes through for fields without a default — is normalized to `undefined`
88
+ * so the resolved contract is null-free and the gate treats null exactly like
89
+ * a missing value. Keys outside the schema ride along untouched (the
90
+ * schemastery object resolver merges unknown keys through; they are never
91
+ * volatile-declared, so they are never references) — the hard gate's
92
+ * unknown-key rejection must keep seeing them.
93
+ *
94
+ * This is a snapshot read, NOT a validation: the result is the RAW composed
95
+ * config `resolveAdvisorConfig` consumes.
96
+ */
97
+ export function unwrapAdvisorConfig(raw) {
98
+ const { enabled, provider, model, systemPrompt, immuneTurns, maxDeltaMessages, ...rest } = raw;
99
+ return {
100
+ ...rest,
101
+ enabled: unwrapReference(enabled),
102
+ provider: unwrapReference(provider) ?? undefined,
103
+ model: unwrapReference(model) ?? undefined,
104
+ systemPrompt: unwrapReference(systemPrompt),
105
+ immuneTurns: unwrapReference(immuneTurns),
106
+ maxDeltaMessages: unwrapReference(maxDeltaMessages),
107
+ };
108
+ }
53
109
  /**
54
110
  * Resolve the raw config into the runtime contract.
55
111
  *
@@ -57,6 +113,10 @@ function isNonEmptyString(value) {
57
113
  * - Applies the explicit model gate (S4): `enabled: true` with `provider` or
58
114
  * `model` missing/empty → disabled-with-reason, never throws, no model call.
59
115
  * - `provider`/`model` are ignored while disabled.
116
+ *
117
+ * The volatile unwrap happens FIRST (before any gate read): a reference
118
+ * object is truthy regardless of the value behind it, so gating on the raw
119
+ * volatile fields would silently enable the advisor on `{ get() }` objects.
60
120
  */
61
121
  export function resolveAdvisorConfig(raw) {
62
122
  if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
@@ -67,15 +127,9 @@ export function resolveAdvisorConfig(raw) {
67
127
  throw new Error(`dsh-advisor: unknown config key "${key}"`);
68
128
  }
69
129
  }
70
- const config = Config(raw);
71
- // schemastery passes nullable input (null) through for fields without a
72
- // default — normalize to undefined so the resolved contract is null-free
73
- // and the gate treats null exactly like a missing value.
74
- const normalized = {
75
- ...config,
76
- provider: config.provider ?? undefined,
77
- model: config.model ?? undefined,
78
- };
130
+ // Config(raw) resolves each `.volatile()` field to its reference; unwrap
131
+ // into the plain contract the gate (and every consumer) reads.
132
+ const normalized = unwrapAdvisorConfig(Config(raw));
79
133
  if (!normalized.enabled)
80
134
  return normalized;
81
135
  const missing = [];
package/lib/config.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,CAAC,MAAM,0BAA0B,CAAA;AA8BxC;;;;;GAKG;AACH,MAAM,WAAW,GAAwB,IAAI,GAAG,CAAC;IAC/C,SAAS;IACT,UAAU;IACV,OAAO;IACP,cAAc;IACd,aAAa;IACb,kBAAkB;CACnB,CAAC,CAAA;AAEF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7B,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IACnC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IACpC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACjD,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;CACxD,CAAC,CAAA;AAEF,SAAS,gBAAgB,CAAC,KAAyB;IACjD,2EAA2E;IAC3E,2EAA2E;IAC3E,kEAAkE;IAClE,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAA;AAC7D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAY;IAC/C,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAClE,MAAM,IAAI,SAAS,CAAC,mDAAmD,CAAC,CAAA;IAC1E,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACnC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,oCAAoC,GAAG,GAAG,CAAC,CAAA;QAC7D,CAAC;IACH,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;IAC1B,wEAAwE;IACxE,yEAAyE;IACzE,yDAAyD;IACzD,MAAM,UAAU,GAAkB;QAChC,GAAG,MAAM;QACT,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,SAAS;QACtC,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,SAAS;KACjC,CAAA;IACD,IAAI,CAAC,UAAU,CAAC,OAAO;QAAE,OAAO,UAAU,CAAA;IAC1C,MAAM,OAAO,GAAa,EAAE,CAAA;IAC5B,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IACpE,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAC9D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,UAAU,CAAA;IAC3C,MAAM,cAAc,GAAG,OAAO,CAAC,MAAM,KAAK,CAAC;QACzC,CAAC,CAAC,mFAAmF;QACrF,CAAC,CAAC,eAAe,OAAO,CAAC,CAAC,CAAC,qDAAqD,CAAA;IAClF,OAAO,EAAE,GAAG,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,CAAA;AAC1D,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/lib/delivery.d.ts CHANGED
@@ -22,13 +22,13 @@
22
22
  * countdown and the KD-5 reset.
23
23
  *
24
24
  * Message shape (spec §6): a user-role message via `createUserMessage` whose
25
- * source is the classified first-party `plugin` arm
26
- * (`kind: 'plugin'`, `plugin: 'advisor'`; src/kinds.ts) and whose content is
27
- * self-describing `[advisor:{severity}] {note}` — the only cue the primary
28
- * model gets about how to treat it ("weigh, don't blindly obey" spirit). The
29
- * plugin's identity rides `source.plugin`, never `source.kind`: `kind` is
30
- * frozen per session-format generation, so a custom value there would make the
31
- * whole log unreadable to a future format edge.
25
+ * source is this plugin's own producer kind (`kind: 'advisor'`, pinned to the
26
+ * `notice` form; src/kinds.ts) and whose content is self-describing
27
+ * `[advisor:{severity}] {note}` — the only cue the primary model gets about
28
+ * how to treat it ("weigh, don't blindly obey" spirit). The kind is claimed by
29
+ * declaration-merging `MessageSourceMap` in dsh-llm, and the V3→V4 format edge
30
+ * preserves direct source kinds, so this identity is cross-generation-safe —
31
+ * the property the retired `plugin` arm used to be borrowed for.
32
32
  *
33
33
  * Delivery is synchronous and fire-and-forget; the runtime path (T4 F1) is
34
34
  * what contains a throwing `inject`/`steer` — this module lets agent-method
@@ -68,8 +68,8 @@ export interface AdvisorDeliveryOptions {
68
68
  }
69
69
  /**
70
70
  * Build the advisor message for one note (spec §6): a user-role message whose
71
- * source carries this plugin's identity on the `plugin` arm and whose content
72
- * is self-describing `[advisor:{severity}] {note}`.
71
+ * source carries this plugin's own producer kind and whose content is
72
+ * self-describing `[advisor:{severity}] {note}`.
73
73
  *
74
74
  * Bounds (qc3 F-2 / qc2 S-1): the note itself is already capped at
75
75
  * `ADVISOR_NOTE_MAX_CHARS` by extraction; the collapsed-row summary is
package/lib/delivery.js CHANGED
@@ -22,13 +22,13 @@
22
22
  * countdown and the KD-5 reset.
23
23
  *
24
24
  * Message shape (spec §6): a user-role message via `createUserMessage` whose
25
- * source is the classified first-party `plugin` arm
26
- * (`kind: 'plugin'`, `plugin: 'advisor'`; src/kinds.ts) and whose content is
27
- * self-describing `[advisor:{severity}] {note}` — the only cue the primary
28
- * model gets about how to treat it ("weigh, don't blindly obey" spirit). The
29
- * plugin's identity rides `source.plugin`, never `source.kind`: `kind` is
30
- * frozen per session-format generation, so a custom value there would make the
31
- * whole log unreadable to a future format edge.
25
+ * source is this plugin's own producer kind (`kind: 'advisor'`, pinned to the
26
+ * `notice` form; src/kinds.ts) and whose content is self-describing
27
+ * `[advisor:{severity}] {note}` — the only cue the primary model gets about
28
+ * how to treat it ("weigh, don't blindly obey" spirit). The kind is claimed by
29
+ * declaration-merging `MessageSourceMap` in dsh-llm, and the V3→V4 format edge
30
+ * preserves direct source kinds, so this identity is cross-generation-safe —
31
+ * the property the retired `plugin` arm used to be borrowed for.
32
32
  *
33
33
  * Delivery is synchronous and fire-and-forget; the runtime path (T4 F1) is
34
34
  * what contains a throwing `inject`/`steer` — this module lets agent-method
@@ -37,11 +37,10 @@
37
37
  * @module dsh-advisor/delivery
38
38
  */
39
39
  import { boundContextSummary, createUserMessage } from '@deepseek-ai/dsh-llm';
40
- import { ADVISOR_PLUGIN_ID } from './kinds.js';
41
40
  /**
42
41
  * Build the advisor message for one note (spec §6): a user-role message whose
43
- * source carries this plugin's identity on the `plugin` arm and whose content
44
- * is self-describing `[advisor:{severity}] {note}`.
42
+ * source carries this plugin's own producer kind and whose content is
43
+ * self-describing `[advisor:{severity}] {note}`.
45
44
  *
46
45
  * Bounds (qc3 F-2 / qc2 S-1): the note itself is already capped at
47
46
  * `ADVISOR_NOTE_MAX_CHARS` by extraction; the collapsed-row summary is
@@ -57,8 +56,7 @@ export function buildAdvisorMessage(note) {
57
56
  const text = `[advisor:${note.severity}] ${note.note}`;
58
57
  const summary = `[${text.slice('[advisor:'.length)}`;
59
58
  const source = {
60
- kind: 'plugin',
61
- plugin: ADVISOR_PLUGIN_ID,
59
+ kind: 'advisor',
62
60
  // n4 (user direction): declare the notice form + a collapsed-row summary so
63
61
  // the web shell's ContextInjectionRow shows "… · advisor · [nit] <note>"
64
62
  // instead of a bare producer label. The severity tag is part of the summary
@@ -1 +1 @@
1
- {"version":3,"file":"delivery.js","sourceRoot":"","sources":["../src/delivery.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"delivery.js","sourceRoot":"","sources":["../src/delivery.ts"],"names":[],"mappings":"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"}
package/lib/gateway.d.ts CHANGED
@@ -11,23 +11,24 @@
11
11
  * exactly one plain-object `args` field whose keys are the method parameter
12
12
  * names (`get()` → `{ args: {} }`; `set(patch)` → `{ args: { patch } }`).
13
13
  *
14
- * Data: `get` reads the `AdvisorSettingsBridge` source — the same live
15
- * composed config the runtime reads (schema defaults → plugin-row base →
16
- * settings user layer), resolved through the `resolveAdvisorConfig` hard gate
17
- * (the SSOT for enabled-without-pair disabled-with-reason). `set` validates
18
- * the patch against the `Config` schema first (unknown-key rejection
19
- * unchanged — the settings service itself is non-strict and would accept the
20
- * unknown key), then writes the USER layer in-process via
21
- * `ctx.settings.update` (no exposed-namespace gate on the in-process write —
22
- * the wire-level `exposedNamespaces()` check only guards the apiproxy path),
23
- * and returns the new composed value.
14
+ * Data: `get` reads the `AdvisorSettingsBridge` source — the same live entry
15
+ * config the runtime reads (volatile references unwrapped), resolved through
16
+ * the `resolveAdvisorConfig` hard gate (the SSOT for enabled-without-pair
17
+ * disabled-with-reason). `set` validates the patch against the `Config`
18
+ * schema first (unknown-key rejection unchanged — the settings service itself
19
+ * is non-strict and would accept the unknown key), then writes the advisor
20
+ * ENTRY config in-process via `settings.update(ADVISOR_SETTINGS_NAMESPACE, ...)`
21
+ * (dsh 0.1.7-rc.1: the namespace key IS the profile entry id — the bundle row
22
+ * id `advisor` — and the write lands through the config editor into the
23
+ * Loader, which commits the volatile fields and emits `loader/volatile-update`,
24
+ * re-applying the runtime through the bridge with no restart), and returns
25
+ * the new composed value.
24
26
  *
25
27
  * The settings service is OPTIONAL (no settings service → the bridge source
26
28
  * stays the entry, `get` still works; `set` fails with a clear error — KD-G5
27
29
  * fallback). The gateway captures the service through a conditional
28
- * `ctx.inject(['settings'], ...)` child (the same activation pattern as
29
- * `installAdvisorSettings`), because `ctx.settings` is only resolvable from a
30
- * fiber that declares it.
30
+ * `ctx.inject(['settings'], ...)` child, because `ctx.settings` is only
31
+ * resolvable from a fiber that declares it.
31
32
  *
32
33
  * The returned config is normalized to the typertGateway JSON wire boundary:
33
34
  * absent keys (provider/model/disabledReason) are OMITTED, never
@@ -40,9 +41,84 @@ import type { Context } from '@deepseek-ai/cordis';
40
41
  import type { TypertContribution } from '@deepseek-ai/dsh-typert-registry';
41
42
  import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
42
43
  import type { AdvisorSettingsBridge } from './settings.js';
44
+ import type { AdvisorModelSource } from './commands.js';
43
45
  import type { AdvisorConfig, ResolvedAdvisorConfig } from './config.js';
44
46
  /** Patch shape accepted by `advisor.set` — any subset of the config keys. */
45
47
  export type AdvisorConfigPatch = Partial<AdvisorConfig>;
48
+ /** An atomic reviewer-route pair on the wire (JSON-safe; never undefined-valued). */
49
+ export interface AdvisorPairWire {
50
+ readonly provider: string;
51
+ readonly model: string;
52
+ }
53
+ /**
54
+ * The authoritative per-session advisor snapshot (spec §5.3) — the wire value
55
+ * of `advisor/getSession` and of a successful `advisor/setSessionModel`.
56
+ * Absent optional keys are OMITTED (the typertGateway result validation
57
+ * rejects undefined values), never present-as-undefined:
58
+ *
59
+ * - `modelOverride` — the session's pinned atomic pair; omitted while the
60
+ * session inherits the global defaults;
61
+ * - `modelSource` — where the effective route comes from (`session` = the
62
+ * pin, `global` = the composed global pair); present iff an effective pair
63
+ * exists at all;
64
+ * - `effectiveModel` — the effective route; omitted when neither level has a
65
+ * complete pair;
66
+ * - `disabledReason` — the S4 explicit-gate reason when it blocks.
67
+ */
68
+ export interface AdvisorSessionSnapshotWire {
69
+ readonly sessionId: string;
70
+ readonly enabled: boolean;
71
+ /** Live-session lifetime marker (spec §5.3 — the snapshot is never durable). */
72
+ readonly lifetime: 'live-session';
73
+ readonly modelOverride?: AdvisorPairWire;
74
+ readonly modelSource?: AdvisorModelSource;
75
+ readonly effectiveModel?: AdvisorPairWire;
76
+ readonly disabledReason?: string;
77
+ }
78
+ /**
79
+ * Plugin-domain error tags carried in RETURNED DATA (`{ error: { tag,
80
+ * message } }`) — never new RemoteError codes (dsh's failure vocabulary stays
81
+ * frozen) and never a thrown coded failure for these business outcomes:
82
+ *
83
+ * - `advisor/session-unknown` — the target session is not (or no longer)
84
+ * live; rejected without allocating any state;
85
+ * - `advisor/unavailable` — this gateway fiber holds no elected owner (no
86
+ * controller face); the endpoints stay inert rather than guessing;
87
+ * - `advisor/rejected` — the selection failed pair validation (nothing was
88
+ * written);
89
+ * - `advisor/failed` — the pre-commit `resolveModelInfo` validation failed
90
+ * (previous selection untouched);
91
+ * - `advisor/superseded` — a newer set/reset superseded the attempt;
92
+ * - `advisor/cancelled` — the attempt was cancelled before commit.
93
+ */
94
+ export type AdvisorSessionErrorTag = 'advisor/session-unknown' | 'advisor/unavailable' | 'advisor/rejected' | 'advisor/failed' | 'advisor/superseded' | 'advisor/cancelled';
95
+ /** One `advisor/getSession` / `advisor/setSessionModel` result: snapshot or tagged data error. */
96
+ export type AdvisorSessionRpcResult = {
97
+ readonly snapshot: AdvisorSessionSnapshotWire;
98
+ } | {
99
+ readonly error: {
100
+ readonly tag: AdvisorSessionErrorTag;
101
+ readonly message: string;
102
+ };
103
+ };
104
+ /** Build one tagged data error (keeps every call site's shape identical). */
105
+ export declare function advisorSessionError(tag: AdvisorSessionErrorTag, message: string): AdvisorSessionRpcResult;
106
+ /**
107
+ * The elected owner's session face — implemented by the wiring (`index.ts`)
108
+ * against the SAME `AdvisorCommandController` the `/advisor model` commands
109
+ * drive (spec §5.3: one controller, no second state store, no divergence
110
+ * between the command face and the web face). The gateway consults it lazily
111
+ * per request: only the reviewer-claiming fiber assigns it, so a gateway on a
112
+ * non-owner fiber stays inert (`advisor/unavailable`) instead of guessing.
113
+ */
114
+ export interface AdvisorSessionGatewayFace {
115
+ /** Authoritative snapshot for one live session (unknown → tagged error). */
116
+ getSession(sessionId: string): AdvisorSessionRpcResult;
117
+ /** Validate + commit a pair through the command controller (same fencing). */
118
+ setSessionModel(sessionId: string, provider: string, model: string): Promise<AdvisorSessionRpcResult>;
119
+ /** Drop the pin and re-inherit (selection: null); never touches the enable override. */
120
+ resetSessionModel(sessionId: string): AdvisorSessionRpcResult;
121
+ }
46
122
  /**
47
123
  * The host-side `advisor` config gateway (`/api/advisor/get` +
48
124
  * `/api/advisor/set`). Registered as the cordis service key `'advisor'`
@@ -64,12 +140,24 @@ export declare class AdvisorConfigGateway extends TypertRemoteService {
64
140
  private readonly bridge;
65
141
  /** The live settings service once the optional inject child activates. */
66
142
  private settings;
143
+ /**
144
+ * The elected owner's session face, resolved LAZILY per request. The
145
+ * reviewer-claiming fiber assigns it after its controller exists (the
146
+ * gateway is constructed before the single-reviewer claim); a `undefined`
147
+ * answer means this fiber owns the service key but not the reviewer role —
148
+ * the session endpoints answer `advisor/unavailable` in data and stay
149
+ * inert (no second controller, no surviving-fiber promotion).
150
+ */
151
+ private readonly sessionControl;
67
152
  /**
68
153
  * @param ctx - owning context (the plugin fiber's ctx inside `apply`).
69
154
  * @param bridge - the same `AdvisorSettingsBridge` the runtime reads, so
70
155
  * get/set always operate on the live composed config.
156
+ * @param sessionControl - lazy access to the elected owner's session face
157
+ * (B2); defaults to "absent" so a direct construction without the wiring
158
+ * keeps the session endpoints cleanly unavailable.
71
159
  */
72
- constructor(ctx: Context, bridge: AdvisorSettingsBridge);
160
+ constructor(ctx: Context, bridge: AdvisorSettingsBridge, sessionControl?: () => AdvisorSessionGatewayFace | undefined);
73
161
  /**
74
162
  * Read the current composed config (schema defaults → entry base → settings
75
163
  * user layer) through the hard gate.
@@ -79,8 +167,35 @@ export declare class AdvisorConfigGateway extends TypertRemoteService {
79
167
  config: ResolvedAdvisorConfig;
80
168
  };
81
169
  /**
82
- * Validate a config patch and write it to the settings USER layer (live —
83
- * the runtime re-applies through the bridge `onChange`; no restart needed).
170
+ * B2 — the authoritative snapshot for one live session
171
+ * (`/api/advisor/getSession`, args `{ sessionId }`). Read-only: never
172
+ * allocates override state (the snapshot rides the wiring's `sessionStatus`
173
+ * readback). Unknown/disposed targets answer `advisor/session-unknown`
174
+ * without touching the controller; a gateway without an elected owner
175
+ * answers `advisor/unavailable`. Business outcomes live in the returned
176
+ * data (plugin-domain tags) — nothing here throws coded failures and the
177
+ * dsh failure vocabulary stays frozen.
178
+ */
179
+ getSession(sessionId: string): AdvisorSessionRpcResult;
180
+ /**
181
+ * B2 — set (or reset) the per-session reviewer route for one live session
182
+ * (`/api/advisor/setSessionModel`, args `{ sessionId, selection }`).
183
+ * `selection: null` = reset (re-inherit the CURRENT global defaults; never
184
+ * touches the enable override). A non-null selection must be the atomic
185
+ * `{ provider, model }` pair and passes the SAME validation as the command
186
+ * face (`parseModelPair` — spec §5.3); the write itself routes through the
187
+ * elected owner's `AdvisorCommandController` (`setModel`/`resetModel`), so
188
+ * pre-commit `resolveModelInfo` validation (60 s, cancellable, no retry),
189
+ * per-session generation fencing, and the route-change semantics are
190
+ * INHERITED, not reimplemented. Unknown/disposed targets are rejected
191
+ * BEFORE the controller runs — no generation is allocated for a dead
192
+ * session. Returns the post-commit snapshot on success.
193
+ */
194
+ setSessionModel(sessionId: string, selection: unknown): Promise<AdvisorSessionRpcResult>;
195
+ /**
196
+ * Validate a config patch and write it to the advisor ENTRY config (live —
197
+ * the Loader commits the volatile fields and the runtime re-applies through
198
+ * the bridge `onChange`; no restart needed).
84
199
  * @param patch - any subset of the config keys; unknown keys are rejected
85
200
  * by the `Config` schema before anything is written.
86
201
  * @returns the NEW composed config after the write.
@@ -91,14 +206,14 @@ export declare class AdvisorConfigGateway extends TypertRemoteService {
91
206
  config: ResolvedAdvisorConfig;
92
207
  }>;
93
208
  /**
94
- * Resolve the live composed config through the hard gate. Containment
95
- * (qc2 W-1): a user layer the resolver rejects (e.g. an unknown key that
96
- * survived the non-strict settings schema) resolves to disabled-with-reason
97
- * carrying the message — the gateway never fails the RPC on a bad user
98
- * layer, and gate semantics hold (no model call can start). S1: when the
99
- * raw source is still readable, the fallback seeds its scalar latches
100
- * (systemPrompt / immuneTurns / maxDeltaMessages) instead of hardcoded
101
- * defaults, so an invalid layer only drops the offending keys.
209
+ * Resolve the live entry config through the hard gate. Containment
210
+ * (qc2 W-1): an entry config the resolver rejects (e.g. an unknown key
211
+ * that survived the non-strict schemastery object merge) resolves to
212
+ * disabled-with-reason carrying the message — the gateway never fails the
213
+ * RPC on a bad config, and gate semantics hold (no model call can start).
214
+ * S1: when the raw source is still readable, the fallback seeds its scalar
215
+ * latches (systemPrompt / immuneTurns / maxDeltaMessages) instead of
216
+ * hardcoded defaults, so an invalid config only drops the offending keys.
102
217
  */
103
218
  private readConfig;
104
219
  }