Files
orca/tests/e2e/runtime-environment-selection-affinity.unit.test.ts
T
Brennan Benson 2e38249a7d Keep a chat's accepted model options in what it shows and sends (#31208)
* Keep a chat's accepted pick in what it shows and sends

A pick held while a new chat is created is committed into the chat's
active option record once accepted, through the same step a normal pick
takes, and older options reads are dropped. The host also publishes the
chat's saved effort, Fast and speed as optional status fields so another
device's pick moves the option stamp and re-reads options.

* Keep chat picks ordered and capture displayed send selection

* fix: keep chat choices in one controller with accepted input capture

* fix: pass captured Codex model input to the turn request

* Keep structured chat option pickers usable during turns

* Preserve logical selection ownership across runtime transitions

* Consolidate selection lifecycle contract coverage

* Consolidate mobile structured send imports

* Preserve legacy action outcomes and opening message identity

* Consolidate repeated selection test setup and assertions

* Fix option reset admission and preserve uncertain write notices

* fix(chat): complete option contract fixture types

* test(chat): place affinity integration in cross-layer suite
2026-10-29 14:18:23 -07:00

188 lines
7.1 KiB
TypeScript

import { mkdtempSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, beforeEach, expect, it, vi } from 'vitest'
import * as environmentStore from '../../src/shared/runtime-environment-store'
import {
RUNTIME_PROTOCOL_VERSION,
MIN_COMPATIBLE_RUNTIME_CLIENT_VERSION
} from '../../src/shared/protocol-version'
import { callRuntimeEnvironment } from '../../src/main/ipc/runtime-environments/runtime-environment-transport-routing'
import {
structuredSessionSelectionController,
forgetStructuredSessionSelection,
registerStructuredSelectionCatalogStore
} from '@/lib/structured-agent-session-selection-controller'
import { replaceRuntimeEnvironmentRevisions } from '@/runtime/runtime-environment-revision'
import { callRuntimeRpc } from '@/runtime/runtime-rpc-client'
import { resetRuntimeEnvironmentStatusOwners } from '../../src/main/ipc/runtime-environments/runtime-environment-request-connections'
import { pairingCode } from '../../src/main/ipc/runtime-environments/runtime-environments-ipc-test-harness'
const { sendRemoteRuntimeRequestMock, getPathMock } = vi.hoisted(() => ({
sendRemoteRuntimeRequestMock: vi.fn(),
getPathMock: vi.fn()
}))
vi.mock('electron', () => ({
BrowserWindow: { getAllWindows: () => [] },
app: { getPath: getPathMock },
ipcMain: { handle: vi.fn(), on: vi.fn(), removeHandler: vi.fn(), removeAllListeners: vi.fn() }
}))
vi.mock('../../src/shared/remote-runtime/remote-runtime-client', () => ({
sendRemoteRuntimeRequest: sendRemoteRuntimeRequestMock,
subscribeRemoteRuntimeRequest: vi.fn()
}))
vi.mock(
'../../src/main/ipc/runtime-environments/runtime-environment-request-connections',
async () => {
const { withRuntimeStatusOwners } =
await import('../../src/main/ipc/runtime-environments/runtime-environments-ipc-test-harness')
return withRuntimeStatusOwners({
sendRemoteRuntimeConnectionRequest: vi.fn(),
sendRemoteRuntimeSharedControlRequest: vi.fn(),
subscribeRemoteRuntimeSharedControlRequest: vi.fn(),
getRemoteRuntimeSharedControlDiagnostics: vi.fn(() => null),
reconnectRemoteRuntimeSharedControlConnection: vi.fn(),
retryRemoteRuntimeSharedControlConnectionsNow: vi.fn(),
ensureRemoteRuntimeSharedControlConnection: vi.fn(),
pauseRemoteRuntimeSharedControlRetry: vi.fn(),
closeRemoteRuntimeRequestConnection: vi.fn()
})
}
)
let userDataPath: string
beforeEach(() => {
userDataPath = mkdtempSync(join(tmpdir(), 'orca-selection-affinity-'))
getPathMock.mockReturnValue(userDataPath)
})
afterEach(() => {
vi.restoreAllMocks()
resetRuntimeEnvironmentStatusOwners()
vi.unstubAllGlobals()
rmSync(userDataPath, { recursive: true, force: true })
})
it('fences deferred selection at the real queue and follows only proved pairing continuity, including late memory', async () => {
const original = environmentStore.addEnvironmentFromPairingCode(userDataPath, {
name: 'selection-owner',
pairingCode: pairingCode(),
now: 1
})
const target = { kind: 'environment', environmentId: original.id } as const
replaceRuntimeEnvironmentRevisions([original])
vi.stubGlobal('window', {
api: {
runtimeEnvironments: {
call: (args: {
selector: string
method: string
params?: unknown
expectedEnvironmentPairingRevision?: number
}) =>
callRuntimeEnvironment(
userDataPath,
args.selector,
args.method,
args.params,
undefined,
args.expectedEnvironmentPairingRevision
)
}
}
})
const restoreCatalog = registerStructuredSelectionCatalogStore({
read: () => ({ runtimeEnvironmentCatalogHydrated: true }),
subscribe: () => () => {}
})
try {
const owner = structuredSessionSelectionController(target, 'selection-queue', 'codex')
const release = Promise.withResolvers<void>()
sendRemoteRuntimeRequestMock.mockImplementation(async (_connection, method) => {
if (method === 'files.read') {
await release.promise
}
return {
id: method,
ok: true,
result:
method === 'status.get'
? {
runtimeId: 'host',
runtimeProtocolVersion: RUNTIME_PROTOCOL_VERSION,
minCompatibleRuntimeClientVersion: MIN_COMPATIBLE_RUNTIME_CLIENT_VERSION
}
: {},
_meta: { runtimeId: 'host' }
}
})
const occupying = Array.from({ length: 8 }, () =>
callRuntimeEnvironment(userDataPath, original.id, 'files.read', {})
)
await vi.waitFor(() =>
expect(
sendRemoteRuntimeRequestMock.mock.calls.filter(([, method]) => method === 'files.read')
).toHaveLength(8)
)
const sendPick = (options: { expectedEnvironmentPairingRevision?: number }) =>
callRuntimeRpc(
target,
'agentSession.setOption',
{},
{ ...options, skipCompatibilityCheck: true }
)
const queued = owner.request(sendPick)
const changed = environmentStore.updateEnvironmentFromPairingCode(userDataPath, original.id, {
pairingCode: pairingCode(),
now: 2
})
replaceRuntimeEnvironmentRevisions([changed], {
retired: [],
sameHost: [],
pending: [{ id: original.id, fromRevision: 1 }]
})
release.resolve()
await Promise.all(occupying)
await expect(queued).rejects.toMatchObject({ code: 'runtime_environment_changed' })
expect(
sendRemoteRuntimeRequestMock.mock.calls.some(
([, method]) => method === 'agentSession.setOption'
)
).toBe(false)
expect(structuredSessionSelectionController(target, 'selection-queue', 'codex')).toBe(owner)
const accepted = owner.issue('model', 'gpt-5.4', null)
owner.settle(accepted, { kind: 'accepted', options: { model: 'gpt-5.4' } })
// Memory starts with the original revision too; the pending replacement cannot receive it.
await Promise.resolve()
expect(
sendRemoteRuntimeRequestMock.mock.calls.some(
([, method]) => method === 'settings.mutateNativeChatSessionOptions'
)
).toBe(false)
replaceRuntimeEnvironmentRevisions([changed], {
retired: [],
sameHost: [{ id: original.id, fromRevision: 1, toRevision: changed.pairingRevision! }]
})
await owner.request(sendPick)
const later = owner.issue('model', 'gpt-5.3-codex', null)
owner.settle(later, { kind: 'accepted', options: { model: 'gpt-5.3-codex' } })
await vi.waitFor(() =>
expect(
sendRemoteRuntimeRequestMock.mock.calls.filter(
([, method]) => method === 'settings.mutateNativeChatSessionOptions'
)
).toHaveLength(1)
)
forgetStructuredSessionSelection('selection-queue', target)
const count = sendRemoteRuntimeRequestMock.mock.calls.length
await expect(
owner.request((options) => callRuntimeRpc(target, 'agentSession.setOption', {}, options))
).rejects.toThrow('retired')
expect(sendRemoteRuntimeRequestMock).toHaveBeenCalledTimes(count)
} finally {
forgetStructuredSessionSelection('selection-queue', target)
replaceRuntimeEnvironmentRevisions([])
restoreCatalog()
}
})