Browse Source

fix(preset): clarify snapshot-vs-registry semantics + address review P2s

The phrase "applying a preset updates the sidebar snapshot immediately"
was misleading: the snapshot refresh is visual only, but "immediately"
read as the whole preset taking effect at once. Rewrite the wording across
code comments, codemap, and docs to state what actually happens and why:

- Apply persists the preset name and refreshes the on-disk TUI snapshot so
  the sidebar shows the new models. The agent registry is NOT hot-swapped
  mid-session — a reload is required.
- The reason is agent-tree stability: hot-swapping during an active
  conversation could truncate context (a new model may have a smaller
  window), drift prior assistant turns under a changed system prompt, leave
  running subagents referencing stale agent definitions, or shift
  tool/skill availability. Reload-on-switch keeps the active session stable.
- A future path to true in-session switching without reset requires
  upgrading @opencode-ai/plugin (tracked in #799).

Also address two Greptile P2 review items on src/tui-preset.ts:
- desc(): drop the redundant setProp('text'); the text is already inserted
  as a child, matching the text() helper in tui.ts.
- pickTemperature: validate the 0-2 range, not just NaN, matching the UI
  description that promises 0-2.
Qesire 2 weeks ago
parent
commit
dfd35d85d6
4 changed files with 33 additions and 17 deletions
  1. 9 3
      docs/preset-switching.md
  2. 1 1
      src/tools/codemap.md
  3. 10 5
      src/tools/preset-switch.ts
  4. 13 8
      src/tui-preset.ts

+ 9 - 3
docs/preset-switching.md

@@ -22,10 +22,16 @@ the built-in `/models`, so it triggers no LLM turn.
 1. Define named presets in `oh-my-opencode-slim.jsonc` under the `presets`
    field, or create them interactively from the manager
 2. The manager writes preset changes to the user config file
-3. **Apply** writes the `preset` field and refreshes the sidebar
+3. **Apply** persists the preset name and refreshes the sidebar snapshot
+   (visual only — the sidebar shows the new models)
 4. **Reload OpenCode** for the new preset to take effect on the agent registry
-5. The current session is **not** reloaded — this is deliberate, to avoid
-   interrupting the active conversation and destabilizing running subagents
+5. The current session is **not** reloaded — this is deliberate.
+   Hot-swapping the agent tree mid-conversation could truncate context (a
+   new model may have a smaller window), drift prior assistant turns under
+   a changed system prompt, leave running subagents referencing stale agent
+   definitions, or shift tool/skill availability. Reload-on-switch keeps
+   the active session stable. A future path to true in-session switching
+   without reset requires upgrading `@opencode-ai/plugin` (tracked in #799).
 
 ### Level 3 — model and variant selection
 

+ 1 - 1
src/tools/codemap.md

@@ -43,7 +43,7 @@ Each tool is implemented as a factory function that returns a `ToolDefinition` r
 ### State Management
 
 - **Runtime Presets**: Preset state persists across plugin reloads via `runtime-preset.ts`
-- **TUI Integration**: Preset changes update the terminal UI snapshot for immediate feedback
+- **TUI Integration**: Preset changes refresh the terminal UI snapshot (sidebar shows new models); the agent registry takes effect on reload, not mid-session — hot-swapping the agent tree during an active conversation risks context truncation, drifted prior turns, and stale subagent references
 - **Background Jobs**: Task cancellation uses a centralized job board for tracking and cleanup
 
 ## Flow

+ 10 - 5
src/tools/preset-switch.ts

@@ -225,8 +225,10 @@ function resolveFirstModel(
 
 /**
  * Persist the preset name to the user-level config file so it survives
- * restarts. Best-effort: a failure must not abort the switch, because the TUI
- * snapshot update is the immediate user-visible effect.
+ * restarts. Best-effort: a failure must not abort the switch, because the
+ * TUI snapshot refresh is the user-visible effect (the sidebar shows the
+ * new models); the agent registry itself only picks up the change on
+ * reload.
  *
  * Note: this rewrites the file as plain JSON (JSONC comments are not
  * preserved), matching the prior server-side behavior.
@@ -243,13 +245,16 @@ function persistPresetName(directory: string, presetName: string): void {
     persisted.preset = presetName;
     fs.writeFileSync(userConfigPath, `${JSON.stringify(persisted, null, 2)}\n`);
   } catch {
-    // Non-critical: the TUI snapshot is updated regardless.
+    // Non-critical: the TUI snapshot refresh still proceeds, so the
+    // sidebar still reflects the new models.
   }
 }
 
 /**
- * Merge the preset's model/variant overrides into the on-disk TUI snapshot so
- * the sidebar reflects the new models on its next poll.
+ * Merge the preset's model/variant overrides into the on-disk TUI snapshot
+ * so the sidebar reflects the new models on its next poll. This is a
+ * visual refresh only — the agent registry is not hot-swapped; reload
+ * OpenCode for the new models to take effect on the running agents.
  */
 function applyPresetToTuiSnapshot(
   directory: string,

+ 13 - 8
src/tui-preset.ts

@@ -10,17 +10,23 @@
  * so it triggers no LLM turn — same channel as the built-in `/models`.
  *
  * All preset mutations are written to the user-level config file
- * (`oh-my-opencode-slim.json[c]`). Applying a preset writes the `preset`
- * field and the TUI snapshot; a reload is required for the live agent
- * registry to pick it up (the current session is deliberately not
- * interrupted, to avoid destabilizing running subagents).
+ * (`oh-my-opencode-slim.json[c]`). Applying a preset persists the preset
+ * name and refreshes the on-disk TUI snapshot so the sidebar shows the new
+ * models. The agent registry is NOT hot-swapped mid-session — a reload is
+ * required. This is deliberate: hot-swapping the agent tree during an
+ * active conversation could truncate context (a new model may have a
+ * smaller window), drift prior assistant turns under a changed system
+ * prompt, leave running subagents referencing stale agent definitions, or
+ * shift tool/skill availability underfoot. Reload-on-switch keeps the
+ * active session stable. A future path to true in-session switching
+ * without reset requires upgrading `@opencode-ai/plugin` (tracked in #799).
  */
 import type {
   TuiDialogSelectOption,
   TuiPluginApi,
 } from '@opencode-ai/plugin/tui';
 import type { JSX } from '@opentui/solid';
-import { createElement, insert, setProp } from '@opentui/solid';
+import { createElement, insert } from '@opentui/solid';
 import type { AgentOverrideConfig, Preset } from './config';
 import { ALL_AGENT_NAMES } from './config/constants';
 import { loadPluginConfig } from './config/loader';
@@ -37,7 +43,6 @@ import { readTuiSnapshot } from './tui-state';
 /** Build a `<text>` JSX element — required for DialogPrompt.description(). */
 function desc(text: string): JSX.Element {
   const node = createElement('text');
-  setProp(node, 'text', text);
   insert(node, text);
   return node as JSX.Element;
 }
@@ -674,11 +679,11 @@ function pickTemperature(
           const next: AgentOverrideConfig = { ...current };
           if (trimmed) {
             const parsed = Number(trimmed);
-            if (Number.isNaN(parsed)) {
+            if (Number.isNaN(parsed) || parsed < 0 || parsed > 2) {
               state.api.ui.toast({
                 variant: 'warning',
                 title: 'Invalid temperature',
-                message: 'Temperature must be a number.',
+                message: 'Temperature must be a number between 0 and 2.',
               });
               pickTemperature(state, presetName, working, agentName, current);
               return;