Files
orca/src/main/runtime/rpc/methods/client-native-chat-settings.test.ts
T
Brennan BensonandMerge Sim 546fd9b21f fix(native-chat): remember structured chat model and effort picks (#19147)
* fix(native-chat): remember structured chat model and effort picks

Structured Claude and Codex sessions already read the saved launch
options at create, but nothing ever wrote them back. The only writer of
`nativeChatSessionOptions` was the PTY picker, and the composer swaps in
the structured surface for structured panes, so a structured pick went
nowhere: it was forgotten when the session ended and every new session
started at the CLI default.

Persist a settled pick from both the desktop and mobile structured
surfaces. Model and effort are stored as a pair, because a launch
resolves a stored effort only under a stored model — so an effort-only
pick adopts the model it was chosen against, otherwise the remembered
effort never reaches a launch at all.

Two things the persist path deliberately avoids: it writes what the
provider committed rather than what was requested, since Codex
reconciles an effort the newly selected model cannot run; and it never
writes the provider readback, which is the CLI's own default and would
pin a `-m` the user never chose.

* fix(native-chat): persist session option picks atomically

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-06 20:28:17 -07:00

79 lines
2.6 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest'
import type { OrcaRuntimeService } from '../../orca-runtime'
import type { RpcRequest } from '../core'
import { RpcDispatcher } from '../dispatcher'
import { CLIENT_UI_METHODS } from './client-ui'
const request = (params: unknown): RpcRequest => ({
id: 'req-1',
authToken: 'tok',
method: 'settings.mutateNativeChatSessionOptions',
params
})
describe('native-chat settings RPC', () => {
it('routes option deltas to the runtime-owned atomic update', async () => {
const updateClientNativeChatSessionOptions = vi.fn()
const runtime = {
getRuntimeId: () => 'test-runtime',
updateClientNativeChatSessionOptions
} as unknown as OrcaRuntimeService
const dispatcher = new RpcDispatcher({ runtime, methods: CLIENT_UI_METHODS })
const mutation = {
type: 'apply-picks' as const,
agent: 'codex' as const,
picks: [
{ modelId: 'gpt-fast', optionId: 'model' as const, value: 'gpt-fast' },
{ modelId: 'gpt-fast', optionId: 'effort' as const, value: 'low' }
]
}
const response = await dispatcher.dispatch(request(mutation))
expect(updateClientNativeChatSessionOptions).toHaveBeenCalledExactlyOnceWith(mutation)
expect(response).toMatchObject({ ok: true, result: { ok: true } })
})
it('rejects malformed option deltas', async () => {
const updateClientNativeChatSessionOptions = vi.fn()
const runtime = {
getRuntimeId: () => 'test-runtime',
updateClientNativeChatSessionOptions
} as unknown as OrcaRuntimeService
const dispatcher = new RpcDispatcher({ runtime, methods: CLIENT_UI_METHODS })
for (const mutation of [
{ type: 'apply-picks', agent: 'codex', picks: [] },
{
type: 'apply-picks',
agent: 'opencode',
picks: [{ modelId: 'model', optionId: 'model', value: 'model' }]
},
{
type: 'apply-picks',
agent: 'codex',
picks: [{ modelId: 'model', optionId: 'arbitrary', value: 'value' }]
},
{
type: 'apply-picks',
agent: 'codex',
picks: [{ modelId: 'model', optionId: 'effort', value: true }]
},
{
type: 'apply-picks',
agent: 'cursor',
picks: [{ modelId: 'model', optionId: 'fastMode', value: 'true' }]
},
{
type: 'clear-model-if-missing',
agent: 'grok',
availableModelIds: []
}
]) {
const response = await dispatcher.dispatch(request(mutation))
expect(response).toMatchObject({ ok: false, error: { code: 'invalid_argument' } })
}
expect(updateClientNativeChatSessionOptions).not.toHaveBeenCalled()
})
})