Read GitHub issue and related content.
UnderstandAdded changelog entry.
EditRan tests to confirm changes.
VerifyAnalyze GitHub issue(s): https://github.com/badlogic/pi-mono/issues/844
For each issue:
Do NOT implement unless explicitly asked. Analyze and propose only.
Planning GitHub issue view
I'm preparing to implement a GitHub issue viewing feature using Bash.
>gh issue view 844 --json title,body,comments,labels,state{"body":"### What happened?\n\nWhen selecting some models in /scoped-models and persisting the settings, /model selection switches to \"Showing models from --models scope\" and there's seemingly no way to access the full model index again, unless you clear your scoped models.\n\n### Steps to reproduce\n\n1. Go to /scoped-models and press Ctrl+X to make sure to clear any previous preferences.\n2. Select some models in /scoped-models\n3. Press Ctrl+X to persist settings across sessions\n4. Go to /model\n5. See only your selected \"scoped model\" and a hint \"Showing models from --models scope\", without any way to see the full list of models or reset the view\n6. Go to /scoped-models and clear them\n7. Go to /model -- works normally again\n\n### Expected behavior\n\nI think /model should always show all the models available to you, without filtering them by what you have selected in /scoped-models. Or have a switch between these modes of display for any scoped-model maniacs out there.\n\n### Version\n\n0.49.1","comments":[],"labels":[{"id":"LA_kwDOPbFNk88AAAACHWTBxw","name":"bug","description":"Something isn't working","color":"d73a4a"}],"state":"OPEN","title":"If you have saved models in /scoped-models, all other models in /model disappear"}Planning repo exploration
I'm preparing to search key directories like packages/coding-agent and tui to identify relevant files for analysis.
>rg -n "scoped-models|scoped models|scoped_models|scopedModels|models scope|--models" packagespackages/coding-agent/test/fixtures/large-session.jsonl:769:{"type":"message","timestamp":"2025-11-21T01:36:50.931Z","message":{"role":"toolResult","toolCallId":"toolu_011EeHubc2DrKE1tACifgzyB","toolName":"bash","content":[{"type":"text","text":"\tsystemPrompt?: string;\n\tcontinue?: boolean;\n\tresume?: boolean;\n\thelp?: boolean;\n\tmode?: Mode;\n\tnoSession?: boolean;\n\tsession?: string;\n\tmodels?: string[];\n\tmessages: string[];\n}\n\nfunction parseArgs(args: string[]): Args {\n\tconst result: Args = {\n\t\tmessages: [],\n\t};\n\n\tfor (let i = 0; i < args.length; i++) {\n\t\tconst arg = args[i];\n\n\t\tif (arg === \"--help\" || arg === \"-h\") {\n\t\t\tresult.help = true;\n\t\t} else if (arg === \"--mode\" && i + 1 < args.length) {\n\t\t\tconst mode = args[++i];\n\t\t\tif (mode === \"text\" || mode === \"json\" || mode === \"rpc\") {\n\t\t\t\tresult.mode = mode;\n\t\t\t}\n\t\t} else if (arg === \"--continue\" || arg === \"-c\") {\n\t\t\tresult.continue = true;\n\t\t} else if (arg === \"--resume\" || arg === \"-r\") {\n\t\t\tresult.resume = true;\n\t\t} else if (arg === \"--provider\" && i + 1 < args.length) {\n\t\t\tresult.provider = args[++i];\n\t\t} else if (arg === \"--model\" && i + 1 < args.length) {\n\t\t\tresult.model = args[++i];\n\t\t} else if (arg === \"--api-key\" && i + 1 < args.length) {\n\t\t\tresult.apiKey = args[++i];\n\t\t} else if (arg === \"--system-prompt\" && i + 1 < args.length) {\n\t\t\tresult.systemPrompt = args[++i];\n\t\t} else if (arg === \"--no-session\") {\n\t\t\tresult.noSession = true;\n\t\t} else if (arg === \"--session\" && i + 1 < args.length) {\n\t\t\tresult.session = args[++i];\n\t\t} else if (arg === \"--models\" && i + 1 < args.length) {\n\t\t\tresult.models = args[++i].split(\",\").map((s) => s.trim());\n\t\t} else if (!arg.startsWith(\"-\")) {\n\t\t\tresult.messages.push(arg);\n\t\t}\n\t}\n\n\treturn result;\n}\n\nfunction printHelp() {\n\tconsole.log(`${chalk.bold(\"pi\")} - AI coding assistant with read, bash, edit, write tools\n\n${chalk.bold(\"Usage:\")}\n pi [options] [messages...]\n\n${chalk.bold(\"Options:\")}\n --provider <name> Provider name (default: google)\n --model <id> Model ID (default: gemini-2.5-flash)\n --api-key <key> API key (defaults to env vars)\n --system-prompt <text> System prompt (default: coding assistant prompt)\n --mode <mode> Output mode: text (default), json, or rpc\n --continue, -c Continue previous session\n --resume, -r Select a session to resume\n --session <path> Use specific session file\n --no-session Don't save session (ephemeral)\n --models <patterns> Comma-separated model patterns for quick cycling with Ctrl+P\n --help, -h Show this help\n\n${chalk.bold(\"Examples:\")}\n # Interactive mode (no messages = interactive TUI)\n pi\n\n # Single message\n pi \"List all .ts files in src/\"\n\n # Multiple messages\n pi \"Read package.json\" \"What dependencies do we have?\"\n"}],"isError":false,"timestamp":1763689010926}}
packages/coding-agent/test/fixtures/large-session.jsonl:964:{"type":"message","timestamp":"2025-11-21T02:05:23.842Z","message":{"role":"toolResult","toolCallId":"toolu_011S6ShGBcKzNpraY7qbokMS","toolName":"bash","content":[{"type":"text","text":"# Changelog\n\n## [Unreleased]\n\n## [0.7.29] - 2025-11-20\n\n### Improved\n\n- **Read Tool Display**: When the `read` tool is called with offset/limit parameters, the tool execution now displays the line range in a compact format (e.g., `read src/main.ts:100-200` for offset=100, limit=100).\n\n## [0.7.28] - 2025-11-20\n\n### Added\n\n- **Message Queuing**: You can now send multiple messages while the agent is processing without waiting for the previous response to complete. Messages submitted during streaming are queued and processed based on your queue mode setting. Queued messages are shown in a pending area below the chat. Press Escape to abort and restore all queued messages to the editor. Use `/queue` to select between \"one-at-a-time\" (process queued messages sequentially, recommended) or \"all\" (process all queued messages at once). The queue mode setting is saved and persists across sessions. ([#15](https://github.com/badlogic/pi-mono/issues/15))\n\n## [0.7.27] - 2025-11-20\n\n### Fixed\n\n- **Slash Command Submission**: Fixed issue where slash commands required two Enter presses to execute. Now pressing Enter on a slash command autocomplete suggestion immediately submits the command, while Tab still applies the completion for adding arguments. ([#30](https://github.com/badlogic/pi-mono/issues/30))\n- **Slash Command Autocomplete**: Fixed issue where typing a typo then correcting it would not show autocomplete suggestions. Autocomplete now re-triggers when typing or backspacing in a slash command context. ([#29](https://github.com/badlogic/pi-mono/issues/29))\n\n## [0.7.26] - 2025-11-20\n\n### Added\n\n- **Tool Output Expansion**: Press `Ctrl+O` to toggle between collapsed and expanded tool output display. Expands all tool call outputs (bash, read, write, etc.) to show full content instead of truncated previews. ([#31](https://github.com/badlogic/pi-mono/issues/31))\n- **Custom Headers**: Added support for custom HTTP headers in `models.json` configuration. Headers can be specified at both provider and model level, with model-level headers overriding provider-level ones. This enables bypassing Cloudflare bot detection and other proxy requirements. ([#39](https://github.com/badlogic/pi-mono/issues/39))\n\n### Fixed\n\n- **Chutes AI Provider**: Fixed 400 errors when using Chutes AI provider. Added compatibility fixes for `store` field exclusion, `max_tokens` parameter usage, and system prompt role handling. ([#42](https://github.com/badlogic/pi-mono/pull/42) by [@butelo](https://github.com/butelo))\n- **Mistral/Chutes Syntax Error**: Fixed syntax error in merged PR that used `iif` instead of `if`.\n- **Anthropic OAuth Bug**: Fixed bug where `process.env.ANTHROPIC_API_KEY = undefined` set the env var to string \"undefined\" instead of deleting it. Now uses `delete` operator.\n\n## [0.7.25] - 2025-11-20\n\n### Added\n\n- **Model Cycling**: Press `Ctrl+P` to quickly cycle through models. Use `--models` CLI argument to scope to specific models (e.g., `--models claude-sonnet,gpt-4o`). Supports pattern matching and smart version selection (prefers aliases over dated versions). ([#37](https://github.com/badlogic/pi-mono/pull/37) by [@fightbulc](https://github.com/fightbulc))\n\n## [0.7.24] - 2025-11-20\n\n### Added\n\n- **Thinking Level Cycling**: Press `Shift+Tab` to cycle through thinking levels (off → minimal → low → medium → high) for reasoning-capable models. Editor border color changes to indicate current level (gray → blue → cyan → magenta). ([#36](https://github.com/badlogic/pi-mono/pull/36) by [@fightbulc](https://github.com/fightbulc))\n\n## [0.7.23] - 2025-11-20\n\n"}],"isError":false,"timestamp":1763690723836}}
packages/coding-agent/src/main.ts:212: scopedModels: ScopedModel[],
packages/coding-agent/src/main.ts:240: } else if (scopedModels.length > 0 && !parsed.continue && !parsed.resume) {
packages/coding-agent/src/main.ts:241: // Check if saved default is in scoped models - use it if so, otherwise first scoped model
packages/coding-agent/src/main.ts:245: const savedInScope = savedModel ? scopedModels.find((sm) => modelsAreEqual(sm.model, savedModel)) : undefined;
packages/coding-agent/src/main.ts:254: options.model = scopedModels[0].model;
packages/coding-agent/src/main.ts:256: if (!parsed.thinking && scopedModels[0].thinkingLevel) {
packages/coding-agent/src/main.ts:257: options.thinkingLevel = scopedModels[0].thinkingLevel;
packages/coding-agent/src/main.ts:268: if (scopedModels.length > 0) {
packages/coding-agent/src/main.ts:270: options.scopedModels = scopedModels.map((sm) => ({
packages/coding-agent/src/main.ts:429: let scopedModels: ScopedModel[] = [];
packages/coding-agent/src/main.ts:432: scopedModels = await resolveModelScope(modelPatterns, modelRegistry);
packages/coding-agent/src/main.ts:460: scopedModels,
packages/coding-agent/src/main.ts:473: console.error(chalk.red("--api-key requires a model to be specified via --provider/--model or -m/--models"));
packages/coding-agent/src/main.ts:507: if (scopedModels.length > 0 && !settingsManager.getQuietStartup()) {
packages/coding-agent/src/main.ts:508: const modelList = scopedModels
packages/pods/src/commands/pods.ts:76: console.error(chalk.red("ERROR: --models-path is required (or must be extractable from --mount)"));
packages/pods/src/commands/pods.ts:112: let setupCmd = `bash /tmp/pod_setup.sh --models-path '${modelsPath}' --hf-token '${hfToken}' --vllm-api-key '${vllmApiKey}'`;
packages/coding-agent/src/modes/interactive/interactive-mode.ts:82:import { ScopedModelsSelectorComponent } from "./components/scoped-models-selector.js";
packages/coding-agent/src/modes/interactive/interactive-mode.ts:271: this.session.scopedModels.length > 0
packages/coding-agent/src/modes/interactive/interactive-mode.ts:272: ? this.session.scopedModels.map((s) => s.model)
packages/coding-agent/src/modes/interactive/interactive-mode.ts:296: { name: "scoped-models", description: "Enable/disable models for Ctrl+P cycling" },
packages/coding-agent/src/modes/interactive/interactive-mode.ts:1474: if (text === "/scoped-models") {
packages/coding-agent/src/modes/interactive/interactive-mode.ts:2240: const msg = this.session.scopedModels.length > 0 ? "Only one model in scope" : "Only one model available";
packages/coding-agent/src/modes/interactive/interactive-mode.ts:2693: if (this.session.scopedModels.length > 0) {
packages/coding-agent/src/modes/interactive/interactive-mode.ts:2694: return this.session.scopedModels.map((scoped) => scoped.model);
packages/coding-agent/src/modes/interactive/interactive-mode.ts:2712: this.session.scopedModels,
packages/coding-agent/src/modes/interactive/interactive-mode.ts:2745: // Check if session has scoped models (from previous session-only changes or CLI --models)
packages/coding-agent/src/modes/interactive/interactive-mode.ts:2746: const sessionScopedModels = this.session.scopedModels;
packages/coding-agent/src/modes/interactive/interactive-mode.ts:2754: // Use current session's scoped models
packages/coding-agent/src/modes/interactive/interactive-mode.ts:2764: const scopedModels = await resolveModelScope(patterns, this.session.modelRegistry);
packages/coding-agent/src/modes/interactive/interactive-mode.ts:2765: for (const sm of scopedModels) {
packages/coding-agent/src/modes/interactive/interactive-mode.ts:2775: // Helper to update session's scoped models (session-only, no persist)
packages/coding-agent/README.md:303:| `/scoped-models` | Enable/disable models for Ctrl+P cycling |
packages/coding-agent/README.md:370:| Ctrl+P / Shift+Ctrl+P | Cycle models forward/backward (scoped by `--models`) |
packages/coding-agent/README.md:766:2. First from `--models` scope (new sessions only)
packages/coding-agent/README.md:828:| `enabledModels` | Model patterns for cycling. Supports glob patterns (`github-copilot/*`, `*sonnet*`) and fuzzy matching. Same as `--models` CLI flag | - |
packages/coding-agent/README.md:1239:| `--models <patterns>` | Comma-separated patterns for Ctrl+P cycling. Supports glob patterns (e.g., `anthropic/*`, `*sonnet*:high`) and fuzzy matching (e.g., `sonnet,haiku:low`) |
packages/coding-agent/README.md:1291:pi --models sonnet:high,haiku:low
packages/coding-agent/README.md:1294:pi --models "github-copilot/*"
packages/coding-agent/src/core/sdk.ts:95: scopedModels?: Array<{ model: Model<any>; thinkingLevel: ThinkingLevel }>;
packages/coding-agent/src/core/sdk.ts:666: scopedModels: options.scopedModels,
packages/coding-agent/docs/sdk.md:304: scopedModels: [
packages/coding-agent/src/core/settings-manager.ts:70: enabledModels?: string[]; // Model patterns for cycling (same format as --models CLI flag)
packages/coding-agent/src/core/agent-session.ts:88: /** Models to cycle through with Ctrl+P (from --models flag) */
packages/coding-agent/src/core/agent-session.ts:89: scopedModels?: Array<{ model: Model<any>; thinkingLevel: ThinkingLevel }>;
packages/coding-agent/src/core/agent-session.ts:123: /** Whether cycling through scoped models (--models flag) or all available */
packages/coding-agent/src/core/agent-session.ts:165: private _scopedModels: Array<{ model: Model<any>; thinkingLevel: ThinkingLevel }>;
packages/coding-agent/src/core/agent-session.ts:220: this._scopedModels = config.scopedModels ?? [];
packages/coding-agent/src/core/agent-session.ts:541: /** Scoped models for cycling (from --models flag) */
packages/coding-agent/src/core/agent-session.ts:542: get scopedModels(): ReadonlyArray<{ model: Model<any>; thinkingLevel: ThinkingLevel }> {
packages/coding-agent/src/core/agent-session.ts:543: return this._scopedModels;
packages/coding-agent/src/core/agent-session.ts:546: /** Update scoped models for cycling */
packages/coding-agent/src/core/agent-session.ts:547: setScopedModels(scopedModels: Array<{ model: Model<any>; thinkingLevel: ThinkingLevel }>): void {
packages/coding-agent/src/core/agent-session.ts:548: this._scopedModels = scopedModels;
packages/coding-agent/src/core/agent-session.ts:1069: * Uses scoped models (from --models flag) if available, otherwise all available models.
packages/coding-agent/src/core/agent-session.ts:1074: if (this._scopedModels.length > 0) {
packages/coding-agent/src/core/agent-session.ts:1081: if (this._scopedModels.length <= 1) return undefined;
packages/coding-agent/src/core/agent-session.ts:1084: let currentIndex = this._scopedModels.findIndex((sm) => modelsAreEqual(sm.model, currentModel));
packages/coding-agent/src/core/agent-session.ts:1087: const len = this._scopedModels.length;
packages/coding-agent/src/core/agent-session.ts:1089: const next = this._scopedModels[nextIndex];
packages/coding-agent/src/modes/interactive/components/index.ts:20:export { type ModelsCallbacks, type ModelsConfig, ScopedModelsSelectorComponent } from "./scoped-models-selector.js";
packages/pods/src/cli.ts:80: // pi pods setup <name> "<ssh>" [--mount "<mount>"] [--models-path <path>] [--vllm release|nightly|gpt-oss]
packages/pods/src/cli.ts:86: 'Usage: pi pods setup <name> "<ssh>" [--mount "<mount>"] [--models-path <path>] [--vllm release|nightly|gpt-oss]',
packages/pods/src/cli.ts:97: } else if (args[i] === "--models-path" && i + 1 < args.length) {
packages/pods/src/cli.ts:113: // If --mount provided but no --models-path, try to extract path from mount command
packages/pods/scripts/pod_setup.sh:18: --models-path)
packages/coding-agent/CHANGELOG.md:118:- Scoped models (`--models` or `enabledModels`) now remember the last selected model across sessions instead of always starting with the first model in the scope ([#736](https://github.com/badlogic/pi-mono/pull/736) by [@ogulcancelik](https://github.com/ogulcancelik))
packages/coding-agent/CHANGELOG.md:206:- `/scoped-models`: Alt+Up/Down to reorder enabled models. Order is preserved when saving with Ctrl+S and determines Ctrl+P cycling order. ([#676](https://github.com/badlogic/pi-mono/pull/676) by [@thomasmhr](https://github.com/thomasmhr))
packages/coding-agent/CHANGELOG.md:248:- `/scoped-models` command to enable/disable models for Ctrl+P cycling. Changes are session-only by default; press Ctrl+S to persist to settings.json. ([#626](https://github.com/badlogic/pi-mono/pull/626) by [@CarlosGtrz](https://github.com/CarlosGtrz))
packages/coding-agent/CHANGELOG.md:1115:- `enabledModels`: allowlist models in `settings.json` (same format as `--models` CLI)
packages/coding-agent/CHANGELOG.md:1152:- **enabledModels glob patterns**: `--models` and `enabledModels` now support glob patterns like `github-copilot/*` or `*sonnet*`. Previously, patterns were only matched literally or via substring search. ([#337](https://github.com/badlogic/pi-mono/issues/337))
packages/coding-agent/CHANGELOG.md:1386:- **Model selector respects --models scope**: The `/model` command now only shows models specified via `--models` flag when that flag is used, instead of showing all available models. This prevents accidentally selecting models from unintended providers. ([#255](https://github.com/badlogic/pi-mono/issues/255))
packages/coding-agent/CHANGELOG.md:2064:- Added model cycling shortcut: `Ctrl+I` cycles through available models (or scoped models with `-m` flag).
packages/coding-agent/CHANGELOG.md:2072:- Replaced `--models` flag with `-m/--model` supporting multiple values. Specify models as `provider/model@thinking` (e.g., `anthropic/claude-sonnet-4-20250514@high`). Multiple `-m` flags scope available models for the session.
packages/coding-agent/src/core/model-resolver.ts:181: const scopedModels: ScopedModel[] = [];
packages/coding-agent/src/core/model-resolver.ts:212: if (!scopedModels.find((sm) => modelsAreEqual(sm.model, model))) {
packages/coding-agent/src/core/model-resolver.ts:213: scopedModels.push({ model, thinkingLevel });
packages/coding-agent/src/core/model-resolver.ts:231: if (!scopedModels.find((sm) => modelsAreEqual(sm.model, model))) {
packages/coding-agent/src/core/model-resolver.ts:232: scopedModels.push({ model, thinkingLevel });
packages/coding-agent/src/core/model-resolver.ts:236: return scopedModels;
packages/coding-agent/src/core/model-resolver.ts:248: * 2. First model from scoped models (if not continuing/resuming)
packages/coding-agent/src/core/model-resolver.ts:256: scopedModels: ScopedModel[];
packages/coding-agent/src/core/model-resolver.ts:266: scopedModels,
packages/coding-agent/src/core/model-resolver.ts:287: // 2. Use first model from scoped models (skip if continuing/resuming)
packages/coding-agent/src/core/model-resolver.ts:288: if (scopedModels.length > 0 && !isContinuing) {
packages/coding-agent/src/core/model-resolver.ts:290: model: scopedModels[0].model,
packages/coding-agent/src/core/model-resolver.ts:291: thinkingLevel: scopedModels[0].thinkingLevel ?? defaultThinkingLevel ?? "off",
[Showing lines 20-109 of 109 (50.0KB limit). Full output: /var/folders/49/l4171l3n219_xwq30fmyzr8w0000gn/T/pi-bash-1a5046e9a16ff3ed.log]Resolving full file read conflict
The interactive-mode file may exceed the 50KB read limit, so I'll read it in chunks using offset/limit despite the rule, ensuring I capture its entirety before analysis.
Verifying file sizes before full reads
I'm checking file sizes with line counts to determine if full reads without truncation are feasible before deciding how to read large files for analysis.
>wc -l packages/coding-agent/src/modes/interactive/interactive-mode.ts packages/coding-agent/src/core/agent-session.ts packages/coding-agent/src/modes/interactive/components/scoped-models-selector.ts3768 packages/coding-agent/src/modes/interactive/interactive-mode.ts
2349 packages/coding-agent/src/core/agent-session.ts
337 packages/coding-agent/src/modes/interactive/components/scoped-models-selector.ts
6454 totalResolving file read limits
I'm planning to read large files in full by using multiple offset/limit calls despite guidance against offset/limit on full reads, since single reads would truncate; smaller files will be read fully without offsets to comply when possible.
Planning chunked file reads
I'm preparing to read two large files in segments using offset and limit to handle tool line limits, splitting interactive-mode into two parts (1-2000, 2001-3768) and agent-session similarly (1-2000, 2001-2349).
Planning partial file reads
I'm deciding to read the file in two parts—lines 1545 to 3544 first, then 3545 to 3768—by setting appropriate offsets and limits for efficient processing.
Identifying need for agent-session
Reading model selector component
I'm inspecting the model selector component code to understand its structure and functionality for integration in the /model route.
Planning to trace scoped models persistence
I'm identifying that scoped models persist via settings into session.scopedModels, causing UI hints and filtering. I'll check main.ts and model-resolver files fully to locate where scoped models get saved to settings and restored to session, starting by assessing main.ts file size.
>wc -l packages/coding-agent/src/main.ts packages/coding-agent/src/core/model-resolver.ts packages/coding-agent/src/core/settings-manager.ts539 packages/coding-agent/src/main.ts
401 packages/coding-agent/src/core/model-resolver.ts
503 packages/coding-agent/src/core/settings-manager.ts
1443 totalPlanning full reads and settings manager
Identifying needed components
Identifying need for settings manager
Noticing conflation of scoped models
Scoped models from persisted settings are incorrectly restricting the full model list in /model, causing /model to use session.scopedModels as a filter when it should show the full list unless limited by CLI flags; this suggests the need to distinguish cycle scope from selector scope and track scoped model sources separately.
Planning to track scoped model source
I'm proposing to add a session property indicating whether scoped models come from CLI or settings, so /model can show the full list unless scoped by CLI flags, while cycling (Ctrl+P) still respects scoped models from settings; this decouples model selector filtering from persisted scopes and aligns with expected behavior.
>wc -l packages/coding-agent/src/core/sdk.ts683 packages/coding-agent/src/core/sdk.tsIdentifying root cause of persistent scoping
Scoping always applies in /model because session.scopedModels is populated from settings.enabledModels, not just CLI input. The fix will track the source of scoped models—CLI, settings, or session-only—and only apply scoping when explicitly passed via CLI or a dedicated flag. I'll update session creation to signal scope origin and adjust logic accordingly.
Proposing fix to restrict model scoping to CLI
The root issue is that /model and autocomplete filter using session.scopedModels sourced from settings.enabledModels, causing unwanted scoping even without CLI flags. The fix is to track scoped models’ origin in session config—restrict selection only if models come from CLI --models. Interactive mode and ModelSelectorComponent should use this flag to decide whether to filter models or show all. The main change involves passing a scope flag from main.ts into AgentSession, then using it to conditionally apply scoping in interactive features.
Issue 844 analysis
Summary of issue
Saving a scoped model selection persists enabledModels in settings. On the next run, /model only shows those scoped models and displays the --models scope hint. There is no way to view the full model list unless scoped models are cleared.
Root cause
The /scoped-models selector writes enabledModels to settings. On startup, main.ts uses parsed.models ?? settingsManager.getEnabledModels() to resolve scopedModels. That list is passed into the session and used by /model and autocomplete. The model selector and /model candidates always use session.scopedModels when the list is non empty, even if the scope came from settings rather than the CLI flag. This makes /model behave as if --models was provided.
Code path
ScopedModelsSelectorComponent persists to SettingsManager.setEnabledModels()main.ts uses settingsManager.getEnabledModels() to populate scopedModelsInteractiveMode.setupAutocomplete and InteractiveMode.getModelCandidates use session.scopedModelsModelSelectorComponent uses its scopedModels input to filter the model list and show the scoped hintProposed fix
Track the source of scoped models and only restrict /model when scope comes from the CLI --models flag. Scoped models from settings should still be used for Ctrl+P cycling but should not constrain the /model list or /model autocomplete.
Suggested approach
restrictModelSelectionToScopedModels: boolean.main.ts to true only when parsed.models is provided.AgentSession and expose a getter.InteractiveMode to use the flag for /model candidates and for ModelSelectorComponent input.ModelSelectorComponent to show the scoped hint only when the flag is enabled.Affected files
packages/coding-agent/src/main.tspackages/coding-agent/src/core/sdk.tspackages/coding-agent/src/core/agent-session.tspackages/coding-agent/src/modes/interactive/interactive-mode.tspackages/coding-agent/src/modes/interactive/components/model-selector.tsi think we should implement the suggestion by the user. model selector listens for tab key press and that switches between all models or scoped models. but only when scoped models are given.
the tui should show a hint that TAB switches between all and scoped models, and it should show the current model "scope" selection.
Planning toggle implementation
I'm preparing to add toggle state to the model selector for switching between scoped and all models, verifying key handling options to use Tab without conflicting with autocomplete, and searching for existing key match utilities to integrate.
>rg -n "Tab|tab" packages/coding-agent/src/modes/interactive/componentspackages/coding-agent/src/modes/interactive/components/tool-execution.ts:40: * Replace tabs with spaces for consistent rendering
packages/coding-agent/src/modes/interactive/components/tool-execution.ts:42:function replaceTabs(text: string): string {
packages/coding-agent/src/modes/interactive/components/tool-execution.ts:471: const lines = lang ? highlightCode(replaceTabs(output), lang) : output.split("\n");
packages/coding-agent/src/modes/interactive/components/tool-execution.ts:480: .map((line: string) => (lang ? replaceTabs(line) : theme.fg("toolOutput", replaceTabs(line))))
packages/coding-agent/src/modes/interactive/components/tool-execution.ts:519: ? highlightCode(replaceTabs(fileContent), lang)
packages/coding-agent/src/modes/interactive/components/tool-execution.ts:537: .map((line: string) => (lang ? replaceTabs(line) : theme.fg("toolOutput", replaceTabs(line))))
packages/coding-agent/src/modes/interactive/components/session-selector.ts:148: const hint1 = keyHint("tab", "scope") + sep + theme.fg("muted", 're:<pattern> regex · "phrase" exact');
packages/coding-agent/src/modes/interactive/components/session-selector.ts:268: truncateToWidth(" No sessions in current folder. Press Tab to view all.", width, "…"),
packages/coding-agent/src/modes/interactive/components/session-selector.ts:365: if (kb.matches(keyData, "tab")) {
packages/coding-agent/src/modes/interactive/components/footer.ts:9: * Removes newlines, tabs, carriage returns, and other control characters.
packages/coding-agent/src/modes/interactive/components/footer.ts:12: // Replace newlines, tabs, carriage returns with space, then collapse multiple spaces
packages/coding-agent/src/modes/interactive/components/diff.ts:15: * Replace tabs with spaces for consistent rendering.
packages/coding-agent/src/modes/interactive/components/diff.ts:17:function replaceTabs(text: string): string {
packages/coding-agent/src/modes/interactive/components/diff.ts:120: replaceTabs(removed.content),
packages/coding-agent/src/modes/interactive/components/diff.ts:121: replaceTabs(added.content),
packages/coding-agent/src/modes/interactive/components/diff.ts:129: result.push(theme.fg("toolDiffRemoved", `-${removed.lineNum} ${replaceTabs(removed.content)}`));
packages/coding-agent/src/modes/interactive/components/diff.ts:132: result.push(theme.fg("toolDiffAdded", `+${added.lineNum} ${replaceTabs(added.content)}`));
packages/coding-agent/src/modes/interactive/components/diff.ts:137: result.push(theme.fg("toolDiffAdded", `+${parsed.lineNum} ${replaceTabs(parsed.content)}`));
packages/coding-agent/src/modes/interactive/components/diff.ts:141: result.push(theme.fg("toolDiffContext", ` ${parsed.lineNum} ${replaceTabs(parsed.content)}`));Planning consistent keybinding hints
I'm deciding to align the model selector keybinding hints with the tab key approach by using the same key matching method and possibly leveraging the existing keyHint function from keybinding-hints for themed hint display instead of plain text.
Planning scoped model toggle hint
The model selector needs state to track current scope ("scoped" or "all"), defaulting to "scoped" if scoped models exist. I'll add a header line showing the current scope and a Tab hint to switch scopes, only if scoped models are available; otherwise, just show API key hints. The Tab toggle must only activate when scoped models exist, and I'll consider updating autocomplete to reflect scope changes.
Detailing scope toggle implementation
I'm outlining comprehensive changes to ModelSelectorComponent: adding a modelScope state with "scoped" or "all" modes, showing a dynamic header line with current scope and a Tab key hint (using keyHint), and updating model loading and filtering on toggle. The Tab toggle only activates if scoped models exist, and the UI falls back to the existing API key hint otherwise. The plan prioritizes keeping user query and smoothly refreshing models on scope switches.
Replacing hint with dynamic scope header
I'm replacing the static model filtering hint with a dynamic scope header line that reflects the current model scope and shows a Tab toggle hint. The scope line uses theme colors and returns a warning message if no scoped models exist. The loadModels method will select models based on the current scope, and input handling will toggle scope on Tab when scoped models are available.
Verifying if-insertion logic
I'll review the full file to confirm the new if-statement was inserted correctly—before Enter and after up/down—and check whether tab handling before up/down applies. This verification ensures the insertion fits logically within the handleInput block.
Reviewing key input handling logic
The tab key handler runs separately after selectDown without else if, which is acceptable since conditions don't conflict, but could be unified for consistency. Also considering updating scope selection display and clearing error messages when toggling scope, especially when no scoped models are available.
Planning error message reset on scope switch
Error messages from previous scope remain when switching between all and scoped models, causing stale errors to show; I'll clear errorMessage at the start of loading models and when switching to scoped to prevent this.
Clarifying scope line behavior and tab hint
The scope line shows current scope selection only when scoped models exist, and the tab key hint appears then to indicate switching scopes. When no scoped models are available, no scope line or tab hint is shown, aligning with requirements. No code changes or tests are needed.
Summary:
Updated packages/coding-agent/src/modes/interactive/components/model-selector.ts to add Tab-based scope toggling between scoped and all models when scoped models exist, with a scope line that shows current selection and the Tab hint.
Tests not run.
ok, i have a different impl in ../pi-mono-codex/. i want that one, it has better ui in /model. can you investigate and copy this to this impl?
Which file(s) in ../pi-mono-codex should I compare and copy from? Give the paths for the model selector UI implementation.
it's the same file you modified in this clone. the other folder is an alternative clone
Planning cross-repo file read
I need to read a full file from another repo using the read tool before editing, specifying the read path accordingly.
Overwriting file with updated content
Summary:
Replaced packages/coding-agent/src/modes/interactive/components/model-selector.ts with the implementation from /Users/badlogic/workspaces/pi-mono-codex to match the improved /model UI.
Tests not run.
looking good, commit and push, and close the issue with a comment in my tone explaining what we implemented
Preparing final git actions and issue closure
>npm run check> pi-monorepo@0.0.3 check
> biome check --write . && tsgo --noEmit && npm run check -w @mariozechner/pi-web-ui
Checked 406 files in 273ms. No fixes applied.
> @mariozechner/pi-web-ui@0.49.1 check
> biome check --write . && tsc --noEmit && cd example && biome check --write . && tsc --noEmit
Checked 73 files in 39ms. No fixes applied.
Checked 3 files in 7ms. No fixes applied.>git status -sb## main...origin/main [behind 1]
M packages/coding-agent/src/modes/interactive/components/model-selector.tsPreparing to rebase before push
>git pull --rebaseerror: cannot pull with rebase: You have unstaged changes.
error: Please commit or stash them.
Command exited with code 128Committing before pull with rebase
I'm adding and committing changes before pulling with rebase to handle being behind origin, then will push afterward.
>git add packages/coding-agent/src/modes/interactive/components/model-selector.ts(no output)Committing toggle improvement
Committing the fix for the scoped toggle in the /model area with a concise descriptive message.
>git commit -m "fix(coding-agent): improve model scope toggle"Running formatting, linting, and type checking...
> pi-monorepo@0.0.3 check
> biome check --write . && tsgo --noEmit && npm run check -w @mariozechner/pi-web-ui
Checked 406 files in 255ms. No fixes applied.
> @mariozechner/pi-web-ui@0.49.1 check
> biome check --write . && tsc --noEmit && cd example && biome check --write . && tsc --noEmit
Checked 73 files in 38ms. No fixes applied.
Checked 3 files in 8ms. No fixes applied.
✅ All pre-commit checks passed!
[main bae91d51] fix(coding-agent): improve model scope toggle
1 file changed, 90 insertions(+), 39 deletions(-)Pulling latest changes with rebase
>git pull --rebaseRebasing (1/1)
Successfully rebased and updated refs/heads/main.Pushing changes
>git pushTo https://github.com/badlogic/pi-mono.git
f7113401..2217cde7 main -> mainComposing issue close comment
Implemented model scope toggle via Tab key. It shows a scope line and hint, supports scoped list or all, and saves the selection automatically.
>gh issue close 844 -c "Fixed in main. /model now shows a scope line and Tab toggles between all models and scoped models when a scope exists. The list is no longer stuck in scoped-only view unless you choose it."✓ Closed issue badlogic/pi-mono#844 (If you have saved models in /scoped-models, all other models in /model disappear)Summary:
packages/coding-agent/src/modes/interactive/components/model-selector.ts.Tests:
>git pullFrom https://github.com/badlogic/pi-mono
2217cde7..25e7e2d0 main -> origin/main
Updating 2217cde7..25e7e2d0
Fast-forward
packages/coding-agent/CHANGELOG.md | 4 ++++
.../src/modes/interactive/components/settings-selector.ts | 12 ++++++++++++
.../coding-agent/src/modes/interactive/interactive-mode.ts | 4 ++++
3 files changed, 20 insertions(+)
add a changelog entry
Preparing to edit changelog
I'll read the entire changelog file without an offset to ensure I have the full context before making edits.
Adding changelog entry for issue
Checking git status before commit
>git status -sb## main...origin/main
M packages/coding-agent/CHANGELOG.mdCommitting and pushing changes
>git add packages/coding-agent/CHANGELOG.md(no output)>git commit -m "docs(coding-agent): add changelog entry for model scope toggle"Running formatting, linting, and type checking...
> pi-monorepo@0.0.3 check
> biome check --write . && tsgo --noEmit && npm run check -w @mariozechner/pi-web-ui
Checked 406 files in 298ms. No fixes applied.
> @mariozechner/pi-web-ui@0.49.1 check
> biome check --write . && tsc --noEmit && cd example && biome check --write . && tsc --noEmit
Checked 73 files in 39ms. No fixes applied.
Checked 3 files in 8ms. No fixes applied.
✅ All pre-commit checks passed!
[main 6327bfd3] docs(coding-agent): add changelog entry for model scope toggle
1 file changed, 4 insertions(+)>git pushTo https://github.com/badlogic/pi-mono.git
25e7e2d0..6327bfd3 main -> mainSummary:
## [Unreleased] in packages/coding-agent/CHANGELOG.md.Tests: