fix(wsl): reconcile hook relays on startup

This commit is contained in:
OrcaWin
2026-08-09 22:20:46 -07:00
parent d9e84d88ef
commit 4dc45cb233
12 changed files with 376 additions and 30 deletions
@@ -310,6 +310,9 @@ async function main() {
const { ensureWslHookRelayForReattach } = await jiti.import(
'../../src/main/agent-hooks/wsl-hook-relay-reattach.ts'
)
const { reconcileWslHookRelaysOnStartup } = await jiti.import(
'../../src/main/agent-hooks/wsl-hook-relay-startup-reconciliation.ts'
)
const { codexHookService } = await jiti.import('../../src/main/codex/hook-service.ts')
const { MANAGED_AGENT_HOOK_TARGETS } = await jiti.import(
'../../src/shared/managed-agent-hook-targets.ts'
@@ -457,6 +460,38 @@ async function main() {
delivered = 0
manager = await createManager('after-restart-token')
const startupDistros = []
const startupStartedAt = performance.now()
await reconcileWslHookRelaysOnStartup({
platform: 'win32',
listLiveProcesses: async () => [
{
id: survivingPty.id,
cwd: guestHome,
title: 'shell',
wslDistro: distro
}
],
ensureForDistro: async (startupDistro) => {
startupDistros.push(startupDistro)
await manager.ensureForDistroReady(startupDistro)
}
})
const startupRelayReadyMs = performance.now() - startupStartedAt
if (startupDistros.length !== 1 || startupDistros[0] !== distro) {
throw new Error(
`Startup inventory did not reconcile '${distro}': [${startupDistros.join(', ')}]`
)
}
const refreshedEndpoint = await waitFor('startup relay endpoint rewrite', async () => {
const contents = await readGuestFile(distro, endpointPath)
const parsed = contents ? parseEndpoint(contents) : null
return parsed && parsed.token === 'after-restart-token' ? parsed : null
})
const immediateHook = await invokeHook(distro, scriptPath, endpointPath)
await waitFor('immediate startup hook delivery', async () => (delivered >= 1 ? true : null))
// Reattach the surviving WSL PTY through main's real spawn path — no direct helper call.
// Why delta: only the reattach phase should record ensureForDistro; length alone can false-
// green if an earlier phase already refreshed (or if a future hooks-on change reintroduces it).
@@ -473,12 +508,8 @@ async function main() {
`PTY reattach did not refresh the relay for '${distro}': before=${refreshesBeforeReattach} all=[${relayRefreshes.join(', ')}]`
)
}
const refreshedEndpoint = await waitFor('reattached relay endpoint rewrite', async () => {
const contents = await readGuestFile(distro, endpointPath)
const parsed = contents ? parseEndpoint(contents) : null
return parsed && parsed.token === 'after-restart-token' ? parsed : null
})
delivered = 0
await invokeHook(distro, scriptPath, endpointPath)
await waitFor('refreshed hook warmup delivery', async () => (delivered >= 1 ? true : null))
delivered = 0
@@ -495,8 +526,11 @@ async function main() {
const result = {
distro,
endpointPath,
endpointPortAfterReattach: refreshedEndpoint.port,
endpointPortAfterStartup: refreshedEndpoint.port,
reattachedPtyId: reattachedPty.id,
startupDistros,
startupRelayReadyMs: Number(startupRelayReadyMs.toFixed(1)),
immediateHookMs: Number(immediateHook.elapsedMs.toFixed(1)),
relayRefreshes,
reattachRefreshes,
stale,
+1
View File
@@ -109,6 +109,7 @@
"bench:macos-computer-helper-owner-loss": "node config/scripts/macos-computer-helper-owner-loss-benchmark.mjs",
"bench:startup": "pnpm run ensure:electron-runtime && node tests/tools/benchmarks/startup-time-bench.mjs",
"bench:daemon-coldstart": "pnpm run ensure:electron-runtime && node tests/tools/benchmarks/daemon-coldstart-bench.mjs",
"bench:wsl-hook-relay-startup": "pnpm run build:relay && node config/scripts/wsl-hook-relay-reattach-benchmark.mjs",
"bench:wsl-hook-relay-reattach": "pnpm run build:relay && node config/scripts/wsl-hook-relay-reattach-benchmark.mjs",
"bench:wsl-git-shell": "node config/scripts/wsl-git-shell-benchmark.mjs",
"bench:hang-watchdog-memory": "pnpm run ensure:electron-runtime && node config/scripts/hang-watchdog-memory-benchmark.mjs",
+13 -12
View File
@@ -16,6 +16,7 @@ import {
type WslRelayStartupFailure
} from './wsl-hook-relay-sentinel'
import { addOrcaWslInteropEnv } from '../pty/wsl-orca-env'
import { wslHookRelayStateKey } from './wsl-hook-relay-state-key'
import {
WSL_HOOK_RELAY_BUNDLE_NAME,
WSL_HOOK_RELAY_DIR,
@@ -140,14 +141,8 @@ export function spawnWslRelayProcess(
})
}
/** True when the distro shows in `wsl --list --running`. Listing does NOT
* boot anything — unlike `wsl -d`, which starts a stopped distro. The
* restart timer must check this so relay recovery never resurrects a VM the
* user shut down with `wsl --shutdown`. Fails CLOSED (false) on probe
* errors: booting a VM the user shut down is worse than a skipped restart
* (the next WSL PTY spawn re-ensures), and a wsl.exe too wedged to list
* distros would not have launched the relay anyway. */
export function isWslDistroRunning(distro: string): Promise<boolean> {
/** One non-booting snapshot; null fails closed when WSL cannot report authority. */
export function listRunningWslDistros(): Promise<string[] | null> {
return new Promise((resolve) => {
execFile(
'wsl.exe',
@@ -157,20 +152,26 @@ export function isWslDistroRunning(distro: string): Promise<boolean> {
{ env: { ...process.env, WSL_UTF8: '1' }, timeout: 10_000, windowsHide: true },
(err, stdout) => {
if (err) {
resolve(false)
resolve(null)
return
}
const wanted = distro.trim().toLowerCase()
const running = decodeWslText(String(stdout))
.split(/\r?\n/)
.map((line) => line.trim().toLowerCase())
.map((line) => line.trim())
.filter(Boolean)
resolve(running.includes(wanted))
resolve(running)
}
)
})
}
/** Recovery must not resurrect a distro the user stopped with `wsl --shutdown`. */
export async function isWslDistroRunning(distro: string): Promise<boolean> {
const running = await listRunningWslDistros()
const wanted = wslHookRelayStateKey(distro)
return running?.some((candidate) => wslHookRelayStateKey(candidate) === wanted) ?? false
}
export function runWslInstallProcess(
distro: string,
script: string,
@@ -267,6 +267,27 @@ describe('WslHookRelayManager', () => {
manager.disposeAll()
})
it('exposes first-attempt readiness for startup ordering', async () => {
let releaseInstall: (() => void) | undefined
const installHooks = vi.fn(
() => new Promise<never[]>((resolve) => (releaseInstall = () => resolve([])))
)
const { manager, deps } = createManager({ installHooks })
let ready = false
const firstAttempt = manager.ensureForDistroReady('Ubuntu').then(() => {
ready = true
})
await vi.waitFor(() => expect(deps.installHooks).toHaveBeenCalledTimes(1))
expect(ready).toBe(false)
releaseInstall?.()
await firstAttempt
expect(manager.getGuestEndpointFilePath('Ubuntu')).not.toBeNull()
expect(ready).toBe(true)
manager.disposeAll()
})
it('ships the OpenCode plugin to the guest and exposes the overlay dir', async () => {
const { manager } = createManager({})
manager.ensureForDistro('Ubuntu')
@@ -82,10 +82,15 @@ export class WslHookRelayManager {
/** Fire-and-forget from every WSL PTY spawn-env build; errors breadcrumb. */
ensureForDistro(distro: string | null): void {
void this.ensureForDistroReady(distro)
}
/** Awaitable first attempt for the startup gate; failures retain timer-driven recovery. */
async ensureForDistroReady(distro: string | null): Promise<void> {
if (this.disposed || !isWslHookRelayAllowed(this.deps)) {
return
}
void this.ensureInternal(distro).catch((err) => {
await this.ensureInternal(distro).catch((err) => {
const detail = err instanceof Error ? err.message : String(err)
this.deps.warn(`[agent-hooks] WSL hook relay ensure failed: ${detail}`)
})
@@ -17,7 +17,8 @@ describe('ensureWslHookRelayForReattach', () => {
['native reattach', { isReattach: true, wslDistro: null }, null],
['legacy reattach without ownership context', { isReattach: true }, null],
['blank distro', { isReattach: true, wslDistro: ' ' }, null],
['SSH reattach', { isReattach: true, wslDistro: 'Ubuntu' }, 'ssh-1']
['SSH reattach', { isReattach: true, wslDistro: 'Ubuntu' }, 'ssh-1'],
['relay reattach', { isReattach: true, wslDistro: 'Ubuntu' }, 'relay-1']
])('does not refresh a %s', (_label, result, connectionId) => {
const ensure = vi.fn()
@@ -0,0 +1,127 @@
import { describe, expect, it, vi } from 'vitest'
import type { PtyProcessInfo } from '../providers/pty-process-info'
import { reconcileWslHookRelaysOnStartup } from './wsl-hook-relay-startup-reconciliation'
function session(id: string, wslDistro?: string | null): PtyProcessInfo {
return {
id,
cwd: '',
title: 'shell',
...(wslDistro !== undefined ? { wslDistro } : {})
}
}
describe('reconcileWslHookRelaysOnStartup', () => {
it('groups surviving panes by distro and awaits one relay per distro', async () => {
const events: string[] = []
const releases = new Map<string, () => void>()
const ensureForDistro = vi.fn(
(distro: string) =>
new Promise<void>((resolve) => {
events.push(`ensure:${distro}`)
releases.set(distro, resolve)
})
)
const listRunningDistros = vi.fn(async () => ['Ubuntu', 'Debian'])
const reconciliation = reconcileWslHookRelaysOnStartup({
platform: 'win32',
listLiveProcesses: async () => [
session('pane-1', 'Ubuntu'),
session('pane-2', 'ubuntu'),
session('pane-3', 'Debian')
],
listRunningDistros,
ensureForDistro
}).then(() => events.push('ready'))
await vi.waitFor(() => expect(events).toEqual(['ensure:Ubuntu', 'ensure:Debian']))
releases.get('Ubuntu')?.()
await Promise.resolve()
expect(events).not.toContain('ready')
releases.get('Debian')?.()
await reconciliation
expect(events).toEqual(['ensure:Ubuntu', 'ensure:Debian', 'ready'])
expect(listRunningDistros).toHaveBeenCalledOnce()
})
it('ignores native panes, blank values, and old daemons without WSL metadata', async () => {
const ensureForDistro = vi.fn(async () => {})
await reconcileWslHookRelaysOnStartup({
platform: 'win32',
listLiveProcesses: async () => [
session('native', null),
session('old-daemon'),
session('blank', ' ')
],
listRunningDistros: vi.fn(async () => ['Ubuntu']),
ensureForDistro
})
expect(ensureForDistro).not.toHaveBeenCalled()
})
it('does not infer desired distros from unavailable inventory', async () => {
const ensureForDistro = vi.fn(async () => {})
await reconcileWslHookRelaysOnStartup({
platform: 'win32',
listLiveProcesses: async () => null,
listRunningDistros: vi.fn(async () => ['Ubuntu']),
ensureForDistro
})
expect(ensureForDistro).not.toHaveBeenCalled()
})
it('does not query local daemon inventory on native hosts', async () => {
const listLiveProcesses = vi.fn(async () => [session('remote', 'Ubuntu')])
await reconcileWslHookRelaysOnStartup({ platform: 'linux', listLiveProcesses })
await reconcileWslHookRelaysOnStartup({ platform: 'darwin', listLiveProcesses })
expect(listLiveProcesses).not.toHaveBeenCalled()
})
it('does not boot a distro that stopped after the live inventory was captured', async () => {
const ensureForDistro = vi.fn(async () => {})
await reconcileWslHookRelaysOnStartup({
platform: 'win32',
listLiveProcesses: async () => [session('survivor', 'Ubuntu')],
listRunningDistros: async () => [],
ensureForDistro
})
expect(ensureForDistro).not.toHaveBeenCalled()
})
it('fails closed when the running-distro snapshot is unavailable', async () => {
const ensureForDistro = vi.fn(async () => {})
await reconcileWslHookRelaysOnStartup({
platform: 'win32',
listLiveProcesses: async () => [session('survivor', 'Ubuntu')],
listRunningDistros: async () => null,
ensureForDistro
})
expect(ensureForDistro).not.toHaveBeenCalled()
})
it('uses live ownership for folder workspace sessions without parsing their ids', async () => {
const ensureForDistro = vi.fn(async () => {})
await reconcileWslHookRelaysOnStartup({
platform: 'win32',
listLiveProcesses: async () => [session('folder-workspace@@pane', 'Ubuntu-24.04')],
listRunningDistros: async () => ['Ubuntu-24.04'],
ensureForDistro
})
expect(ensureForDistro).toHaveBeenCalledWith('Ubuntu-24.04')
})
})
@@ -0,0 +1,56 @@
import type { PtyProcessInfo } from '../providers/pty-process-info'
import { listLiveDaemonProcesses } from '../daemon/daemon-init'
import { wslHookRelayManager } from './wsl-hook-relay-manager'
import { wslHookRelayStateKey } from './wsl-hook-relay-state-key'
import { listRunningWslDistros } from './wsl-hook-relay-launch'
type StartupReconciliationDeps = {
platform: NodeJS.Platform
listLiveProcesses: () => Promise<PtyProcessInfo[] | null>
listRunningDistros: () => Promise<string[] | null>
ensureForDistro: (distro: string) => Promise<void>
}
const defaultDeps: StartupReconciliationDeps = {
platform: process.platform,
listLiveProcesses: listLiveDaemonProcesses,
listRunningDistros: listRunningWslDistros,
ensureForDistro: (distro) => wslHookRelayManager.ensureForDistroReady(distro)
}
export async function reconcileWslHookRelaysOnStartup(
deps: Partial<StartupReconciliationDeps> = {}
): Promise<void> {
const io = { ...defaultDeps, ...deps }
if (io.platform !== 'win32') {
return
}
const processes = await io.listLiveProcesses()
if (!processes) {
return
}
const distros = new Map<string, string>()
for (const process of processes) {
if (typeof process.wslDistro !== 'string') {
continue
}
const distro = process.wslDistro.trim()
const key = wslHookRelayStateKey(distro)
if (distro && !distros.has(key)) {
distros.set(key, distro)
}
}
if (distros.size === 0) {
return
}
const runningSnapshot = await io.listRunningDistros()
if (!runningSnapshot) {
return
}
const runningKeys = new Set(runningSnapshot.map(wslHookRelayStateKey))
await Promise.all(
[...distros].flatMap(([key, distro]) =>
runningKeys.has(key) ? [io.ensureForDistro(distro)] : []
)
)
}
+8 -2
View File
@@ -59,6 +59,7 @@ import {
} from '../claude-accounts/live-pty-gate'
import { parseDaemonReadyIdentity, readDaemonProcessIncarnation } from './daemon-ready-identity'
import type { DaemonEndpointIdentity } from './daemon-hello-protocol'
import type { PtyProcessInfo } from '../providers/pty-process-info'
// Why: daemon init runs concurrent with window load, so an in-process t timestamp (not harness stderr timing) measures cold-start.
function logDaemonMilestone(event: string, details: Record<string, unknown> = {}): void {
@@ -1081,7 +1082,7 @@ export async function getCurrentDaemonMacTccAttributionHealth(): Promise<MacDaem
}
/** Returns null unless every daemon generation supplied an authoritative inventory. */
export async function listLiveDaemonPtyIds(): Promise<string[] | null> {
export async function listLiveDaemonProcesses(): Promise<PtyProcessInfo[] | null> {
if (!adapter) {
return null
}
@@ -1096,10 +1097,15 @@ export async function listLiveDaemonPtyIds(): Promise<string[] | null> {
return null
}
return inventories.flatMap((inventory) =>
inventory.status === 'fulfilled' ? inventory.value.map((process) => process.id) : []
inventory.status === 'fulfilled' ? inventory.value : []
)
}
export async function listLiveDaemonPtyIds(): Promise<string[] | null> {
const processes = await listLiveDaemonProcesses()
return processes?.map((process) => process.id) ?? null
}
// Why: keep the module-level adapter and ipc/pty.ts's localProvider in sync so app-quit can't dispose a stale reference.
export function replaceDaemonProvider(newAdapter: DaemonProvider): void {
adapter = newAdapter
+13
View File
@@ -35,6 +35,7 @@ import {
initDaemonPtyProvider,
disconnectDaemon,
getDaemonProvider,
listLiveDaemonProcesses,
listLiveDaemonPtyIds,
shutdownDaemon
} from './daemon/daemon-init'
@@ -249,6 +250,7 @@ import { agentHookServer, type AgentHookProviderSessionIdentity } from './agent-
import { createHookProviderSessionInvalidator } from './agent-hooks/hook-provider-session-invalidation'
import { createHookStatusSessionTabsInvalidator } from './agent-hooks/hook-status-session-tabs-invalidation'
import { wslHookRelayManager } from './agent-hooks/wsl-hook-relay-manager'
import { reconcileWslHookRelaysOnStartup } from './agent-hooks/wsl-hook-relay-startup-reconciliation'
import { maybeAutoRenameBranchOnFirstWork } from './agent-hooks/first-work-branch-rename'
import { rememberBranchRenameFailureOutput } from './agent-hooks/branch-rename-failure-output'
import { renameWorktreeFolderOnFirstWork } from './agent-hooks/first-work-folder-rename'
@@ -940,6 +942,14 @@ function startTerminalRuntimeStartupServices(): Promise<void> {
})
logStartupMilestone('startup-service-done', { service: 'agent-hook-server' })
},
reconcileWslHookRelays: async () => {
if (!isAgentStatusHooksEnabled(store?.getSettings())) {
return
}
logStartupMilestone('startup-service-start', { service: 'wsl-hook-relay-reconciliation' })
await reconcileWslHookRelaysOnStartup({ listLiveProcesses: listLiveDaemonProcesses })
logStartupMilestone('startup-service-done', { service: 'wsl-hook-relay-reconciliation' })
},
onDaemonError: (error) => {
// Why: daemon failure silently falls back to non-persistent local PTYs; log + telemetry so a fleet-wide outage is observable (was invisible in v1.4.129-rc.1).
const reason = error instanceof Error ? error.message : String(error)
@@ -951,6 +961,9 @@ function startTerminalRuntimeStartupServices(): Promise<void> {
onAgentHookServerError: (error) => {
// Why: hook callbacks are sidebar enrichment only; Orca must still boot if the loopback receiver fails.
console.error('[agent-hooks] Failed to start local hook server:', error)
},
onWslHookRelayReconciliationError: (error) => {
console.warn('[agent-hooks] Failed to reconcile startup WSL hook relays:', error)
}
})
firstWindowStartupServicesReady = startupServices.firstWindowReady
@@ -22,8 +22,10 @@ describe('startFirstWindowStartupServices', () => {
events.push('hooks-started')
resolveHooks = resolve
}),
reconcileWslHookRelays: vi.fn(async () => {}),
onDaemonError: vi.fn(),
onAgentHookServerError: vi.fn()
onAgentHookServerError: vi.fn(),
onWslHookRelayReconciliationError: vi.fn()
})
await Promise.resolve()
@@ -45,6 +47,66 @@ describe('startFirstWindowStartupServices', () => {
expect(completed).toBe(true)
})
it('reconciles after both authorities and keeps local reattach gated until relays are ready', async () => {
const events: string[] = []
let resolveDaemon!: () => void
let resolveHooks!: () => void
let resolveRelays!: () => void
const started = startFirstWindowStartupServices({
startDaemonPtyProvider: () =>
new Promise<void>((resolve) => {
events.push('daemon-started')
resolveDaemon = resolve
}),
startAgentHookServer: () =>
new Promise<void>((resolve) => {
events.push('hooks-started')
resolveHooks = resolve
}),
reconcileWslHookRelays: () =>
new Promise<void>((resolve) => {
events.push('relay-reconciliation-started')
resolveRelays = resolve
}),
onDaemonError: vi.fn(),
onAgentHookServerError: vi.fn(),
onWslHookRelayReconciliationError: vi.fn()
})
await Promise.resolve()
resolveHooks()
await Promise.resolve()
expect(events).toEqual(['daemon-started', 'hooks-started'])
resolveDaemon()
await vi.waitFor(() => expect(events).toContain('relay-reconciliation-started'))
let localPtyReady = false
void started.localPtyReady.then(() => {
localPtyReady = true
})
await Promise.resolve()
expect(localPtyReady).toBe(false)
resolveRelays()
await started.localPtyReady
expect(localPtyReady).toBe(true)
})
it('reports reconciliation failure and still opens the startup gate', async () => {
const onWslHookRelayReconciliationError = vi.fn()
const started = startFirstWindowStartupServices({
startDaemonPtyProvider: () => Promise.resolve(),
startAgentHookServer: () => Promise.resolve(),
reconcileWslHookRelays: () => Promise.reject(new Error('inventory unavailable')),
onDaemonError: vi.fn(),
onAgentHookServerError: vi.fn(),
onWslHookRelayReconciliationError
})
await expect(started.localPtyReady).resolves.toBeUndefined()
expect(onWslHookRelayReconciliationError).toHaveBeenCalledWith(expect.any(Error))
})
it('opens the provider gate when daemon startup finishes without waiting for hooks', async () => {
let resolveDaemon!: () => void
let resolveHooks!: () => void
@@ -57,8 +119,10 @@ describe('startFirstWindowStartupServices', () => {
new Promise<void>((resolve) => {
resolveHooks = resolve
}),
reconcileWslHookRelays: vi.fn(async () => {}),
onDaemonError: vi.fn(),
onAgentHookServerError: vi.fn()
onAgentHookServerError: vi.fn(),
onWslHookRelayReconciliationError: vi.fn()
})
await Promise.resolve()
@@ -81,8 +145,10 @@ describe('startFirstWindowStartupServices', () => {
const started = startFirstWindowStartupServices({
startDaemonPtyProvider: () => Promise.reject(new Error('daemon failed')),
startAgentHookServer: () => Promise.reject(new Error('hooks failed')),
reconcileWslHookRelays: vi.fn(async () => {}),
onDaemonError,
onAgentHookServerError
onAgentHookServerError,
onWslHookRelayReconciliationError: vi.fn()
})
await expect(started.firstWindowReady).resolves.toBeUndefined()
@@ -104,8 +170,10 @@ describe('startFirstWindowStartupServices', () => {
startAgentHookServer: () => {
throw new Error('hooks sync failed')
},
reconcileWslHookRelays: vi.fn(async () => {}),
onDaemonError,
onAgentHookServerError
onAgentHookServerError,
onWslHookRelayReconciliationError: vi.fn()
})
await expect(started.firstWindowReady).resolves.toBeUndefined()
@@ -131,8 +199,10 @@ describe('startFirstWindowStartupServices', () => {
})
},
startAgentHookServer: () => Promise.resolve(),
reconcileWslHookRelays: vi.fn(async () => {}),
onDaemonError,
onAgentHookServerError: vi.fn()
onAgentHookServerError: vi.fn(),
onWslHookRelayReconciliationError: vi.fn()
})
let ptyGateOpened = false
@@ -172,8 +242,10 @@ describe('startFirstWindowStartupServices', () => {
return new Promise<void>(() => {})
},
startAgentHookServer: () => Promise.resolve(),
reconcileWslHookRelays: vi.fn(async () => {}),
onDaemonError,
onAgentHookServerError
onAgentHookServerError,
onWslHookRelayReconciliationError: vi.fn()
})
await Promise.resolve()
@@ -1,8 +1,10 @@
type FirstWindowStartupServices = {
startDaemonPtyProvider: (signal: AbortSignal) => Promise<void>
startAgentHookServer: (signal: AbortSignal) => Promise<void>
reconcileWslHookRelays: () => Promise<void>
onDaemonError: (error: unknown) => void
onAgentHookServerError: (error: unknown) => void
onWslHookRelayReconciliationError: (error: unknown) => void
}
type StartupService = {
@@ -62,8 +64,10 @@ function startService(
export function startFirstWindowStartupServices({
startDaemonPtyProvider,
startAgentHookServer,
reconcileWslHookRelays,
onDaemonError,
onAgentHookServerError
onAgentHookServerError,
onWslHookRelayReconciliationError
}: FirstWindowStartupServices): FirstWindowStartupServicesResult {
// Why: daemon startup and hook-server binding are independent, but both gate
// restored terminals; run them together so cold-start latency is max(), not sum().
@@ -74,7 +78,11 @@ export function startFirstWindowStartupServices({
// strand any fallback PTYs that spawn after the gate opens.
const daemon = startService('daemon PTY provider', startDaemonPtyProvider, onDaemonError)
const hooks = startService('agent hook server', startAgentHookServer, onAgentHookServerError)
const allServicesReady = Promise.all([daemon.ready, hooks.ready]).then(() => undefined)
let failOpened = false
const allServicesReady = Promise.all([daemon.ready, hooks.ready])
.then(() => (failOpened ? undefined : reconcileWslHookRelays()))
.catch(onWslHookRelayReconciliationError)
.then(() => undefined)
let windowTimeout: ReturnType<typeof setTimeout> | null = null
let failOpenTimeout: ReturnType<typeof setTimeout> | null = null
const servicesSettled = allServicesReady.finally(() => {
@@ -87,6 +95,7 @@ export function startFirstWindowStartupServices({
})
const failOpenReady = new Promise<void>((resolve) => {
failOpenTimeout = setTimeout(() => {
failOpened = true
daemon.reportTimeout()
hooks.reportTimeout()
resolve()