Repository navigation
feat(advisor): add defaultEnabled opt-in default for new sessions - #126
Conversation
Add the persisted `defaultEnabled` config key (boolean, schema default true) from GitHub issue omdsh-dev#124. Sessions without an explicit `/advisor` enable override now start from this default instead of the hardcoded "on": `true` keeps every pre-omdsh-dev#124 session byte-identical, while `false` makes the advisor opt-in per session — a session with no override resolves `enabled: false` with a disabled-by-default reason and `/advisor on` writes the ephemeral per-session override that turns it back on. The explicit provider/model gate still applies first, an explicit `/advisor off` still wins (no gate reason), and pinning a session model never toggles the switch. The key is wired through every surface: the Loader schema (volatile, NOT a legacy key), both resolver arms, the gateway wire (always carried like `nextPrompt`), `/advisor config` (always rendered), the web card + store (seeded true, included in the always patch list), the TUI `/settings` section, and the docs/spec key census. It is deliberately NOT part of `runtimeSignature`: `bridge.onChange` reconciles the effective switch instead — a live flip to false disposes running session runtimes, while a flip to true applies to already-tracked sessions and reaches sessions with no runtime/override on their next event.
btspoony
left a comment
There was a problem hiding this comment.
⛔ Verdict: blocked · Score 40% (fail)
| Findings | Count |
|---|---|
| 🔴 must-fix | 1 |
| 🟠 should-fix | 1 |
| 🔵 nit | 2 |
| ❓ unverified | 0 |
📋 Review
What this PR does: adds a persisted boolean defaultEnabled (schema default true) to the advisor entry config, so a session with no own enable override resolves as override ?? defaultEnabled ANDed with the existing provider/model gate. It wires the key through the resolver, the gateway get/set wire, /advisor config, the web card, TUI /settings, the live bridge.onChange re-apply, docs and the frozen spec, and keeps the default byte-identical for existing profiles.
Revalidated findings
none
Findings
🔴 A model reset that closes the pair gate leaves the old runtime running
- Evidence:
src/index.ts:545—applyRouteChangeearly-returns when!effectiveEnabled(sessionId), and this PR changedeffectiveEnabledfromoverride ?? truetooverride ?? defaultEnabledAND the provider/model gate (src/index.ts:373-386).resetModel(src/index.ts:907-912) clears the session pin first, so a session pinned to a valid pair while the global pair is missing (or invalid) now returns beforedisposeRuntime. - Impact:
/advisor model reset(or the web reset) reports the session as disabled while the old runtime keeps its in-flight call and pending backlog, and a later global-pair repair resumes that stale route before the next rebuild — the §5.3 "a route change aborts the old call and drops its backlog" contract is broken, and the session keeps emitting in a state the switch says is off. - Merge class: must-fix
- Confidence: HIGH
- Fix sketch: dispose the runtime before the gate return (
disposeRuntime(sessionId)when the effective switch is off, rebuild only when on), plus a regression test for "pin reset to a missing global pair aborts the old call".
🟠 An explicit /advisor on|off that matches the current state records no session override
- Evidence:
src/commands.ts:705-712(onreturns when already enabled and not recovering),src/commands.ts:717-719(offreturns when already disabled), andsrc/index.ts:798-805(the controller writes the override only when the effective switch changes). - Impact: the new persisted default means "effectively on/off" and "explicitly chosen on/off" are no longer the same state. A session explicitly turned off under
defaultEnabled:falsestores no false override, so flipping the persisted default to true starts reviewing it; mirror case: an explicit on undertrueis lost when the default flips to false. The issue's requirement that the per-session override take precedence is only half satisfied. - Merge class: should-fix
- Confidence: HIGH
- Fix sketch: always record the explicit value on an explicit
on/off; keep start/stop plus the KD-5 recovery routing as a separate decision.
🔵 Delete the DOM-id pin in the new defaultEnabled card spec
- Evidence:
tests/advisor-card.spec.tsx:895—expect(toggle.id).toBe('advisor-default-enabled');getByLabelText(en.defaultEnabled)on the line above already proves the label↔control association the user actually relies on. - Impact: pins an internal wiring detail instead of product behavior. Non-product-assertion class: incidental / source-shape — delete, or replace with a product-behaviour assertion (no replacement needed;
type/checked/hint assertions carry the behavior). - Merge class: nit
- Confidence: HIGH
- Fix sketch: drop the single
idassertion.
🔵 Card header comment states the fields in the wrong order
- Evidence:
src/client/advisor-card.tsx:14-16— the header lists "the pairedimmuneTurns/maxDeltaMessagesnumbers plus theproseFallback/nextPrompt/defaultEnabledcheckboxes"; the DOM renders the three checkboxes (:369-422) first and the.numberFieldsgroup second (:423), which the per-field comment at:374-375states correctly. This PR edited this comment block and kept the inversion. - Impact: a comment that contradicts both the DOM and the adjacent comment; only maintenance cost.
- Merge class: nit
- Confidence: HIGH
- Fix sketch: swap the two phrases to match the render order.
Linked-issue AC
Closes #124 —
- effective enable =
defaultEnabledwhen no session override, still behind the pair gate — met (src/index.ts:373-378). /advisor on|offoverride precedence — met (precedence holds); the same-value no-op gap is finding 🟠, not a second AC increment.- Model pin does not toggle the switch — met (the pin only feeds the pair substitution in
resolveSessionConfig). - New sessions inherit the default — met (no override →
defaultEnabled). defaultEnabled:falsewith no override reports a reason distinct from the missing-model reason — met (disabled by default — enable it for this session with /advisor on), and a missing pair still reports its own gate reason.- Default
truekeeps existing profiles unchanged — met (schema default, not inLEGACY_KEYS, not inruntimeSignature).
✅ Verified
- Read the pinned diff pack (
git diff origin/main...pr-126, 799 changed lines, 25 files) in full across three seats; opened every cited line in the review worktree. - Traced
applyRouteChange/resetModel/ensureRuntime/disposeRuntime/bridge.onChangeand the/advisorcommand handler to confirm both findings' call paths. - Client surface cross-checked against the nine-key census (
src/config.ts,src/gateway.ts), locale parity, and the no-churnpatchForround-trip. - No tests, build or typecheck were run (read-only review); the PR's own typecheck/build/test claims were not re-executed. The PR states handoff to a real dsh Loader / web-GUI host was not exercised — that gap is unchanged by this review, which is static.
🗑️ Considered & rejected
- Per-candidate
sourceConfig()re-run inbridge.onChange: rejected — volatile commits are low-frequency andsourceConfig()reads in-memory bridge references; no measured cost at realistic session counts. - Gateway
readConfigfallback forwarding a non-booleandefaultEnabled: rejected —rawis typedAdvisorConfig, soraw?.defaultEnabled ?? truecannot widen the type. defaultEnabledreintroducing the removed config-levelenabled: rejected — by design; the issue and the spec notes separate the global row toggle from the new-session initial state.
🛠️ Plan to fix
Expand fix plan
1. src/index.ts — applyRouteChange: dispose before the disabled return
- `if (!effectiveEnabled(sessionId)) { disposeRuntime(sessionId); return }`
- keep the unchanged-route short-circuit for the enabled case.
- tests/settings-live.test.ts: assert that resetting a pinned session while the global pair is
missing leaves no runtime (no further llm.stream call) and that a later pair repair builds a
fresh runtime on the new route.
2. commands.ts / index.ts — record the explicit switch
- `/advisor on` and `/advisor off`: write the override even when the effective value already
matches, then run the existing start/stop/recovery decision.
- tests/commands.test.ts: explicit off under defaultEnabled:false leaves a false override; explicit
on under true leaves a true override; flipping the persisted default afterwards does not change
either session.
3. tests/advisor-card.spec.tsx — delete line 895 (DOM id pin).
4. src/client/advisor-card.tsx — reorder the header comment phrases to match the DOM.
Verification: `pnpm typecheck && pnpm build && pnpm test` on the branch.… closes Address the review findings on the defaultEnabled change: - `applyRouteChange` returned early when the effective switch was off, so resetting a session pin while the global pair is missing left the old runtime reviewing a route the switch already reported as off. Dispose before the return so the §5.3 "a route change aborts the old call and drops its backlog" contract holds even when the gate closes (must-fix). - `/advisor on|off` returned "already on/off" without recording the choice. With `override ?? defaultEnabled`, "effectively on" and "explicitly chosen" are different states: always record the explicit value so a later `defaultEnabled` flip cannot silently override it (should-fix). - Drop the DOM-id assertion in the new card spec and reorder the card header comment to match the render order (nits). Regression tests: reset-to-pairless disposes the runtime (no further call) and a later pair repair builds fresh; explicit off under `defaultEnabled: false` / explicit on under `true` survive a default flip.
|
Addressed all four findings in 7de05e3:
Verification on the pushed head: |
Routing an already-on `/advisor on` through setEnabled re-ran the KD-5 seed; `seedTo` resets the session renderer's cursor and fold state, so the next delta would lose the context folded before the command. Seed only on a real off→on transition (or a halted/quota recovery), and keep the override recording unconditional. Test: an idempotent on during a gated in-flight call leaves the backlog and the emission guard intact (both deltas still drain, the duplicate note is still suppressed, no steer).
|
Follow-up on the always-record change (9b68a0e): routing an already-on |
btspoony
left a comment
There was a problem hiding this comment.
⚠️ Verdict: needs fixes · Score 88% (good)
| Findings | Count |
|---|---|
| 🔴 must-fix | 0 |
| 🟠 should-fix | 1 |
| 🔵 nit | 0 |
| ❓ unverified | 0 |
📋 Review
What this PR does: increment re-review of 24c4270..9b68a0e (178 changed lines, 9 files) on top of the settled first round. Two fix commits land: 7de05e3 disposes the runtime when a route change closes the pair gate and records the explicit /advisor on|off choice, and 9b68a0e keeps the KD-5 seed off an idempotent /advisor on. Both prior blocking findings are addressed; the round's only remaining issue is the regression test that is supposed to guard the first fix.
Revalidated findings
| Prior finding | State | Evidence |
|---|---|---|
| 🔴 A model reset that closes the pair gate leaves the old runtime running | fixed |
src/index.ts:550-552 — the closed-gate branch now calls disposeRuntime before returning; src/advisor-runtime.ts:592-596 aborts the call and clears the queue. resetModel still clears the pin then calls applyRouteChange (:924-928). |
🟠 An explicit /advisor on|off that matches the current state records no session override |
fixed |
src/commands.ts:713-715, :728-731 always route through setEnabled; src/index.ts:811-814 writes the override unconditionally; :823-826 seeds only on an off→on transition or a halted/quota recovery, so an idempotent on no longer resets the renderer. |
| 🔵 DOM-id pin in the new card spec | fixed |
tests/advisor-card.spec.tsx:890-898 — the id assertion is deleted; type/checked/hint remain. |
| 🔵 Card header comment field order | fixed |
src/client/advisor-card.tsx:14-15 now lists the checkboxes before the paired numbers, matching the DOM and the per-field comment at :374-375. |
Findings
🟠 The closed-gate reset regression test cannot catch the regression it names
- Evidence:
tests/session-model-override.test.ts:441-464— the new test never blocks the adapter, never queues a backlog delta, and never asserts that the old note stops delivering; it only checks the request count going 1 → 1 → 2. The repair commits the same pair (stub/stub-model) the pin held, so the post-repair request cannot distinguish "old runtime survived" from "fresh runtime built". A closed gate already blocks new deltas from enqueueing (src/index.ts:483), so the middle count stays 1 either way. - Impact: restoring the old
applyRouteChangeearly return would still pass this test — the must-fix behaviour (abort the in-flight call, drop the backlog) ships with no regression defence. The production fix itself is correct; the guard is not. - Merge class: should-fix
- Confidence: HIGH
- Fix sketch: reuse the blocking pattern already in this file (
:723-738): block the adapter, queue a backlog delta, reset the pin to the missing global pair, release, then assert no note was delivered and no queued request fired.
Linked-issue AC
Closes #124 —
- effective enable =
defaultEnabledwhen no session override, behind the pair gate — met. /advisor on|offoverride precedence — met; the increment now also records the explicit choice when the effective state already matches, so a laterdefaultEnabledflip cannot overturn it.- Model pin does not toggle the switch — met.
- New sessions inherit the default — met.
defaultEnabled:falsewith no override reports a reason distinct from the missing-model reason — met.- Default
truekeeps existing profiles unchanged — met.
✅ Verified
- Read the increment pack (315 diff lines / 178 changed lines / 9 files) and verified both fix sites plus the new tests by opening them at head
9b68a0e. - Traced the repaired paths:
applyRouteChangeclosed-gate dispose,setEnabledunconditional override write with the separate runtime decision,ensureRuntime/disposeRuntimeidempotence, and theonChangecandidate loop's behaviour for a session whose runtime was kept. - Confirmed the docs and spec sentence ("an explicit
/advisor on|offalways records the choice") matches the shipped code. - No tests, build or typecheck were run (read-only review); the increment's own test claims were not re-executed.
🗑️ Considered & rejected
- FakeController
setCallsassertions in the idempotent on/off test (tests/commands.test.ts:355,:361): rejected — they are the unit-layer observation of the exact contract this PR introduces ("an already-matchingon/offmust still reachsetEnabled") and follow the file's existingsetCallsconvention (:316-343); product-level coverage elsewhere does not make them meaningless. - Re-seeding the observer on an idempotent
/advisor on: rejected —9b68a0edeliberately skips it (!wasOn || recovering), which is the correct reading of the KD-5 seed contract.
🛠️ Plan to fix
Expand fix plan
1. tests/session-model-override.test.ts — make the closed-gate reset test discriminating
- adapter.blockNext() before the pinned turn;
- feed a second turn while the call is blocked (backlog);
- `/advisor model reset` against the pairless global;
- adapter.release(); assert the inject spy was never called and the backlog request never fired;
- repair the global pair with a DIFFERENT pair, feed a new delta, assert one request on the new route.
2. pnpm typecheck && pnpm build && pnpm test
btspoony
left a comment
There was a problem hiding this comment.
⚠️ Verdict: needs fixes · Score 86% (good)
Corrects review 5597750819: the FakeController wiring assertions below are counted as a nit instead of being rejected.
| Findings | Count |
|---|---|
| 🔴 must-fix | 0 |
| 🟠 should-fix | 1 |
| 🔵 nit | 1 |
| ❓ unverified | 0 |
📋 Review
What this PR does: increment re-review of 24c4270..9b68a0e (178 changed lines, 9 files) on top of the settled first round. Two fix commits land: 7de05e3 disposes the runtime when a route change closes the pair gate and records the explicit /advisor on|off choice, and 9b68a0e keeps the KD-5 seed off an idempotent /advisor on. Both prior blocking findings are addressed; the round's only remaining issue is the regression test that is supposed to guard the first fix.
Revalidated findings
| Prior finding | State | Evidence |
|---|---|---|
| 🔴 A model reset that closes the pair gate leaves the old runtime running | fixed |
src/index.ts:550-552 — the closed-gate branch now calls disposeRuntime before returning; src/advisor-runtime.ts:592-596 aborts the call and clears the queue. resetModel still clears the pin then calls applyRouteChange (:924-928). |
🟠 An explicit /advisor on|off that matches the current state records no session override |
fixed |
src/commands.ts:713-715, :728-731 always route through setEnabled; src/index.ts:811-814 writes the override unconditionally; :823-826 seeds only on an off→on transition or a halted/quota recovery, so an idempotent on no longer resets the renderer. |
| 🔵 DOM-id pin in the new card spec | fixed |
tests/advisor-card.spec.tsx:890-898 — the id assertion is deleted; type/checked/hint remain. |
| 🔵 Card header comment field order | fixed |
src/client/advisor-card.tsx:14-15 now lists the checkboxes before the paired numbers, matching the DOM and the per-field comment at :374-375. |
Findings
🟠 The closed-gate reset regression test cannot catch the regression it names
- Evidence:
tests/session-model-override.test.ts:441-464— the new test never blocks the adapter, never queues a backlog delta, and never asserts that the old note stops delivering; it only checks the request count going 1 → 1 → 2. The repair commits the same pair (stub/stub-model) the pin held, so the post-repair request cannot distinguish "old runtime survived" from "fresh runtime built". A closed gate already blocks new deltas from enqueueing (src/index.ts:483), so the middle count stays 1 either way. - Impact: restoring the old
applyRouteChangeearly return would still pass this test — the must-fix behaviour (abort the in-flight call, drop the backlog) ships with no regression defence. The production fix itself is correct; the guard is not. - Merge class: should-fix
- Confidence: HIGH
- Fix sketch: reuse the blocking pattern already in this file (
:723-738): block the adapter, queue a backlog delta, reset the pin to the missing global pair, release, then assert no note was delivered and no queued request fired.
🔵 Delete the FakeController setCalls wiring assertions in the idempotent on/off test
- Evidence:
tests/commands.test.ts:355,:361—expect(controller.setCalls.at(-1)).toMatchObject({ sessionId, enabled }). The asserted outcome ("the explicit choice survives a laterdefaultEnabledflip") is already proven at product level by the newtests/settings-live.test.tscases, which commit a default flip and read/advisor status. - Impact: a mock-seam call echo that pins the handler→fake call sequence instead of user-visible behaviour. Non-product-assertion class: incidental / wiring — delete, or replace with a product-behaviour assertion (the reply-text assertions on the lines above already carry the unit-level behaviour).
- Merge class: nit
- Confidence: HIGH
- Fix sketch: drop the two
setCallsassertions; keep theonResult.text/offResult.textchecks.
Linked-issue AC
Closes #124 —
- effective enable =
defaultEnabledwhen no session override, behind the pair gate — met. /advisor on|offoverride precedence — met; the increment now also records the explicit choice when the effective state already matches, so a laterdefaultEnabledflip cannot overturn it.- Model pin does not toggle the switch — met.
- New sessions inherit the default — met.
defaultEnabled:falsewith no override reports a reason distinct from the missing-model reason — met.- Default
truekeeps existing profiles unchanged — met.
✅ Verified
- Read the increment pack (315 diff lines / 178 changed lines / 9 files) and verified both fix sites plus the new tests by opening them at head
9b68a0e. - Traced the repaired paths:
applyRouteChangeclosed-gate dispose,setEnabledunconditional override write with the separate runtime decision,ensureRuntime/disposeRuntimeidempotence, and theonChangecandidate loop's behaviour for a session whose runtime was kept. - Confirmed the docs and spec sentence ("an explicit
/advisor on|offalways records the choice") matches the shipped code. - No tests, build or typecheck were run (read-only review); the increment's own test claims were not re-executed.
🗑️ Considered & rejected
- Re-seeding the observer on an idempotent
/advisor on: rejected —9b68a0edeliberately skips it (!wasOn || recovering), which is the correct reading of the KD-5 seed contract.
🛠️ Plan to fix
Expand fix plan
1. tests/session-model-override.test.ts — make the closed-gate reset test discriminating
- adapter.blockNext() before the pinned turn;
- feed a second turn while the call is blocked (backlog);
- `/advisor model reset` against the pairless global;
- adapter.release(); assert the inject spy was never called and the backlog request never fired;
- repair the global pair with a DIFFERENT pair, feed a new delta, assert one request on the new route.
2. pnpm typecheck && pnpm build && pnpm test|
Taking over the remaining fixes from the review round. Planned work on this branch:
Both are test-only changes; no product code is touched. Verification: |
The new regression test could not detect the regression it names: it only compared adapter request counts, and a closed gate already blocks new deltas from enqueueing, so a surviving runtime and a fresh one looked identical. Block the adapter, queue a backlog delta behind it, reset the pin against the pairless global, release, and assert the old note never delivers and the queued delta never reaches the adapter. Repair the global pair with a different pair so the post-repair request proves a fresh runtime on the new route. Also drop the FakeController setCalls assertions from the idempotent on/off test: the reply text and the new settings-live cases already prove the behaviour. I-000044, I-000045
`textReply` already wraps its argument as the advice JSON, so passing a full JSON string produced a nested, unparseable reply. The stale-delivery assertion could then never fire: the note was dropped at extraction rather than by the abort. Pass plain note text so the "no stale note delivered" assertion proves the cancelled call, not a broken fixture. QC finding F-001 (plan dsh-advisor-pr126-review-fixes).
btspoony
left a comment
There was a problem hiding this comment.
✅ Verdict: ship it · Score 98% (mergeable)
| Findings | Count |
|---|---|
| 🔴 must-fix | 0 |
| 🟠 should-fix | 0 |
| 🔵 nit | 1 |
| ❓ unverified | 0 |
📋 Review
What this PR does: increment re-review of 9b68a0e..669e800 (25 changed lines, 2 test files) — the author's follow-up to round 2. It makes the closed-gate reset test actually discriminating (block the adapter, queue a backlog delta, reset against the pairless global, release, assert no delivery and no queued request, then repair with a different pair) and deletes the two FakeController.setCalls wiring assertions. Both round-2 findings are closed; the only remainder is one over-strong comment.
Revalidated findings
| Prior finding | State | Evidence |
|---|---|---|
| 🟠 The closed-gate reset regression test cannot catch the regression it names | fixed |
tests/session-model-override.test.ts:449-467 blocks the first call, queues the second delta, resets, releases, then asserts inject was never called and adapter requests stayed at 1. textReply now receives plain text (:444, helper at :63-65), so a surviving runtime would deliver a parseable nit note and drain the backlog. |
🔵 FakeController setCalls wiring assertions |
fixed |
tests/commands.test.ts:346-360 keeps only the already on / already off reply assertions; setCalls is still used legitimately elsewhere (:316,326,334-336,343), so no dead helper. |
Findings
🔵 The repair step's comment overstates what that step proves
- Evidence:
tests/session-model-override.test.ts:469-471— "the repair pair differs from the pin, so a surviving runtime would keep calling the old one". It would not:entry.commitfiresbridge.onChange, whose candidate loop compares runtime signatures and rebuilds a surviving runtime onto the new pair (src/index.ts:730-747). Therequests[1]assertion therefore passes on both the "disposed at reset" and the "survived, then rebuilt by signature" paths. - Impact: a comment that teaches the next editor a false causal story about the assertion. The guard itself is unaffected — both discriminating assertions (
:466,:467) run before the repair, so the buggy version still fails at the delivery assertion. - Merge class: nit
- Confidence: HIGH
- Fix sketch: reword to "repairing with a different pair proves the new route is served" and drop the claim that this step proves the dispose.
Linked-issue AC
Closes #124 — all six criteria remain met; this increment touches no product behaviour, so the round-2 AC mapping stands unchanged.
✅ Verified
- Read the increment pack (67 diff lines) and both seats opened the product paths it depends on:
disposeRuntime/dispose()abort + queue clear (src/advisor-runtime.ts:592-596), drain continuing past a closed gate (:640-650),requests.pushbefore the await (tests/session-model-override.test.ts:104-108), and delivery routing toinjectfornit(src/delivery.ts:210). - Confirmed both assertions are independently meaningful and fire before the repair step; the
flushtimer (20 ms) cannot mask a late delivery because the release path completes in a microtask. - Confirmed isolation: fresh adapter per case, reviewer marker cleared in
beforeEach,blockNextconsumed by the firststream(). - No tests, build or typecheck were run in this review (read-only). The author's earlier run reports typecheck/build green and 649/649.
🗑️ Considered & rejected
requests.length === 1does not prove the queue array was emptied: rejected — the product-observable behaviour (backlog never reaches the adapter) is what the assertion covers, anddispose()aborts and clears together.flush(20 ms timer) could hide a late delivery: rejected — the released call settles in a microtask; no timer or I/O sits between release and the assertion.- Self-review bias: mitigated by two independent read-only seats asked to judge the increment adversarially; both revalidated the prior findings from the product code rather than the commit message.
🛠️ Plan to fix
Expand fix plan
1. tests/session-model-override.test.ts:469-471 — reword the repair comment:
keep "repairing the global pair builds a fresh runtime on the new route" and
drop the "a surviving runtime would keep calling the old one" claim
(onChange's signature compare would rebuild it too).
2. pnpm test (no product change; targeted file is enough, full suite before push)The comment claimed a surviving runtime would keep calling the old route; the onChange signature compare would rebuild it onto the new pair. The route assertion proves the route, not the reset-time dispose - the two assertions before it do that.
Problem
The advisor has no "configured but opt-in" mode.
enabledis exactly the provider/model gate, and a session with no explicit override is ON (override ?? true). To review only chosen sessions, the only workaround is clearingprovider/model— which also removes the model, so every opt-in must re-pin a full pair with/advisor model setinstead of a single/advisor on.Closes #124.
Change
Add a persisted boolean key
defaultEnabled(defaulttrue) to the §5.1 entry config:resolveAdvisorConfignormalizes it on every path (the model gate is unchanged); it is a new key, not inLEGACY_KEYS, and unknown-key strictness is untouched.defaultEnabled, AND the provider/model gate. An explicit/advisor offkeeps today's "no reason" status; a default-off session reportsdisabled by default — enable it for this session with /advisor on.bridge.onChange): the enable switch is reconciled for tracked sessions — a flip tofalsedisposes their runtimes, a flip totrueapplies to sessions already tracked (a session with no runtime and no override resolves on its next event). The runtime-signature rebuild path is unchanged, anddefaultEnabledis deliberately not part of that signature./settingsboolean,/advisor configreadback (defaultEnabled: on|off), gatewayget/setwire, docs and the frozen spec's §5.1 / §8.6 annotations.Semantics and compatibility
truekeeps every existing profile byte-identical: no stored config change, no behavior change.provider/modelkeeps its own disabled reason./advisor on; the web session-header Advisor action still only pins the model (it has no enable control today), so no new RPC surface is added.Verification
pnpm typecheck— cleanpnpm build— cleanpnpm test— 645/645 (25 files), including new cases for:getcarrying the key and a NON-defaultset({ defaultEnabled: false })round-trip;/advisor configrendering and live edit;defaultEnabled: false+/advisor onopts one session in while the persisted default stays false.composeLiveHarness) for the live cases.