From bb1d4789c4e0540d2b5b1815457d209d2fa92c25 Mon Sep 17 00:00:00 2001 From: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com> Date: Wed, 11 Nov 2026 03:55:39 +0000 Subject: [PATCH] fix(pi): start Pi terminal launches on the model picked in the launcher (#41005) Pi had no launch catalog, so a picked model compiled to no arguments. A launch-only catalog maps it to `pi --model `. A --model in Pi's Arguments stays literal: Pi's CLI fuzzy-matches it, while Pi chats need an exact provider/id. Co-authored-by: lucas-wu-wakakak <8309814+lucas-wu-wakakak@users.noreply.github.com> --- .../choices/agent-choice-args-analysis.ts | 18 +++++---------- .../choices/agent-choice-cli-apply.test.ts | 22 +++++++++++++++++++ .../choices/agent-launch-choices.ts | 22 ++++++++++++++++++- .../tui-agent-startup-session-options.spec.ts | 14 ++++++++++++ .../agent-session-option-catalog-pi.ts | 21 ++++++++++++++++++ .../options/agent-session-option-catalog.ts | 7 +++++- 6 files changed, 90 insertions(+), 14 deletions(-) create mode 100644 src/shared/agent-session/options/agent-session-option-catalog-pi.ts diff --git a/src/shared/agent-launch/choices/agent-choice-args-analysis.ts b/src/shared/agent-launch/choices/agent-choice-args-analysis.ts index 1fa1a11df5d..4fa9dd43a21 100644 --- a/src/shared/agent-launch/choices/agent-choice-args-analysis.ts +++ b/src/shared/agent-launch/choices/agent-choice-args-analysis.ts @@ -12,20 +12,10 @@ import { type AgentStartupShell } from '../startup/tui-agent-startup-shell' import type { TuiAgent } from '../catalog/tui-agent' +import type { ChoiceOccurrence } from './agent-launch-choices' import { agentChoiceConfigString, agentChoiceConfigValue } from './agent-choice-cli-apply' -export type ChoiceOccurrence = { - key: string - value: string - valueType?: 'string' | 'boolean' - source?: boolean - words: string[] - start: number - end: number - lift: boolean - owned: boolean - replacement?: string -} +export type { ChoiceOccurrence } from './agent-launch-choices' /** Every cut uses the original spans and preserves operand roles in its consuming grammar. */ export function analyzeChoiceArgs( @@ -137,6 +127,10 @@ export function analyzeChoiceArgs( (!inline || name.startsWith('--')) && !!value && !value.startsWith('-') + if (form.kind === 'value' && form.recognizes?.(value) === false) { + valueType = undefined + lift = false + } if (form.kind === 'settings') { const effective = effectiveSettings.get(form) if (effective?.index !== index || opaqueKeys.has(key)) { diff --git a/src/shared/agent-launch/choices/agent-choice-cli-apply.test.ts b/src/shared/agent-launch/choices/agent-choice-cli-apply.test.ts index cb4437434a3..58da953f44a 100644 --- a/src/shared/agent-launch/choices/agent-choice-cli-apply.test.ts +++ b/src/shared/agent-launch/choices/agent-choice-cli-apply.test.ts @@ -92,3 +92,25 @@ it.each(['true', '42', '["high"]', '{key="high"}'])( expect(result.userArgs).toBe(userArgs) } ) + +it('keeps a Pi model in Arguments literal so chats never receive a CLI pattern', () => { + const lift = (userArgs: string) => + reconcileAgentChoiceArgsDetailed({ + agent: 'pi', + userArgs, + choices: {}, + mode: 'arguments-edit', + keys + }) + for (const userArgs of [ + '--model sonnet:high', + '--model anthropic/sonnet', + '--model anthropic/claude-opus-5-5', + '--provider openai --model gpt-6' + ]) { + const result = lift(userArgs) + expect(result.recognized).toEqual([]) + expect(result.choices).toEqual({}) + expect(result.userArgs).toBe(userArgs) + } +}) diff --git a/src/shared/agent-launch/choices/agent-launch-choices.ts b/src/shared/agent-launch/choices/agent-launch-choices.ts index 89b0b4767b6..0b720887af8 100644 --- a/src/shared/agent-launch/choices/agent-launch-choices.ts +++ b/src/shared/agent-launch/choices/agent-launch-choices.ts @@ -25,11 +25,31 @@ export function terminalAgentChoices( export type AgentLaunchChoices = Partial> export type AgentChoiceCliForm = - | { kind: 'value'; names: readonly string[]; lift: 'separate-and-long-equals' | 'never' } + | { + kind: 'value' + names: readonly string[] + lift: 'separate-and-long-equals' | 'never' + /** Values outside the choice's grammar stay literal arguments instead of becoming choices. */ + recognizes?: (value: string) => boolean + } | { kind: 'config'; names: readonly string[]; key: string; lift: 'codex-bare-string' | 'never' } | { kind: 'settings'; names: readonly string[]; key: string; lift: 'merged-json' } | { kind: 'recipe'; words: readonly string[]; value: AgentPermissionChoice } +/** One choice flag found in free-form agent arguments. */ +export type ChoiceOccurrence = { + key: string + value: string + valueType?: 'string' | 'boolean' + source?: boolean + words: string[] + start: number + end: number + lift: boolean + owned: boolean + replacement?: string +} + export function normalizeAgentModelOptionChoices( value: unknown ): AgentModelOptionChoices | undefined { diff --git a/src/shared/agent-launch/startup/tui-agent-startup-session-options.spec.ts b/src/shared/agent-launch/startup/tui-agent-startup-session-options.spec.ts index 2a199d505a6..fd4da937bf8 100644 --- a/src/shared/agent-launch/startup/tui-agent-startup-session-options.spec.ts +++ b/src/shared/agent-launch/startup/tui-agent-startup-session-options.spec.ts @@ -41,6 +41,20 @@ describe('tui agent startup session options', () => { expect(plan?.sessionOptions).toEqual({ model: 'zai-coding-plan/glm-5.3-flash' }) }) + it('starts Pi on the picked model in place of its configured one', () => { + const plan = buildAgentStartupPlan({ + agent: 'pi', + prompt: '', + cmdOverrides: {}, + platform: 'linux', + allowEmptyPromptLaunch: true, + sessionOptions: { model: 'anthropic/claude-opus-5-5' }, + sessionOptionsOverrideAgentArgs: true, + agentArgs: '--model openai/gpt-6 --no-skills' + }) + expect(plan?.launchCommand).toBe("pi '--no-skills' '--model' 'anthropic/claude-opus-5-5'") + }) + it('rejects conflicting singleton flags in an agent command override', () => { expect( resolveAgentLaunchCommand({ diff --git a/src/shared/agent-session/options/agent-session-option-catalog-pi.ts b/src/shared/agent-session/options/agent-session-option-catalog-pi.ts new file mode 100644 index 00000000000..bc0af98dbfd --- /dev/null +++ b/src/shared/agent-session/options/agent-session-option-catalog-pi.ts @@ -0,0 +1,21 @@ +import { agentChoiceCliApply } from '../../agent-launch/choices/agent-choice-cli-apply' +import type { AgentSessionOptionCatalog } from './agent-session-option-catalog-types' + +// Why: terminal launches need Pi's `--model`; Pi chats keep applying picks over RPC. +export const PI_LAUNCH_OPTION_CATALOG: AgentSessionOptionCatalog = { + // Pi models depend on configured providers; ids are opaque `provider/id` selectors. + models: [], + modelApply: agentChoiceCliApply( + 'pi', + [ + { + kind: 'value', + names: ['--model'], + lift: 'separate-and-long-equals', + // Pi's CLI fuzzy-matches `--model`; chats need exact ids, so Arguments never become a model choice. + recognizes: () => false + } + ], + '--model' + ) +} diff --git a/src/shared/agent-session/options/agent-session-option-catalog.ts b/src/shared/agent-session/options/agent-session-option-catalog.ts index 8117dfdd1a6..3cac0cfae2e 100644 --- a/src/shared/agent-session/options/agent-session-option-catalog.ts +++ b/src/shared/agent-session/options/agent-session-option-catalog.ts @@ -13,6 +13,7 @@ import { import { GROK_SESSION_OPTION_CATALOG } from './agent-session-option-catalog-grok' import { MUSE_SESSION_OPTION_CATALOG } from './agent-session-option-catalog-muse' import { OMP_SESSION_OPTION_CATALOG } from './agent-session-option-catalog-omp' +import { PI_LAUNCH_OPTION_CATALOG } from './agent-session-option-catalog-pi' import type { AgentSessionOptionCatalog, AgentSessionOptionCatalogMap, @@ -91,5 +92,9 @@ export function sessionOptionValueIsValid(value: unknown): value is SessionOptio export function getAgentSessionOptionLaunchCatalog( agent: AgentType ): AgentSessionOptionCatalog | null { - return agent === 'opencode' ? OPENCODE_LAUNCH_OPTION_CATALOG : getAgentSessionOptionCatalog(agent) + return agent === 'opencode' + ? OPENCODE_LAUNCH_OPTION_CATALOG + : agent === 'pi' + ? PI_LAUNCH_OPTION_CATALOG + : getAgentSessionOptionCatalog(agent) }