feat(native-chat): enable Windows structured sessions

This commit is contained in:
Orca Worker
2026-08-31 14:57:30 -07:00
parent 02a7742406
commit f8e351ebae
6 changed files with 36 additions and 24 deletions
+8 -2
View File
@@ -1,4 +1,5 @@
import { spawn } from 'node:child_process'
import type { ChildProcessHandle, ProcessSpec } from '../../shared/child-process/process-spec'
import { spawnProcess } from '../../shared/child-process/run-process'
import { normalizeHookTrustKeyForLookup } from './config-toml-trust'
import { runCodexAppServerSession, type CodexAppServerInvocation } from './codex-app-server-session'
@@ -105,7 +106,12 @@ function collectHookListings(result: unknown): CodexHookListing[] {
*/
export async function runCodexHookTrustGrantSession(
request: CodexHookTrustGrantRequest,
spawnImpl: typeof spawn = spawn
spawnImpl: (
program: string,
args: string[],
options: Record<string, unknown>
) => ChildProcessHandle = (program, args, options) =>
spawnProcess({ program, args, ...options } as ProcessSpec)
): Promise<CodexHookTrustGrantSessionResult> {
return runCodexAppServerSession(
request.invocation,
+14 -5
View File
@@ -1,4 +1,6 @@
import { spawn, type ChildProcess, type ChildProcessWithoutNullStreams } from 'node:child_process'
import { spawnProcess } from '../../shared/child-process/run-process'
import type { ChildProcessHandle, ProcessSpec } from '../../shared/child-process/process-spec'
import type { ChildProcessWithoutNullStreams } from 'node:child_process'
import { waitForProcessExitUntil } from './codex-process-exit-deadline'
import { stderrIndicatesMissingAppServer } from './codex-app-server-capability-signal'
import { withCliRuntimeOnPath } from '../../shared/node-cli-command-resolution'
@@ -68,11 +70,17 @@ const STDERR_TAIL_MAX_BYTES = 8192
const STDOUT_LINE_MAX_BYTES = 1024 * 1024
export function killCodexAppServerProcessTree(
child: Pick<ChildProcess, 'pid' | 'kill'>,
options: { platform?: NodeJS.Platform; spawnImpl?: typeof spawn } = {}
child: Pick<ChildProcessHandle, 'pid' | 'kill'>,
options: {
platform?: NodeJS.Platform
spawnImpl?: (program: string, args: string[], options: Record<string, unknown>) => ChildProcessHandle
} = {}
): void {
const platform = options.platform ?? process.platform
const spawnImpl = options.spawnImpl ?? spawn
const spawnImpl =
options.spawnImpl ??
((program: string, args: string[], spawnOptions: Record<string, unknown>) =>
spawnProcess({ program, args, ...spawnOptions } as ProcessSpec))
if (platform === 'win32' && child.pid) {
try {
// Why: npm-installed Codex runs behind cmd.exe; killing only that wrapper
@@ -139,7 +147,8 @@ export function isCodexMethodNotFoundError(error: unknown): boolean {
export async function runCodexAppServerSession<T>(
invocation: CodexAppServerInvocation,
body: (rpc: CodexAppServerRpc) => Promise<T>,
spawnImpl: typeof spawn = spawn
spawnImpl: (program: string, args: string[], options: Record<string, unknown>) => ChildProcessHandle =
(program, args, options) => spawnProcess({ program, args, ...options } as ProcessSpec)
): Promise<T> {
// Why: a default-home grant must run against the real ~/.codex, so strip an
// inherited CODEX_HOME (envToDelete) after applying the overlay, not before.
+9 -1
View File
@@ -724,6 +724,7 @@ import {
RUNTIME_CAPABILITIES,
RUNTIME_PROTOCOL_VERSION,
SESSION_TABS_AUTHORITATIVE_INVENTORY_RUNTIME_CAPABILITY,
STRUCTURED_AGENT_SESSION_RUNTIME_CAPABILITY,
TERMINAL_PAIRED_PARKING_RUNTIME_CAPABILITY,
type RuntimeCapability
} from '../../shared/protocol-version'
@@ -732,6 +733,7 @@ import {
listAiVaultSessions
} from '../ai-vault/cached-session-list'
import { configureHostReadableTranscriptPathSources } from '../native-chat/host-readable-transcript-path'
import { isWindowsProcessStartTimeAvailable } from '../windows/windows-process-table'
import { resolveLocalAiVaultSessionTitles } from '../ai-vault/session-title-resolver'
import type { AiVaultListArgs, AiVaultListResult } from '../../shared/ai-vault-types'
import type {
@@ -6619,7 +6621,13 @@ export class OrcaRuntimeService {
(process.env.ORCA_E2E_DISABLE_PAIRED_TERMINAL_PARKING !== '1' ||
capability !== TERMINAL_PAIRED_PARKING_RUNTIME_CAPABILITY) &&
(process.env.ORCA_E2E_DISABLE_AUTHORITATIVE_SESSION_TABS_INVENTORY !== '1' ||
capability !== SESSION_TABS_AUTHORITATIVE_INVENTORY_RUNTIME_CAPABILITY)
capability !== SESSION_TABS_AUTHORITATIVE_INVENTORY_RUNTIME_CAPABILITY) &&
// Structured ownership on Windows requires the native process-table
// creation-time field; an older host must honestly fall back to the
// terminal bridge rather than advertise an unsafe capability.
(capability !== STRUCTURED_AGENT_SESSION_RUNTIME_CAPABILITY ||
process.platform !== 'win32' ||
isWindowsProcessStartTimeAvailable())
)
if (hasOffscreen || hasHeadlessCommands) {
capabilities.push(BROWSER_HEADLESS_RUNTIME_CAPABILITY)
@@ -152,11 +152,9 @@ describe('canUseStructuredNativeChat', () => {
expect(canUseStructuredNativeChat(stateFor({ windowsRuntime: 'wsl' }), 'wt-1')).toBe(false)
})
it('keeps Windows-host projects on the terminal path until native start-time proof is advertised', () => {
it('allows Windows-host projects when structured chat is enabled', () => {
mockGetRendererAppPlatform.mockReturnValue('win32')
expect(canUseStructuredNativeChat(stateFor({ windowsRuntime: 'windows-host' }), 'wt-1')).toBe(
false
)
expect(canUseStructuredNativeChat(stateFor({ windowsRuntime: 'windows-host' }), 'wt-1')).toBe(true)
})
it('refuses a Windows folder workspace even though its key resolves no project runtime', () => {
@@ -166,7 +164,7 @@ describe('canUseStructuredNativeChat', () => {
activeRepoId: null,
activeWorktreeId: null
} as unknown as AppState
expect(canUseStructuredNativeChat(state, 'folder:folder-1')).toBe(false)
expect(canUseStructuredNativeChat(state, 'folder:folder-1')).toBe(true)
})
it('allows a folder workspace on a non-Windows platform', () => {
@@ -1,7 +1,6 @@
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'
export function canUseStructuredNativeChat(state: AppState, worktreeId: string): boolean {
if (state.settings?.experimentalStructuredNativeChat !== true) {
@@ -16,15 +15,8 @@ export function canUseStructuredNativeChat(state: AppState, worktreeId: string):
if (getExecutionHostIdForWorktree(state, worktreeId) !== 'local') {
return false
}
// The shipped Windows process-tree addon may not expose creation time. Until
// 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.
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.
// Refuse WSL and repair-required runtimes; Windows native execution is
// supported when the host advertises the process identity capability.
const projectRuntime = getLocalProjectExecutionRuntimeContext(state, worktreeId)
return !(projectRuntime?.status === 'repair-required' || projectRuntime?.runtime.kind === 'wsl')
}
@@ -50,7 +50,6 @@ src/main/codex-accounts/legacy-wsl-runtime-auth-drain-recovery-script-harness.ts
src/main/codex-accounts/legacy-wsl-runtime-auth-drain-script-harness.ts
src/main/codex-accounts/legacy-wsl-runtime-auth-drain-script-interference-shims.ts
src/main/codex-accounts/service.ts
src/main/codex/codex-app-server-client.ts
src/main/codex/codex-app-server-posix-supervisor.ts
src/main/codex/codex-app-server-session.ts
src/main/codex/codex-state-db-backfill-recovery.ts