fix(native-chat): restore runtime refusals behind the win32 gate

506d375de3 replaced the project-runtime checks with a bare platform test,
so a WSL or repair-required runtime resolution would no longer refuse
structured chat off-win32. Keep the unconditional win32 refusal and
re-run the runtime resolution after it, so the gate does not depend on
the resolver's own platform guard. Tests inject WSL and repair-required
resolutions on darwin/linux and fail against the regressed gate.
This commit is contained in:
Brennan Benson
2026-08-26 19:51:34 -07:00
parent 506d375de3
commit 5a02a9365c
2 changed files with 77 additions and 1 deletions
@@ -1,5 +1,7 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { AppState } from '@/store/types'
import type * as localPreflightContext from '@/lib/local-preflight-context'
import type { ProjectExecutionRuntimeResolution } from '../../../shared/project-execution-runtime'
import { canUseStructuredNativeChat } from './structured-native-chat-availability'
const { mockGetRendererAppPlatform } = vi.hoisted(() => ({
@@ -10,6 +12,43 @@ vi.mock('@/lib/renderer-app-platform', () => ({
getRendererAppPlatform: mockGetRendererAppPlatform
}))
type GetLocalProjectExecutionRuntimeContext =
typeof localPreflightContext.getLocalProjectExecutionRuntimeContext
const projectRuntimeMock = vi.hoisted(() => ({
fn: vi.fn<GetLocalProjectExecutionRuntimeContext>(),
actual: undefined as GetLocalProjectExecutionRuntimeContext | undefined
}))
vi.mock('@/lib/local-preflight-context', async (importOriginal) => {
const actual = await importOriginal<typeof localPreflightContext>()
projectRuntimeMock.actual = actual.getLocalProjectExecutionRuntimeContext
return { ...actual, getLocalProjectExecutionRuntimeContext: projectRuntimeMock.fn }
})
const wslRuntimeResolution: ProjectExecutionRuntimeResolution = {
status: 'resolved',
runtime: {
kind: 'wsl',
hostPlatform: 'wsl',
projectId: 'repo-1',
distro: 'Ubuntu',
reason: 'project-override',
cacheKey: 'repo-1|wsl|Ubuntu'
}
}
const repairRequiredResolution: ProjectExecutionRuntimeResolution = {
status: 'repair-required',
repair: {
projectId: 'repo-1',
preferredRuntime: { kind: 'wsl', distro: null },
reason: 'wsl-distro-required',
source: 'project-override',
cacheKey: 'repo-1|wsl|repair'
}
}
function stateFor(input: {
connectionId?: string | null
windowsRuntime?: 'windows-host' | 'wsl'
@@ -46,6 +85,13 @@ function stateFor(input: {
describe('canUseStructuredNativeChat', () => {
beforeEach(() => {
mockGetRendererAppPlatform.mockReturnValue('darwin')
projectRuntimeMock.fn.mockReset()
projectRuntimeMock.fn.mockImplementation((...args) => {
if (!projectRuntimeMock.actual) {
throw new Error('real getLocalProjectExecutionRuntimeContext was never captured')
}
return projectRuntimeMock.actual(...args)
})
})
it('allows the structured stack on a local worktree', () => {
@@ -101,4 +147,27 @@ describe('canUseStructuredNativeChat', () => {
} as unknown as AppState
expect(canUseStructuredNativeChat(state, 'folder:folder-1')).toBe(true)
})
it.each(['darwin', 'linux'] as const)('allows a supported local worktree on %s', (platform) => {
mockGetRendererAppPlatform.mockReturnValue(platform)
expect(canUseStructuredNativeChat(stateFor({}), 'wt-1')).toBe(true)
})
it.each(['darwin', 'linux'] as const)(
'refuses a WSL project runtime even when the renderer reports %s',
(platform) => {
mockGetRendererAppPlatform.mockReturnValue(platform)
projectRuntimeMock.fn.mockReturnValue(wslRuntimeResolution)
expect(canUseStructuredNativeChat(stateFor({}), 'wt-1')).toBe(false)
}
)
it.each(['darwin', 'linux'] as const)(
'refuses a repair-required runtime even when the renderer reports %s',
(platform) => {
mockGetRendererAppPlatform.mockReturnValue(platform)
projectRuntimeMock.fn.mockReturnValue(repairRequiredResolution)
expect(canUseStructuredNativeChat(stateFor({}), 'wt-1')).toBe(false)
}
)
})
@@ -1,4 +1,5 @@
import type { AppState } from '@/store/types'
import { getLocalProjectExecutionRuntimeContext } from '@/lib/local-preflight-context'
import { getExecutionHostIdForWorktree } from '@/lib/worktree-runtime-owner'
import { getRendererAppPlatform } from '@/lib/renderer-app-platform'
@@ -13,5 +14,11 @@ export function canUseStructuredNativeChat(state: AppState, worktreeId: string):
// the host advertises that proof, refuse every local Windows execution path —
// windows-host, WSL, and keys that resolve no project runtime (folder
// workspaces, floating terminal) — so create cannot fail after the click.
return getRendererAppPlatform() !== 'win32'
if (getRendererAppPlatform() === 'win32') {
return false
}
// Refuse WSL and repair-required runtimes even if resolution ever runs
// off-win32; the gate must not depend on the resolver's platform guard.
const projectRuntime = getLocalProjectExecutionRuntimeContext(state, worktreeId)
return !(projectRuntime?.status === 'repair-required' || projectRuntime?.runtime.kind === 'wsl')
}