Files
orca/src/shared/runtime-environment-store.ts
T
Lesley MurfinandJinwoo Hong 3d013e48e8 fix(windows): slow launch + keystroke stalls — main process synchronously spawns powershell to re-harden env-store ACLs (#4840)
* fix(windows): stop synchronous powershell ACL spawns blocking the main thread

markEnvironmentUsed re-hardened the env-store on every runtime round-trip,
spawning powershell.exe ~5x synchronously (~1-1.5s each) and freezing the
Windows UI on launch and every keystroke. Throttle the non-security lastUsedAt
write (60s window; runtimeId changes bypass) and make path hardening idempotent
per process. ACL hardening is preserved; post-rename credential hardening still
always runs. ~14.0s -> ~6.8s launch; main thread 95.7% blocked -> 99.0% idle.

* Fix secure-file hardening cache retries

---------

Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>
2026-06-07 21:47:54 -04:00

180 lines
5.8 KiB
TypeScript

import { randomUUID } from 'crypto'
import { existsSync, readFileSync } from 'fs'
import { join } from 'path'
import { parsePairingCode, type PairingOffer } from './pairing'
import { hardenExistingSecureFile, writeSecureJsonFile } from './secure-file'
import {
createEnvironmentFromPairingOffer,
getPreferredPairingOffer,
KnownRuntimeEnvironmentSchema,
RuntimeEnvironmentStoreSchema,
type KnownRuntimeEnvironment,
type RuntimeEnvironmentStore
} from './runtime-environments'
const ENVIRONMENTS_FILE = 'orca-environments.json'
export type RuntimeEnvironmentStoreErrorCode = 'invalid_argument' | 'runtime_error'
export class RuntimeEnvironmentStoreError extends Error {
readonly code: RuntimeEnvironmentStoreErrorCode
constructor(code: RuntimeEnvironmentStoreErrorCode, message: string) {
super(message)
this.name = 'RuntimeEnvironmentStoreError'
this.code = code
}
}
export function getEnvironmentStorePath(userDataPath: string): string {
return join(userDataPath, ENVIRONMENTS_FILE)
}
export function listEnvironments(userDataPath: string): KnownRuntimeEnvironment[] {
return readEnvironmentStore(userDataPath).environments
}
export function addEnvironmentFromPairingCode(
userDataPath: string,
args: { name: string; pairingCode: string; now?: number }
): KnownRuntimeEnvironment {
const offer = parsePairingCode(args.pairingCode)
if (!offer) {
throw new RuntimeEnvironmentStoreError(
'invalid_argument',
'Invalid pairing code. Expected an orca://pair?... URL or bare pairing payload.'
)
}
const store = readEnvironmentStore(userDataPath)
const now = args.now ?? Date.now()
const existing = store.environments.find((entry) => entry.name === args.name)
if (existing) {
throw new RuntimeEnvironmentStoreError(
'invalid_argument',
`A server named "${args.name}" already exists.`
)
}
const environment = createEnvironmentFromPairingOffer({
id: randomUUID(),
name: args.name,
now,
offer,
runtimeId: null
})
const next = {
version: 1 as const,
environments: [
...store.environments.filter((entry) => entry.id !== environment.id),
environment
].sort((a, b) => a.name.localeCompare(b.name))
}
writeEnvironmentStore(userDataPath, next)
return environment
}
export function removeEnvironment(userDataPath: string, selector: string): KnownRuntimeEnvironment {
const store = readEnvironmentStore(userDataPath)
const environment = resolveEnvironmentFromStore(store, selector)
writeEnvironmentStore(userDataPath, {
version: 1,
environments: store.environments.filter((entry) => entry.id !== environment.id)
})
return environment
}
export function resolveEnvironment(
userDataPath: string,
selector: string
): KnownRuntimeEnvironment {
return resolveEnvironmentFromStore(readEnvironmentStore(userDataPath), selector)
}
export function resolveEnvironmentPairingOffer(
userDataPath: string,
selector: string
): PairingOffer {
return getPreferredPairingOffer(resolveEnvironment(userDataPath, selector))
}
// Why: markEnvironmentUsed runs on every runtime round-trip; persisting lastUsedAt each
// time forces a secure-file rewrite (ACL hardening), which blocks the main thread on
// Windows. lastUsedAt only needs coarse freshness, so skip writes within this window.
const LAST_USED_PERSIST_INTERVAL_MS = 60_000
export function markEnvironmentUsed(
userDataPath: string,
selector: string,
args: { runtimeId?: string | null; now?: number } = {}
): void {
const store = readEnvironmentStore(userDataPath)
const environment = resolveEnvironmentFromStore(store, selector)
const now = args.now ?? Date.now()
const runtimeIdChanged = args.runtimeId != null && args.runtimeId !== environment.runtimeId
const lastUsedIsFresh =
environment.lastUsedAt != null &&
now >= environment.lastUsedAt &&
now - environment.lastUsedAt < LAST_USED_PERSIST_INTERVAL_MS
if (!runtimeIdChanged && lastUsedIsFresh) {
return
}
const next = store.environments.map((entry) =>
entry.id === environment.id
? {
...entry,
runtimeId: args.runtimeId ?? entry.runtimeId,
lastUsedAt: now,
updatedAt: now
}
: entry
)
writeEnvironmentStore(userDataPath, { version: 1, environments: next })
}
function resolveEnvironmentFromStore(
store: RuntimeEnvironmentStore,
selector: string
): KnownRuntimeEnvironment {
const byId = store.environments.find((entry) => entry.id === selector)
if (byId) {
return byId
}
const matches = store.environments.filter((entry) => entry.name === selector)
if (matches.length === 1) {
return matches[0]!
}
if (matches.length > 1) {
throw new RuntimeEnvironmentStoreError(
'invalid_argument',
`Environment name "${selector}" is ambiguous; use the environment id.`
)
}
throw new RuntimeEnvironmentStoreError('invalid_argument', `Unknown environment: ${selector}`)
}
function readEnvironmentStore(userDataPath: string): RuntimeEnvironmentStore {
const path = getEnvironmentStorePath(userDataPath)
if (!existsSync(path)) {
return { version: 1, environments: [] }
}
try {
hardenExistingSecureFile(path)
const parsed = RuntimeEnvironmentStoreSchema.parse(JSON.parse(readFileSync(path, 'utf8')))
return {
version: 1,
environments: parsed.environments
.map((entry) => KnownRuntimeEnvironmentSchema.parse(entry))
.sort((a, b) => a.name.localeCompare(b.name))
}
} catch {
throw new RuntimeEnvironmentStoreError(
'runtime_error',
`Could not read Orca environments at ${path}; the file is invalid.`
)
}
}
function writeEnvironmentStore(userDataPath: string, store: RuntimeEnvironmentStore): void {
const path = getEnvironmentStorePath(userDataPath)
writeSecureJsonFile(path, RuntimeEnvironmentStoreSchema.parse(store))
}