mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 08:03:20 +00:00
fix(windows): refuse structured chat in a paired web client
Reverts the two review-loop commits (restoring a tree byte-identical to the validated head) and closes the hole they were aiming at, without their cost. A paired web client's `platform` describes the browser's machine, not the host that will run the agent, so the Windows gate cannot be evaluated there. Before this, a browser on macOS driving a Windows runtime read "not win32", skipped the creation-time proof entirely and allowed structured chat — fail-OPEN, the dangerous direction, bypassing the guarantee this lane is built on. `isWebClient` is a required input like the other gate fields, so the compiler enumerated all seven call sites. Refusal is synchronous and fail-closed: no async round-trip, no null window, no cache to invalidate — unlike keying on an asynchronously-fetched host platform, which would have made every desktop launch wait on a round-trip to fix a paired-web-only hole. Paired web therefore gets the legacy chat until the host publishes eligibility itself; that is the proper fix and belongs in its own PR. Ablation-proven: removing the guard reddens both refusal tests; the desktop-unaffected test is a preservation check and passes either way. Gates: tc 0, oxlint 0. Known open: repos-onboarding-folder-startup.test.ts fails on this branch and passes on plain main — under investigation, NOT caused by this commit.
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
||||
type LinkedWorkItemSummary
|
||||
} from '@/lib/new-workspace'
|
||||
import { seedNativeChatLaunchDraftForAgentTab } from '@/lib/agent-launch-prompt-delivery'
|
||||
import { isWebClientLocation } from '@/lib/web-client-location'
|
||||
import {
|
||||
pathUsesWslUnc,
|
||||
readWindowsProcessStartTimeGate
|
||||
@@ -157,6 +158,7 @@ export async function submitFolderWorkspaceCreate({
|
||||
platform: CLIENT_PLATFORM,
|
||||
hostCapabilities: readLocalRuntimeCapabilities(),
|
||||
windowsProcessStartTime: readWindowsProcessStartTimeGate(),
|
||||
isWebClient: isWebClientLocation(),
|
||||
// The workspace has no store entry yet, but it will be created under
|
||||
// the group's parent, so the parent decides WSL-ness before the click.
|
||||
worktreeUsesWslPath: pathUsesWslUnc(projectGroup.parentPath),
|
||||
|
||||
@@ -32,6 +32,7 @@ import { useCallback } from 'react'
|
||||
import type { PendingSmartGitHubSubmitResolution } from './source-selection-decisions'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { settleComposerSubmit } from '@/lib/composer-submit-cancellation'
|
||||
import { isWebClientLocation } from '@/lib/web-client-location'
|
||||
import { readWindowsProcessStartTimeGate } from '@/lib/agent-launch-routing-windows-gate'
|
||||
import { toFolderWorkspaceLinkedTask } from '@/components/sidebar/folder-workspace-composer-helpers'
|
||||
import { CLIENT_PLATFORM, ensureAgentStartupInTerminal } from '@/lib/new-workspace'
|
||||
@@ -144,6 +145,7 @@ export function useFullCreationExecution(input: FullCreationExecutionInput) {
|
||||
platform: CLIENT_PLATFORM,
|
||||
hostCapabilities: readLocalRuntimeCapabilities(),
|
||||
windowsProcessStartTime: readWindowsProcessStartTimeGate(),
|
||||
isWebClient: isWebClientLocation(),
|
||||
// The workspace has no store entry until after this decision, so the
|
||||
// WSL-UNC check cannot run yet; it applies on the next launch.
|
||||
worktreeUsesWslPath: false,
|
||||
|
||||
@@ -36,6 +36,7 @@ import { useCallback } from 'react'
|
||||
import type { Repo } from '../../../../shared/repo-types'
|
||||
import type { TuiAgent } from '../../../../shared/tui-agent'
|
||||
import type { WorktreeCreationRequest } from '@/lib/pending-worktree-creation'
|
||||
import { isWebClientLocation } from '@/lib/web-client-location'
|
||||
import { readWindowsProcessStartTimeGate } from '@/lib/agent-launch-routing-windows-gate'
|
||||
import { useAppStore } from '@/store'
|
||||
import { settleComposerSubmit } from '@/lib/composer-submit-cancellation'
|
||||
@@ -210,6 +211,7 @@ export function useQuickCreationExecution(input: QuickCreationExecutionInput) {
|
||||
platform: CLIENT_PLATFORM,
|
||||
hostCapabilities: readLocalRuntimeCapabilities(),
|
||||
windowsProcessStartTime: readWindowsProcessStartTimeGate(),
|
||||
isWebClient: isWebClientLocation(),
|
||||
// The workspace has no store entry until after this decision, so the
|
||||
// WSL-UNC check cannot run yet; it applies on the next launch.
|
||||
worktreeUsesWslPath: false,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { AppState } from '@/store/types'
|
||||
import { worktreeUsesWslPath } from '@/store/terminals/terminal-workspace-routing'
|
||||
import { isWslUncPath } from '../../../shared/wsl-paths'
|
||||
import { isWebClientLocation } from '@/lib/web-client-location'
|
||||
import {
|
||||
getCachedWindowsTerminalCapabilities,
|
||||
hasCachedWindowsTerminalCapabilities
|
||||
@@ -47,9 +48,14 @@ export function pathUsesWslUnc(path: string | null | undefined): boolean {
|
||||
export function readWindowsStructuredGateInputs(
|
||||
state: Partial<Pick<AppState, 'folderWorkspaces' | 'worktreesByRepo'>>,
|
||||
worktreeId: string
|
||||
): { windowsProcessStartTime: WindowsProcessStartTimeGate; worktreeUsesWslPath: boolean } {
|
||||
): {
|
||||
windowsProcessStartTime: WindowsProcessStartTimeGate
|
||||
worktreeUsesWslPath: boolean
|
||||
isWebClient: boolean
|
||||
} {
|
||||
return {
|
||||
windowsProcessStartTime: readWindowsProcessStartTimeGate(),
|
||||
worktreeUsesWslPath: readWorktreeUsesWslPath(state, worktreeId)
|
||||
worktreeUsesWslPath: readWorktreeUsesWslPath(state, worktreeId),
|
||||
isWebClient: isWebClientLocation()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ function route(overrides: Partial<Parameters<typeof resolveAgentLaunchRoute>[0]>
|
||||
platform: 'darwin',
|
||||
hostCapabilities: [STRUCTURED_AGENT_SESSION_RUNTIME_CAPABILITY],
|
||||
windowsProcessStartTime: 'unavailable' as const,
|
||||
isWebClient: false,
|
||||
worktreeUsesWslPath: false,
|
||||
workspaceKind: 'git-worktree',
|
||||
nativeChatTranscriptIsLocalReadable: true,
|
||||
@@ -176,3 +177,27 @@ describe('resolveAgentLaunchRoute Windows structured gate', () => {
|
||||
).toBe('legacy-native-chat')
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveAgentLaunchRoute paired web client', () => {
|
||||
// A web client's `platform` is the browser's machine, not the host that runs
|
||||
// the agent, so the Windows gate cannot be evaluated. Refusing is the only
|
||||
// safe answer until the host publishes eligibility itself.
|
||||
it('refuses structured chat in a paired web client', () => {
|
||||
expect(route({ isWebClient: true })).toBe('legacy-native-chat')
|
||||
})
|
||||
|
||||
it('still refuses when the web client looks fully eligible on Windows', () => {
|
||||
expect(
|
||||
route({
|
||||
isWebClient: true,
|
||||
platform: 'win32',
|
||||
windowsProcessStartTime: 'available',
|
||||
worktreeUsesWslPath: false
|
||||
})
|
||||
).toBe('legacy-native-chat')
|
||||
})
|
||||
|
||||
it('leaves the desktop renderer unaffected', () => {
|
||||
expect(route({ isWebClient: false })).toBe('structured-native-chat')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -36,6 +36,11 @@ export type AgentLaunchRoutingInput = {
|
||||
* the distro's runtime and must keep the legacy terminal. Required for the
|
||||
* same reason. */
|
||||
worktreeUsesWslPath: boolean
|
||||
/** True when this renderer is a paired web client. Its `platform` then
|
||||
* describes the browser's machine, not the host that will run the agent, so
|
||||
* the Windows gate below cannot be evaluated and must refuse rather than
|
||||
* guess. Lifting this needs host-published eligibility, not a client probe. */
|
||||
isWebClient: boolean
|
||||
workspaceKind?: 'git-worktree' | 'folder' | 'floating'
|
||||
projectRuntime?: ProjectExecutionRuntimeResolution | null
|
||||
promptDelivery?: NativeChatLaunchPromptDelivery
|
||||
@@ -106,6 +111,7 @@ export function resolveAgentLaunchRoute(input: AgentLaunchRoutingInput): AgentLa
|
||||
input.platform !== 'win32' ||
|
||||
(input.windowsProcessStartTime === 'available' && !input.worktreeUsesWslPath)
|
||||
const structuredSupported =
|
||||
!input.isWebClient &&
|
||||
input.agent === 'codex' &&
|
||||
input.promptDelivery !== 'draft' &&
|
||||
input.workspaceKind !== 'floating' &&
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { buildAgentStartupPlan } from '@/lib/tui-agent-startup'
|
||||
import { isWebClientLocation } from '@/lib/web-client-location'
|
||||
import { readWindowsProcessStartTimeGate } from '@/lib/agent-launch-routing-windows-gate'
|
||||
import { tuiAgentToAgentKind } from '@/lib/telemetry'
|
||||
import { isTuiAgentEnabled } from '../../../shared/tui-agent-selection'
|
||||
@@ -139,6 +140,7 @@ export function resolveDismissedOnboardingFolderAgentLaunch(args: {
|
||||
platform: getClientPlatform(),
|
||||
hostCapabilities: readLocalRuntimeCapabilities(),
|
||||
windowsProcessStartTime: readWindowsProcessStartTimeGate(),
|
||||
isWebClient: isWebClientLocation(),
|
||||
// The workspace has no store entry until after this decision, so the
|
||||
// WSL-UNC check cannot run yet; it applies on the next launch.
|
||||
worktreeUsesWslPath: false,
|
||||
|
||||
Reference in New Issue
Block a user