fix(native-chat): a remembered effort a saved Pi listing lacks no longer refuses new chats (carries #31576) (#33637)

New native-chat Pi sessions are no longer refused before Pi starts when a remembered effort (e.g. xhigh) isn't listed in Orca's saved `pi --list-models` row: a remembered effort the row doesn't list moves to the nearest listed level (explicit --model/--effort stay strict), and the remembered preference itself is left intact. Pi clamps unsupported levels itself.

Fixes #31493
Supersedes #31576

Co-authored-by: 王浩生 <22971846+hualong1009@users.noreply.github.com>
This commit is contained in:
Neil
2026-10-25 14:21:53 -07:00
committed by GitHub
co-authored by 王浩生
parent e26e4185bc
commit a6c450718a
3 changed files with 131 additions and 1 deletions
@@ -6,6 +6,7 @@ import type {
AgentSessionRecord
} from '../../shared/agent-session-record'
import type { AgentSessionModelOption } from '../../shared/agent-session-wire'
import { nearestAgentEffort } from './session-tabs/structured-launch-seed-listing'
export function replaceAgentSessionRecordOptions(
record: AgentSessionRecord,
@@ -47,6 +48,35 @@ export function agentSessionOptionAdmitted(
)
}
/** Fit a remembered seed to its model's saved row: an effort the row lacks moves to the nearest listed
* level, other unlisted values drop; keys the caller named this launch keep strict admission. */
export function seedOptionsAdmittedOnSavedListing(
provider: string,
seed: Readonly<Record<string, string>>,
models: AgentSessionAdmissionModels,
explicit: ReadonlySet<string>
): Record<string, string> | undefined {
const admitted: Record<string, string> = {}
for (const [key, value] of Object.entries(seed)) {
if (explicit.has(key) || agentSessionOptionAdmitted(provider, models, seed.model, key, value)) {
admitted[key] = value
continue
}
const row = models?.find((entry) => entry.id === seed.model)
const nearest =
key === 'effort' && row
? nearestAgentEffort(
value,
row.efforts.map((choice) => choice.value)
)
: undefined
if (nearest) {
admitted[key] = nearest
}
}
return Object.keys(admitted).length > 0 ? admitted : undefined
}
export function assertAgentSessionOptionsAdmitted(
provider: string,
models: AgentSessionAdmissionModels,
@@ -0,0 +1,89 @@
import { expect, it, vi } from 'vitest'
import type { AgentSessionAttachParams } from '../../../native-chat/agent-session-wire/structured-agent-session-attach'
import type { StructuredAgentSessionHost } from '../../../native-chat/agent-session-wire/structured-agent-session-host'
import type { OrcaRuntimeService } from '../../orca-runtime'
import { prepareStructuredAgentSessionCreateForWorktree } from './structured-agent-session-create'
const listing = (models: unknown[]) => ({ origin: 'probe', models })
async function prepareWithSeed(
seed: Readonly<Record<string, string>> | undefined,
options: Readonly<Record<string, string>> | undefined,
listingModels: unknown[] | null
): Promise<AgentSessionAttachParams['options']> {
const attachParams = {
envelope: {
sessionId: 'pi_session_1',
clientOperationId: 'operation-1',
expectedRuntimeFence: null,
payloadFingerprint: ''
},
location: {
executionHostId: 'local',
wslDistro: null,
workspaceId: 'workspace-1',
workspaceKind: 'git-worktree'
},
provider: 'pi',
agent: 'pi',
accountHome: { variable: 'HOME', path: '/home/user' },
runtimeKind: 'native' as const,
...(seed ? { options: seed } : {})
}
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the stub implements only the members prepare reaches.
const host = {
deps: {
modelCatalog: listingModels ? { read: vi.fn(async () => listing(listingModels)) } : undefined
}
} as unknown as StructuredAgentSessionHost
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the stub implements only the members prepare reaches.
const runtime = {
resolveStructuredAgentSessionCreateIntent: vi.fn(async () => attachParams),
resolveStructuredAgentSessionLocalWorkspacePath: vi.fn(async () => '/work/repo')
} as unknown as OrcaRuntimeService
const prepared = await prepareStructuredAgentSessionCreateForWorktree({
runtime,
ensureHost: async () => host,
envelope: attachParams.envelope,
worktree: 'id:workspace-1',
agent: 'pi',
caller: { callerKey: 'caller-1' },
...(options ? { options } : {})
})
return prepared.attachParams.options
}
const piRow = (efforts: readonly string[]) => ({
id: 'some/model',
label: 'Some Model',
isDefault: false,
efforts: efforts.map((value) => ({ value, label: value }))
})
it('moves a remembered effort the saved probe row lacks to its nearest listed level', async () => {
// Pi's live menu offers xhigh where its --list-models probe stops at high.
const options = await prepareWithSeed({ model: 'some/model', effort: 'xhigh' }, undefined, [
piRow(['off', 'minimal', 'low', 'medium', 'high'])
])
expect(options).toEqual({ model: 'some/model', effort: 'high' })
})
it('drops a remembered effort its saved row no longer admits', async () => {
const options = await prepareWithSeed({ model: 'some/model', effort: 'off' }, undefined, [
piRow([])
])
expect(options).toEqual({ model: 'some/model' })
})
it('keeps an effort this launch named itself, even one its row does not admit', async () => {
// Explicit options replace the saved selection; only their own values stay the caller's picks.
const options = await prepareWithSeed({ model: 'some/model' }, { effort: 'off' }, [piRow([])])
expect(options).toEqual({ effort: 'off' })
})
it('keeps a remembered effort when no saved rows name its model', async () => {
const options = await prepareWithSeed({ model: 'some/model', effort: 'off' }, undefined, [
{ id: 'other/model', label: 'Other Model', isDefault: false, efforts: [] }
])
expect(options).toEqual({ model: 'some/model', effort: 'off' })
})
@@ -35,6 +35,7 @@ import { ORCA_LAYOUT_OWNER_IS_MAIN } from '../../../../shared/layout-owner-switc
import { structuredAgentSessionClosedBeforeCreate } from './structured-agent-session-close-before-create'
import { structuredLaunchSeedOnSavedListing } from '../../session-tabs/structured-launch-seed-listing'
import type { ChatTabPlacement } from '../../agent-session/agent-session-tab-layout'
import { seedOptionsAdmittedOnSavedListing } from '../../agent-session-record-options'
export type PreparedStructuredAgentSessionCreate = {
host: StructuredAgentSessionHost
@@ -165,13 +166,23 @@ export async function prepareStructuredAgentSessionCreateForWorktree(args: {
await catalogFolder()
)
: undefined
// Fit remembered values to the saved row instead of refusing the create (#31493).
const admittedSeed =
options && catalogModels && !args.options?.model
? seedOptionsAdmittedOnSavedListing(
resolved.provider,
options,
catalogModels,
new Set(Object.keys(args.options ?? {}))
)
: options
return {
host,
...(catalogModels ? { catalogModels } : {}),
...(hostLaunchDirectory ? { hostLaunchDirectory } : {}),
attachParams: {
...resolvedAttach,
...(options ? { options } : {}),
...(admittedSeed ? { options: admittedSeed } : {}),
...(args.tabId ? { surfaceTabId: args.tabId } : {}),
provider: resolved.provider,
agent: resolved.agent,