Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions .mstar/specs/advisor-plugin.md

Large diffs are not rendered by default.

21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,29 +28,30 @@ Same plugin, either front end — the only difference is the `--profile` flag. P

### Configuration

Edit the `config` of the `advisor` row in your profile's patch layer (`~/.dsh/profiles/<profile>/cordis.patch.yml`). All six fields are schema-volatile live fields (dsh ≥ 0.1.7-rc.1): the web card and the TUI `/settings` screen write this same entry config — persisted in the profile patch, committed without a remount. (A pre-0.1.7 `$DSH_HOME/settings.yaml` `advisor:` section no longer exists: dsh imports it into the active profile once and renames the file `.imported`.)
Edit the `config` of the `advisor` row in your profile's patch layer (`~/.dsh/profiles/<profile>/cordis.patch.yml`). All five fields are schema-volatile live fields (dsh ≥ 0.1.7-rc.1): the web card and the TUI `/settings` screen write this same entry config — persisted in the profile patch, committed without a remount. (A pre-0.1.7 `$DSH_HOME/settings.yaml` `advisor:` section no longer exists: dsh imports it into the active profile once and renames the file `.imported`.)

> **Breaking change (2026-09-26): the `enabled` config key was removed.** The plugin-row enable/disable toggle in the host UI is the master switch — a running row is enabled, and there is nothing left to configure for it. A stored profile still carrying an `enabled:` line keeps working: the line is **silently ignored** (accepted, never read, never re-persisted — 2026-09-27 ruling, no manual deletion needed); the key is deprecated and the write paths no longer persist it.

```yaml
# ~/.dsh/profiles/<profile>/cordis.patch.yml — the advisor row's config
- id: advisor
config:
enabled: true # master switch (default false) — set explicitly to enable
provider: deepseek-official # REQUIRED when enabled
model: deepseek-flash # REQUIRED when enabled; fallback: deepseek-v4-flash (or another V4 id) until the gateway enables the V41 route
provider: deepseek-official # REQUIRED (non-empty) for the advisor to run
model: deepseek-flash # REQUIRED (non-empty); fallback: deepseek-v4-flash (or another V4 id) until the gateway enables the V41 route
systemPrompt: "" # optional; "" = built-in reviewer prompt
immuneTurns: 3 # int ≥ 0, default 3 — cooldown after a delivered steer
maxDeltaMessages: 60 # int ≥ 0, default 60 — delta window; 0 = unbounded
```

The advisor is off by default. When enabled, `provider` and `model` are **mandatory**: `enabled: true` without both is a hard gate — the advisor never starts a model call and reports a disabled-with-reason status; unknown config keys are rejected.
`provider` and `model` are **mandatory**: either missing or empty is a hard gate — the advisor never starts a model call and reports a disabled-with-reason status; unknown config keys are rejected.

The same keys are read and edited from **three surfaces** (one store — the advisor entry config above; every surface shares the same key set and the same hard gate, with the host-side gate as the final line of defense on every path):

1. **Plugin-row config** — the profile patch layer (`~/.dsh/profiles/<profile>/cordis.patch.yml`). This is where the config lives.
2. **dsh web Plugins page — the dsh-advisor bundle's own page** — the Advisor **card** (bundle key `dsh-advisor`) with the enabled toggle, provider / model selects restricted to system-configured providers and their models, and the optional fields. Saving writes the advisor entry's config (landed through the config editor into the profile patch) and applies to running sessions immediately — no restart. The card requires a dsh web build whose shell declares the `plugins.bundle.config` card slot (dsh ≥ 0.1.7-rc.1) and loads packages that declare `dsh.client`; it reads and writes the config through the official `GatewayService` RPC channel (`/api/advisor/get` + `/api/advisor/set`), which is not gated by the settings exposure allowlist. It additionally blocks saving while enabled with a required field empty.
2. **dsh web Plugins page — the dsh-advisor bundle's own page** — the Advisor **card** (bundle key `dsh-advisor`), a flat settings form (the page's title/description come from the plugin's locale meta) with provider / model selects restricted to system-configured providers and their models, and the optional fields. Saving writes the advisor entry's config (landed through the config editor into the profile patch) and applies to running sessions immediately — no restart. The card requires a dsh web build whose shell declares the `plugins.bundle.config` card slot (dsh ≥ 0.1.7-rc.1) and loads packages that declare `dsh.client`; it reads and writes the config through the official `GatewayService` RPC channel (`/api/advisor/get` + `/api/advisor/set`), which is not gated by the settings exposure allowlist. It additionally blocks saving while a required field is empty.
3. **`/advisor` command** — per-session and ephemeral: it flips a session override and pins a per-session reviewer model, never the persisted config (see [Verify](#verify)).

In a **dsh-tui** profile the same five keys are editable in the TUI `/settings` screen: run `dsh --profile dsh-tui`, open `/settings`, and edit the **Advisor** section (`enabled` / `provider` / `model` / `immuneTurns` / `maxDeltaMessages`, each with zh/en label + hint). Edits are staged and written on save through the revision-fenced `settings.mutate` into the same advisor entry config the web card writes, and re-apply live without a restart. `systemPrompt` is NOT a TUI field (the TUI text control is single-line; a multi-line prompt would be truncated) — edit it via the web card or the profile patch layer. The section requires dsh-tui ≥ v0.8.0 (shipped in the `dsh-tui-settings-sections` row of the v0.8.0+ bundle); older dsh-tui versions no-op it cleanly and the profile patch layer remains the edit path. `/advisor config` stays a read-only readback whose edit hint names the `/settings` screen when the seam is mounted. Save behavior differs from the web card: the TUI seam has no cross-field validation, so a save may set `enabled: true` with empty `provider`/`model` — the explicit model gate resolves that to disabled-with-reason at runtime (visible via `/advisor status` and `/advisor config`); the web card blocks such a save outright. Full reference → [docs/configuration.md](docs/configuration.md).
In a **dsh-tui** profile the same four keys are editable in the TUI `/settings` screen: run `dsh --profile dsh-tui`, open `/settings`, and edit the **Advisor** section (`provider` / `model` / `immuneTurns` / `maxDeltaMessages`, each with zh/en label + hint). Edits are staged and written on save through the revision-fenced `settings.mutate` into the same advisor entry config the web card writes, and re-apply live without a restart. `systemPrompt` is NOT a TUI field (the TUI text control is single-line; a multi-line prompt would be truncated) — edit it via the web card or the profile patch layer. The section requires dsh-tui ≥ v0.8.0 (shipped in the `dsh-tui-settings-sections` row of the v0.8.0+ bundle); older dsh-tui versions no-op it cleanly and the profile patch layer remains the edit path. `/advisor config` stays a read-only readback whose edit hint names the `/settings` screen when the seam is mounted. Save behavior differs from the web card: the TUI seam has no cross-field validation, so a save may leave `provider`/`model` empty — the explicit model gate resolves that to disabled-with-reason at runtime (visible via `/advisor status` and `/advisor config`); the web card blocks such a save outright. Full reference → [docs/configuration.md](docs/configuration.md).

![Advisor card on the dsh web Plugins page (the dsh-advisor bundle page)](docs/screenshots/advisor-settings-card.webp)

Expand All @@ -60,7 +61,7 @@ In a **dsh-tui** profile the same five keys are editable in the TUI `/settings`
dsh --profile web --dump-config # shows a "# == dsh-advisor" layer with the advisor row
```

With the advisor installed and enabled, control it in-session with the `/advisor` command (available when a command registry is composed):
With the advisor installed and its plugin row enabled (the row switch on the Plugins page is the master switch), control it in-session with the `/advisor` command (available when a command registry is composed):

```
/advisor toggle the advisor for this session
Expand All @@ -72,7 +73,7 @@ With the advisor installed and enabled, control it in-session with the `/advisor
/advisor model reset drop the session pin and re-inherit the global defaults
```

`/advisor on|off|toggle` are session-scoped and ephemeral: they flip a per-session override, never the persisted config. Enabling a session whose config lacks `provider`/`model` starts no model call — `/advisor status` (and the `/advisor on` reply) shows the gate reason: the advisor runs only when enabled **with** both configured. `/advisor on` is also the manual recovery path: a session advisor paused by a quota/rate-limit (`quota_exhausted` — no auto-resume timer) resumes in place, and a halted advisor (permanent model error, e.g. invalid credentials) is rebuilt fresh for the session.
`/advisor on|off|toggle` are session-scoped and ephemeral: they flip a per-session override, never the persisted config. Enabling a session whose config lacks `provider`/`model` starts no model call — `/advisor status` (and the `/advisor on` reply) shows the gate reason: the advisor runs only with both configured. `/advisor on` is also the manual recovery path: a session advisor paused by a quota/rate-limit (`quota_exhausted` — no auto-resume timer) resumes in place, and a halted advisor (permanent model error, e.g. invalid credentials) is rebuilt fresh for the session.

`/advisor model set` pins a reviewer model for the **invoking session only** — an in-memory, atomic `provider + model` pair that lives for the live session (cleared on dispose, owner teardown, cold resume, or restart; a forked/new session inherits the global defaults). It rides above the persisted global defaults without rewriting them: a complete session pair is used even when the global config has no pair yet, a malformed global config still blocks every session, half-pairs are never merged, and setting/resetting never touches the enable switch. Validation resolves the pair through the LLM service before commit (60 s bound, cancellable, no auto-retry); on failure the previous selection stays untouched. `/advisor config` remains the readback of the **global defaults**, not the session state.

Expand All @@ -89,7 +90,7 @@ On the **web**, the same session model controls ride the session header's **Advi
[advisor:concern] extract the helper into a module and unit-test it
```

- **Explicit model gate**: `enabled` defaults to off; `enabled: true` without `provider` + `model` never starts a model call — status reports disabled-with-reason. The gate applies to the *effective* route after session resolution: a complete per-session override pair satisfies it for that session; a malformed global config cannot be bypassed. Unknown config keys are rejected.
- **Explicit model gate**: a missing `provider` + `model` never starts a model call — status reports disabled-with-reason. The gate applies to the *effective* route after session resolution: a complete per-session override pair satisfies it for that session; a malformed global config cannot be bypassed. Unknown config keys are rejected — the removed `enabled` key is the one legacy exception (silently ignored; the plugin-row toggle is the switch).
- **Zero-tool minimal start**: the reviewer is an independent model call only — no advisor tools, nothing it can do to the session besides advisory messages.
- **No-stall failure policy**: a failing or quota-limited advisor only drops its own bounded backlog — it can never park or pollute the primary loop.
- **Session-scoped controls**: `/advisor on|off|status|config|model` work per session; the toggles and the per-session model pin are ephemeral overrides, never persisted config — `/advisor config` always reports the global defaults. On the web, the session header's **Advisor action** drives the same per-session pin through dedicated session endpoints (see [Verify](#verify)).
Expand Down
Loading
Loading