Read PR description, comments, commits, and changed files.
UnderstandIdentified and reviewed linked issues.
UnderstandAnalyzed PR diff and relevant source files.
UnderstandChecked changelog and documentation compliance.
VerifyPosted review comment, added label, and converted PR to draft.
EditYou are given one or more GitHub PR URLs: https://github.com/badlogic/pi-mono/pull/837
For each PR URL, do the following in order:
packages/*/CHANGELOG.md files. Report whether an entry exists. If missing, state that a changelog entry is required before merge and that you will add it if the user decides to merge. Follow the changelog format rules in AGENTS.md. Verify:
### Breaking Changes, ### Added, ### Fixed, etc.)Fixed foo ([#123](https://github.com/badlogic/pi-mono/pull/123) by [@user](https://github.com/user))### Breaking Changes, not just ### FixedOutput format per PR: PR: Changelog:
If no issues are found, say so under Bad and Ugly.
The user wants me to review a GitHub PR. Let me start by fetching the PR details.
>gh pr view 837 --repo badlogic/pi-mono --json title,body,comments,commits,files,author,state,labels,headRefName,baseRefName>gh pr diff 837 --repo badlogic/pi-monoGood, I have the PR details and diff. Let me read the relevant source files to understand the changes better. Let me also check if there are any linked issues.
From the PR, there are no linked issues mentioned. Let me read the source files to review the changes properly.
Now let me read the relevant source files to understand the context:
Let me read more of the interactive-mode.ts to understand the context better. I need to check the parts that were changed in the PR, specifically around the showModelsSelector function and the cycle model function.
Let me read more of the interactive-mode.ts file to see the cycle model functionality:
Let me check where supportsXhigh is exported from. The diff shows it's imported from @mariozechner/pi-ai. Let me search for it.
>cd /Users/badlogic/workspaces/pi-mono && grep -rn "supportsXhigh\|supports-xhigh\|supportsXhigh" packages/ai/src --include="*.ts" | head -20>cd /Users/badlogic/workspaces/pi-mono && grep -n "export.*supportsXhigh" packages/ai/src/**/*.ts 2>/dev/null || grep -rn "supportsXhigh" packages/ai/src --include="*.ts" | head -20Good, now let me check if supportsXhigh is exported from the index of @mariozechner/pi-ai.
>cd /Users/badlogic/workspaces/pi-mono && grep -n "supportsXhigh" packages/ai/src/index.tsI see, supportsXhigh is not exported from the index, so the PR's imports may not work. Let me check the actual index exports:
>cd /Users/badlogic/workspaces/pi-mono && grep "supportsXhigh" packages/ai/src/index.ts || echo "Not in index.ts"Good, export * from "./models.js"; means it will export everything from models.ts including supportsXhigh. So the import should work.
Now let me look at the test file added to understand what tests exist:
The test file doesn't exist on main yet, it's only in the PR. Let me check if the docs need to be updated.
>cd /Users/badlogic/workspaces/pi-mono && grep -rn "scoped-models\|Ctrl\+P\|Ctrl\+R\|Ctrl\+T" packages/coding-agent/README.md packages/coding-agent/docs/*.md 2>/dev/null | head -30Let me check the README and docs to see if Ctrl+T and Ctrl+R are documented anywhere for the /scoped-models command.
>cd /Users/badlogic/workspaces/pi-mono && grep -B2 -A5 "scoped-models" packages/coding-agent/README.md>cd /Users/badlogic/workspaces/pi-mono && ls packages/coding-agent/docs/Let me also verify if there are pkg labels expected for the PR:
>cd /Users/badlogic/workspaces/pi-mono && grep -rn "enabledModels\|scoped-models\|:level" packages/coding-agent/README.md packages/coding-agent/docs/*.md | head -20The README already mentions the :level suffix format. Let me verify the tests would pass:
>cd /Users/badlogic/workspaces/pi-mono && npm run check 2>&1 | tail -50There are type errors in the current main branch. But I should analyze the PR changes to determine if they pass the type check. Let me see if this PR's changes compile by checking the diff more carefully.
The key changes in the PR are:
scoped-models-selector.ts - new logic for Ctrl+R reset, Ctrl+T thinking overridesinteractive-mode.ts - simplified callbacks, includes provider name in Ctrl+P statusscoped-models-selector.test.tsLet me check if there are any obvious issues with the code:
>cd /Users/badlogic/workspaces/pi-mono && grep -n "ThinkingLevel" packages/agent-core/src/types.ts | head -10Let me find the ThinkingLevel type definition:
>cd /Users/badlogic/workspaces/pi-mono && grep -rn "ThinkingLevel" packages/agent-core/src --include="*.ts" | head -5Let me search for where ThinkingLevel is defined:
>cd /Users/badlogic/workspaces/pi-mono && grep -rn "type ThinkingLevel\|ThinkingLevel =" packages --include="*.ts" | head -10Let me check where ThinkingLevel is defined and imported from:
>cd /Users/badlogic/workspaces/pi-mono && grep -rn "export.*ThinkingLevel\|^export type ThinkingLevel" packages --include="*.ts" | head -10The PR imports ThinkingLevel from @mariozechner/pi-agent-core. Let me check if this is correct:
>cd /Users/badlogic/workspaces/pi-mono && grep -rn "ThinkingLevel" packages/agent-core --include="*.ts" | head -10Let me find the agent-core package:
>cd /Users/badlogic/workspaces/pi-mono && find packages -name "pi-agent-core" -type d 2>/dev/null || ls packagesLet me list the packages:
>cd /Users/badlogic/workspaces/pi-mono && ls -la packages/I see, the packages are agent, ai, etc., not agent-core. Let me check the package names:
>cd /Users/badlogic/workspaces/pi-mono && grep '"name"' packages/agent/package.json packages/ai/package.jsonAh, packages/agent is @mariozechner/pi-agent-core. Let me check if ThinkingLevel is exported:
>cd /Users/badlogic/workspaces/pi-mono && grep -rn "ThinkingLevel" packages/agent/src --include="*.ts" | head -10Good, ThinkingLevel is defined in packages/agent/src/types.ts and should be exported. The import in the PR's code (import type { ThinkingLevel } from "@mariozechner/pi-agent-core";) looks correct.
Now let me analyze the PR changes thoroughly:
CHANGELOG.md changes - The PR adds an entry under ## [Unreleased] with:
### Added: Ctrl+R reset and Ctrl+T thinking overrides### Changed: Provider name in Ctrl+P status, ✓ for enabled modelsscoped-models-selector.ts changes:
ThinkingLevel, Api, supportsXhighThinkingOverrides typegetThinkingCycleLevels(model)ModelItem now includes thinkingOverride fieldModelsConfig interface changed from enabledModelIds: Set<string> + hasEnabledModelsFilter: boolean to enabledIds: EnabledIds + thinkingOverrides: ThinkingOverridesModelsCallbacks interface simplified from many callbacks to just onChange, onPersist, onCancelinitialEnabledIds and initialThinkingOverrides for reset functionalitybuildPatterns() to convert internal state to string patterns with :level suffixesemitChange() helper to notify parentenableAll function changed to not return null when all are enabled (always returns array)Ctrl+T (cycle thinking) and Ctrl+R (reset)Ctrl+A now just clears filter (sets null) instead of enabling all filteredinteractive-mode.ts file has been significantly refactored where the cycled model status now includes the provider name in brackets, and the showModelsSelector method contains complex logic to preserve thinking overrides from settings by matching session scope to settings scope to determine if explicit :level suffixes should be retained, with a new applySessionScope(patterns) function to update the session and a simplified callback structure using just onChange and onPersist.A new test file scoped-models-selector.test.ts covers showing ✓ when all enabled, cycling thinking overrides with Ctrl+T, ignoring Ctrl+T when no filter is active, handling Ctrl+T when all models are explicitly selected, and resetting with Ctrl+R. One potential issue stands out: the enableAll function now always returns an array instead of returning null when all models become enabled, which is a behavioral change since null previously indicated "all enabled, no filter" and thinking overrides only function when a filter is active.
The Ctrl+A handler sets enabledIds to null and clears thinking overrides, representing "all enabled without per-model thinking" rather than the previous behavior of keeping some models disabled. The matching logic for preserving :level suffixes from settings is intricate, comparing session and settings scopes by checking if IDs align in order. The test mocks use api: "anthropic-messages" for both models despite one having provider: "openai" with id gpt-4o, which works but could be clearer.
Looking closer at the Ctrl+A handler, there's a potential issue: it sets enabledIds to null and clears thinkingOverrides, but when persisting via Ctrl+S, this passes buildPatterns() which returns null when enabledIds is null. This could remove any filter and lose thinking level settings the user previously configured. The onPersist callback in interactive-mode.ts needs to handle this case properly.
Let me verify the type imports work correctly:
>cd /Users/badlogic/workspaces/pi-mono && grep -n "export" packages/agent/src/index.ts | head -20export * from "./types.js" means ThinkingLevel is exported. Good.
Now let me check if there are any missing labels for the PR:
>cd /Users/badlogic/workspaces/pi-mono && gh label list --repo badlogic/pi-mono | grep pkgThe PR doesn't have a pkg:coding-agent label but it's modifying packages/coding-agent. Let me finalize my review.
Summary of the PR changes:
:level suffixes[provider] when cycling modelsThe CHANGELOG is properly formatted with ### Added and ### Changed sections. The PR includes tests for the new functionality.
Now let me create my review:
Now let me verify documentation requirements:
>cd /Users/badlogic/workspaces/pi-mono && grep -B2 -A10 "enabledModels" packages/coding-agent/README.md | head -40The README already documents the enabledModels setting format with :level suffixes (e.g., gemini-2.5-pro:high). So there's no additional documentation needed for that.
However, the new /scoped-models UI keybindings (Ctrl+T for thinking, Ctrl+R for reset) are not documented in the README. The README only has a brief mention of /scoped-models in the commands table. We could argue this should be documented in the README since it's new interactive functionality.
Let me finalize my review.
PR: https://github.com/badlogic/pi-mono/pull/837
## [Unreleased] in packages/coding-agent/CHANGELOG.md### Added for new features and ### Changed for modifications- /scoped-models: Ctrl+R resets... ([#837](https://github.com/badlogic/pi-mono/pull/837) by [@CarlosGtrz](https://github.com/CarlosGtrz))onModelToggle, onEnableAll, onClearAll, onToggleProvider, onPersist) with 2 (onChange, onPersist):level suffix format (e.g., model-id:high)pkg:coding-agent labelenabledIds = null). This changes the semantics: pressing Ctrl+A with a search filter used to add those filtered models to the selection, now it clears all filteringshowModelsSelector code that tries to preserve explicit :level suffixes from settings by comparing session scope to settings scope is fragile. If the session scope gets modified (e.g., model order changes), the suffixes are lost and rebuilt from session stateenableAll() previously returned null when all models became enabled (indicating "no filter"). Now it always returns an array. This breaks the convention that null = "all enabled without explicit filter". Code comment says "null = all enabled (no filter)" but the function no longer produces that state/scoped-models?/scoped-models:level suffixes[provider] when cycling models via Ctrl+PonChange callback instead of multiple granular callbacksscoped-models-selector.test.ts with 5 test cases:
:level suffixpackages/coding-agent/README.md or docs (the :level suffix format is already documented)/scoped-models keybindings could be documented, but they're visible in the component's footer hint textpost that on the PR, ask the contributor to adjust their pr, turn the pr into a draft pr
The user wants me to:
Let me do all of these.
>gh pr comment 837 --repo badlogic/pi-mono --body '## PR Review
### Changelog
- **Present**: Yes, properly added under `## [Unreleased]` in `packages/coding-agent/CHANGELOG.md`
- **Format**: Correct. Uses `### Added` for new features and `### Changed` for modifications
- **Attribution**: External contribution, should include PR link and author. Currently missing:
- Should be: `- /scoped-models: Ctrl+R resets... ([#837](https://github.com/badlogic/pi-mono/pull/837) by [@CarlosGtrz](https://github.com/CarlosGtrz))`
### Good
- Clean API simplification: Replaces 5 callbacks (`onModelToggle`, `onEnableAll`, `onClearAll`, `onToggleProvider`, `onPersist`) with 2 (`onChange`, `onPersist`)
- Thinking overrides persist correctly using the existing `:level` suffix format (e.g., `model-id:high`)
- Tests cover key scenarios: checkmarks when all enabled, Ctrl+T cycling, Ctrl+T ignored without filter, Ctrl+R reset
- Footer hint text updated to show new shortcuts (^T thinking, ^R reset)
- Provider name in Ctrl+P status improves UX when multiple providers have similar model names
### Bad
- **Missing PR label**: Should have `pkg:coding-agent` label
- **Changelog attribution missing**: External contribution needs PR link and author per project rules
- **Ctrl+A behavior change**: Previous behavior enabled filtered models; new behavior clears the filter entirely (sets `enabledIds = null`). This changes the semantics: pressing Ctrl+A with a search filter used to add those filtered models to the selection, now it clears all filtering
- **Complex session/settings matching logic**: The `showModelsSelector` code that tries to preserve explicit `:level` suffixes from settings by comparing session scope to settings scope is fragile. If the session scope gets modified (e.g., model order changes), the suffixes are lost and rebuilt from session state
### Ugly
- **enableAll() function semantics changed silently**: The helper function `enableAll()` previously returned `null` when all models became enabled (indicating "no filter"). Now it always returns an array. This breaks the convention that `null` = "all enabled without explicit filter". Code comment says "null = all enabled (no filter)" but the function no longer produces that state
### Questions
- The Ctrl+A change from "enable filtered" to "clear filter entirely" seems intentional for the new thinking override feature (overrides only work with explicit filter). Is this the intended UX change?
- Should the README document the new Ctrl+T and Ctrl+R keybindings in `/scoped-models`?
### Change summary
1. **Ctrl+R reset**: Restores initial model selection and thinking overrides when entering `/scoped-models`
2. **Ctrl+T thinking cycling**: Cycles per-model thinking levels (minimal→low→medium→high→xhigh→off) for enabled models, persisted as `:level` suffixes
3. **Provider in Ctrl+P status**: Shows `[provider]` when cycling models via Ctrl+P
4. **Always show ✓**: Enabled models now always show ✓ checkmark, even in "all enabled" mode
5. **Simplified callbacks**: Component uses single `onChange` callback instead of multiple granular callbacks
### Tests
New test file `scoped-models-selector.test.ts` with 5 test cases covering the new functionality.
---
@CarlosGtrz Thanks for the contribution! Please address the following before we can merge:
1. **Add changelog attribution**: Update the changelog entries to include the PR link and your username (see format above)
2. **Clarify the Ctrl+A behavior change**: Is the change from "enable filtered models" to "clear filter entirely" intentional? If so, please add a note in the PR description explaining the rationale
3. **Consider the `enableAll()` semantics**: The function no longer returns `null` when all models are enabled, which breaks the documented convention. Either restore that behavior or update the code comments to reflect the new semantics
Converting this to draft until these items are addressed.'>gh pr edit 837 --repo badlogic/pi-mono --add-label "pkg:coding-agent">gh pr ready 837 --repo badlogic/pi-mono --undoDone. I've:
pkg:coding-agent labelThe contributor @CarlosGtrz has been asked to:
enableAll() semantics changeDone. I've:
pkg:coding-agentThe contributor has been asked to:
enableAll() function semantics issue