@jopqior/pi-subagents-model-selector
Pi extension that asks for model and thinking before every new pi-subagents run.
Package details
Install @jopqior/pi-subagents-model-selector from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@jopqior/pi-subagents-model-selector- Package
@jopqior/pi-subagents-model-selector- Version
3.0.0- Published
- Oct 4, 2026
- Downloads
- 1,032/mo · 40/wk
- Author
- jopqior
- License
- MIT
- Types
- extension
- Size
- 46.5 KB
- Dependencies
- 0 dependencies · 4 peers
Pi manifest JSON
{
"extensions": [
"./src/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
@jopqior/pi-subagents-model-selector
Ask for model and thinking before every new in-process @jopqior/pi-subagents child session.
Installing or upgrading the core alone does not change selection.
Source history: the repository's git history was restored to the original upstream merge graph. Already-published npm artifacts are unchanged, and artifacts published after that restoration are built from the restored history. See History restoration for what this means for old commit links and existing clones.
Install
Load after @jopqior/pi-subagents.
Pi loads packages in the order they are listed in .pi/settings.json, and this extension registers its provider with the subagents service at load time — so the core must load first.
pi install npm:@jopqior/pi-subagents
pi install npm:@jopqior/pi-subagents-model-selector
Then list those two npm: sources in that order:
{
"packages": [
"npm:@jopqior/pi-subagents",
"npm:@jopqior/pi-subagents-model-selector"
]
}
Or load from a checkout:
{
"packages": [
"../packages/pi-subagents",
"../packages/pi-subagents-model-selector"
]
}
If the core package is absent, the selector's static import fails while modules load, Pi reports the missing @jopqior/pi-subagents module, and the selector is not activated.
If the core is installed but its service is absent because the core loaded too late, initialization throws a configuration error naming the required package, the registration method, and the load order.
If the service is present but lacks registerSpawnSelectionProvider, the same configuration error applies and the selector registers no hooks and does not activate.
Core compatibility
The selector declares @jopqior/pi-subagents as a required peer dependency with a floor of >=1.0.0.
The floor describes the spawn-selection API the selector needs, independently of the core version used for development, so developing against one core version does not move the published range.
The floor has been verified against the published core releases 1.0.0, 1.0.1, 1.0.2, and 2.0.0, and the maintainer verification command below keeps that verification current.
The open upper bound is a maintenance policy rather than a guarantee that every future core major works without a selector release.
Migrating to the peer dependency
This version is breaking: the core moved from an ordinary dependency to a required peer.
Installing only the selector no longer downloads the core, because Pi installs packages with peer resolution disabled.
Install the core explicitly and list it before the selector in .pi/settings.json, exactly as the instructions above show.
Setups that already install and load both packages in that order need no change.
Release policy
Selector changes and compatibility-declaration changes each require a selector release. A core release alone does not.
Maintainer verification
pnpm run verify:core-compatibility in this package reproduces the packed-compatibility checks behind this policy.
It packs the real selector, asserts the packed manifest contract, and repacks an isolated workspace copy across a changed sibling core version to show the public peer range is unchanged.
It installs the selector tarball into disposable consumers with the historical core releases listed above and the selector's legacy host pins.
A separate candidate row packs the actual local core (including its public declarations), installs both local tarballs with Pi 1.0.0 and TypeBox 1.3.27, and checks the installed core's identity and version against its packed manifest.
The candidate host pins do not change the historical rows or the selector's published peer range.
Every positive row type-checks the installed selector source without workspace aliases and loads the core before the selector through the real Pi loader in a fresh process, asserting the service's registration capability and selector lifecycle hooks.
The matrix retains negative missing-package, missing-service, reversed-order and labeled synthetic incompatible-service rows, plus controls rejecting incorrect diagnostics.
Loader registration is not an interactive chooser, cancellation or resume smoke test.
The command needs network access to npmjs.org, builds every fixture in a temporary directory it removes afterwards, and stays out of the Vitest suite.
Behavior
Every new run in an enabled root's in-process tree opens one /model-style form: model, thinking, and Submit.
Foreground tool calls, background tool calls, and SubagentsService.spawn() all go through the same gate.
The operator's pair is applied after ordinary call and config resolution.
It overrides defaults, explicit model/thinking arguments, and locked: values for those two fields only.
Other locked fields are unchanged.
The form has a task identity title, horizontal frame rules, and Model, Thinking, and Submit tabs.
The active tab has a highlighted background.
Model and Thinking use ☐ / ☒ completion boxes: Model starts incomplete and becomes complete only after Enter confirms a highlighted candidate.
The current model is an initial candidate, not a confirmed selection.
The browsing arrow marks the candidate; a checkmark marks the confirmed model, while current and default badges remain informational.
Model and Thinking show the confirmed model separately, including when search or scope hides its row.
Thinking is complete only when an explicitly chosen level is supported by the confirmed model.
Without a confirmed model, Thinking has no choices and Submit is blocked.
A model that supports only off still requires an explicit choice on Thinking.
Browsing rows, filtering, and toggling scope do not change the confirmed model or thinking choice.
Confirming a different model keeps compatible thinking and clears incompatible thinking.
Submit has a distinct review marker (☰), not a completion box.
Model's all scope lists every authenticated available model of the session whose manager is spawning, not a process-global list.
Its scoped scope is ctx.scopedModels intersected with that catalogue and is available only when the intersection is non-empty.
Scope information, Ctrl+S, and configured-provider catalogue notices appear only on Model.
Ctrl+S toggles all/scoped there and does nothing on Thinking or Submit.
When upgrading from the chooser that allowed scope changes on any page, return to Model with Tab or Shift+Tab before pressing Ctrl+S.
Tab moves to the next page; Shift+Tab moves to the previous page, with wraparound and no confirmation or submission. The tab strip's arrows are visual cues, not arrow-key navigation. Left/right edit the search cursor on Model and do nothing on the other pages. Cursor-only movement keeps the highlighted candidate and confirmed pair. Up/down move the highlighted model candidate or thinking row. Enter on Model confirms the candidate and advances to Thinking; Enter on Thinking explicitly chooses a level for the confirmed model and advances to Submit. Submit reviews and submits only the confirmed pair, even if that model is filtered out or outside the current scope. Enter on Model with no matching candidate does nothing, preserving any confirmed pair. Escape cancels from any page.
Concurrent requests are FIFO at the root chooser. A queued run does not open the form until it is admitted. Cancel the form stops that run without creating a workspace or child session.
A missing TUI, a UI that is not yet attached, an empty catalogue, or an invalid selection fails that run explicitly.
Print, JSON, and RPC sessions fail closed — there is no ui.select fallback.
There is no default, timeout, remembered choice, or retry.
Nested children still route to the root UI, including when this package is excluded from them.
A child that also loads this package receives an inherited registration and installs no second queue.
resume does not re-ask.
The parent's active model, thinking level, agent files, and tool-call arguments are not rewritten to carry the choice.
Lifecycle
The extension captures getSubagentsService() once during initialization and registers the provider immediately, before any session_start handler can spawn.
It attaches ctx.ui.custom at session_start when ctx.mode is tui, and closes the chooser at session_shutdown.
A request that arrives before the UI is attached fails closed rather than waiting for a later sequential handler.
Limitations
- Out-of-process children and third-party session factories are not covered.
- The form rebuilds the
/modelexperience without mounting Pi'sModelSelectorComponent(no type-to-filter on the thinking tab, no set-as-default). - Layout stays within the supplied terminal columns using ANSI-aware wrapping and clipping.
Task titles, catalogue notices, review values, and keyboard hints wrap; model rows and model-name details clip.
Narrow tab strips retain the active tab, abbreviating to
M,T, orSwhen needed and dropping decorative arrows or completion boxes at tiny widths. Resizing preserves the active page, highlighted candidate, and confirmed pair; a non-positive width produces blank output. There is no separate height budget, so wrapped content can make the form taller. - Public task status stays
runningwhile waiting. Pending selection is private widget, foreground, and background wording.
License
MIT