fix(serve): give headless hosts the services the desktop window path started (#37633)

Headless orca serve (orcad and Electron --serve) was missing services that only
the desktop composition or a window attach started.

- orcad now wires the orchestration environment transport, so a headless Run
  home can place workers on its paired servers again. The transport moves into
  a shared factory both hosts use. Its routing graph broadcast to renderers
  through BrowserWindow directly; that now goes through the desktop-surface
  port, so the graph runs on plain Node.
- orcad also resolves paired-device names, which was a declared gap.
- Usage polling runs from the account composition every host builds. Polls
  fire while someone watches: the focused desktop window, or a live
  accounts.subscribe stream. A headless host's remote readers now get the polls
  that stream promises, and unwatched hosts spend no usage budget.
- The composition parity test now accounts for every service a window attach
  starts, so a new window-only start can't silently skip headless hosts.

Fixes #36853
Refs #37256

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
This commit is contained in:
Neil
2026-11-04 17:43:35 -08:00
committed by GitHub
co-authored by Brennan Benson
parent 86d0ddd06b
commit 688adc4b4c
20 changed files with 309 additions and 69 deletions
@@ -17,6 +17,18 @@ export const electronRuntimeDesktopSurface: RuntimeDesktopSurface = {
new Notification({ title, body }).show()
return true
},
sendToRenderers: (channel, payload) => {
for (const window of BrowserWindow.getAllWindows()) {
if (window.isDestroyed()) {
continue
}
try {
window.webContents.send(channel, payload)
} catch {
// A renderer can disappear between isDestroyed() and send().
}
}
},
findWindowById: (id) => BrowserWindow.fromId(id),
isResourceViewVisible: (sender) => isResourceWindowVisible(BrowserWindow.fromWebContents(sender)),
onIpc: (channel, listener) => {
@@ -0,0 +1,36 @@
import { getAppEnvironment } from '../../../shared/app-environment'
import { getPreferredPairedRuntimeConnection } from '../../../shared/remote-runtime/host-catalog/runtime-environments'
import { resolveEnvironment } from '../../../shared/remote-runtime/host-store/runtime-environment-store'
import {
pairedOrchestrationPeers,
type OrchestrationEnvironmentTransport
} from '../../runtime/orchestration/environment-transport'
/** Dials this host's paired servers for cross-server orchestration; host-free, so desktop and orcad share it. */
export function createOrchestrationEnvironmentTransport(): OrchestrationEnvironmentTransport {
// Read per call: the desktop settles userData casing only after app.setName.
const registryRoot = (): string => getAppEnvironment().getPath('userData')
return {
resolve: (selector) => {
const environment = resolveEnvironment(registryRoot(), selector)
const connection = getPreferredPairedRuntimeConnection(environment)
return {
environmentId: environment.id,
name: environment.name,
...pairedOrchestrationPeers(connection.hostPublicKeyB64, connection.credential.deviceToken),
pairingRevision: environment.pairingRevision ?? environment.createdAt
}
},
// Loaded on first dial: the routing graph is large and most hosts never place remote workers.
call: async (selector, method, params, timeoutMs, envelope, expectedPairingRevision) =>
(await import('./runtime-environment-transport-routing')).callRuntimeEnvironment(
registryRoot(),
selector,
method,
params,
timeoutMs,
expectedPairingRevision,
envelope
)
}
}
@@ -1,6 +1,6 @@
import { BrowserWindow } from 'electron'
import type { RemoteRuntimeSharedConnectionDiagnostics } from '../../../shared/remote-runtime/control/remote-runtime-shared-control-types'
import { RUNTIME_ENVIRONMENT_DIAGNOSTICS_CHANNEL } from '../../../shared/remote-runtime/host-catalog/runtime-environment-diagnostics'
import { getRuntimeDesktopSurface } from '../../runtime/runtime-desktop-surface'
export type RuntimeEnvironmentDiagnosticsEvent = {
environmentId: string
@@ -11,14 +11,5 @@ export type RuntimeEnvironmentDiagnosticsEvent = {
export function publishRuntimeEnvironmentDiagnostics(
event: RuntimeEnvironmentDiagnosticsEvent
): void {
for (const window of BrowserWindow.getAllWindows()) {
if (window.isDestroyed()) {
continue
}
try {
window.webContents.send(RUNTIME_ENVIRONMENT_DIAGNOSTICS_CHANNEL, event)
} catch {
// A renderer can disappear between isDestroyed() and send().
}
}
getRuntimeDesktopSurface().sendToRenderers?.(RUNTIME_ENVIRONMENT_DIAGNOSTICS_CHANNEL, event)
}
@@ -1,4 +1,4 @@
import { BrowserWindow } from 'electron'
import { getRuntimeDesktopSurface } from '../../runtime/runtime-desktop-surface'
import { sendRemoteRuntimeRequest } from '../../../shared/remote-runtime/remote-runtime-client'
import { REMOTE_RUNTIME_SHARED_CONTROL_CAPABILITY } from '../../../shared/protocol-version'
import { ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES } from '../../../shared/electron-remote-runtime-client-capabilities'
@@ -140,16 +140,7 @@ export function createRuntimeEnvironmentStatusOwner(
return capable && active
},
publish: (snapshot) => {
for (const window of BrowserWindow.getAllWindows()) {
if (window.isDestroyed()) {
continue
}
try {
window.webContents.send(RUNTIME_HOST_STATUS_CHANNEL, snapshot)
} catch {
/* A renderer can close during publication. */
}
}
getRuntimeDesktopSurface().sendToRenderers?.(RUNTIME_HOST_STATUS_CHANNEL, snapshot)
for (const listener of statusListeners) {
listener()
}
-1
View File
@@ -65,7 +65,6 @@ export function createOrcadAccountServices(args: {
const services = createProviderAccountServices(store, {
setClaudeStatusLineListener: args.setClaudeStatusLineListener
})
// Refresh usage on demand through list/subscribe rather than starting a background poller.
args.registerCleanup(() => {
services.rateLimits.stop()
args.setClaudeStatusLineListener(null)
+4 -4
View File
@@ -189,10 +189,9 @@ async function startOrcadRuntime(
const { loadAgentSessionClaimSigner } =
await import('../runtime/agent-session/ownership/agent-session-claim-identity')
let runtimeRpc: InstanceType<typeof OrcaRuntimeRpcServer> | null = null
const { createOrcadPairedEnvironmentOptions } = await import('./orcad-paired-environment-options')
const pairedEnvironments = createOrcadPairedEnvironmentOptions()
const runtime = new OrcaRuntimeService(profileStore, stats, {
getPairedDeviceName: (deviceId) =>
runtimeRpc?.getDeviceRegistry()?.getDevice(deviceId)?.name ?? null,
// Persistent signatures let restarted runtimes adopt PTYs owned before restart.
agentSessionClaimSigner: loadAgentSessionClaimSigner(runtimeUserDataPath, runtimeUserDataPath),
getAdditionalAiVaultCodexHomePaths: () => [
@@ -206,6 +205,7 @@ async function startOrcadRuntime(
),
// Resolve lazily because daemon swaps replace the provider after construction.
getLocalProvider: () => getLocalPtyProvider(),
...pairedEnvironments.runtimeOptions,
// Read current daemon ownership; failed/degraded launch cannot claim fresh PTY persistence.
canRecoverPersistentLocalPtys: () => daemonOwnsFreshPersistentPtys(),
// Node hosts cannot promote into desktop windows; override the constructor’s openable default.
@@ -304,7 +304,7 @@ async function startOrcadRuntime(
webClientRoot: resolveOrcadWebClientRoot(options.webClientRoot),
...orcadWsPortOptions(options.port)
})
runtimeRpc = rpc
pairedEnvironments.bindRpc(rpc)
runtime.setAgentSenderPeerKey(() => rpc.getE2EEPublicKey())
// Stop RPC before cleanup can tear down writers and runtime state.
registerCleanup(() => rpc.stop())
@@ -0,0 +1,27 @@
import { createOrchestrationEnvironmentTransport } from '../ipc/runtime-environments/orchestration-environment-transport'
import type { RuntimeConstructionDependencies } from '../runtime/construction/runtime-construction-dependencies'
import type { OrcaRuntimeRpcServer } from '../runtime/runtime-rpc'
type PairedEnvironmentRuntimeOptions = Required<
Pick<RuntimeConstructionDependencies, 'orchestrationEnvironmentTransport' | 'getPairedDeviceName'>
>
/** Runtime options for this host's pairings: the servers it dials and the devices paired to it. */
export function createOrcadPairedEnvironmentOptions(): {
runtimeOptions: PairedEnvironmentRuntimeOptions
/** RPC owns the device registry and starts after the runtime. */
bindRpc: (rpc: Pick<OrcaRuntimeRpcServer, 'getDeviceRegistry'>) => void
} {
let rpc: Pick<OrcaRuntimeRpcServer, 'getDeviceRegistry'> | null = null
return {
runtimeOptions: {
// A headless host is a Run home too; it places workers on its own paired servers (#36853).
orchestrationEnvironmentTransport: createOrchestrationEnvironmentTransport(),
getPairedDeviceName: (pairedDeviceId) =>
rpc?.getDeviceRegistry()?.getDevice(pairedDeviceId)?.name ?? null
},
bindRpc: (server) => {
rpc = server
}
}
}
@@ -173,5 +173,7 @@ export function createProviderAccountServices(
)
}))
})
// Every host runs the poll timer; ticks fetch only while a window or remote reader watches.
rateLimits.start({ fetchImmediately: false })
return { rateLimits, codexRuntimeHome, codexAccounts, claudeRuntimeAuth, claudeAccounts }
}
@@ -129,6 +129,32 @@ describe('RateLimitService', () => {
service.stop()
})
// #37256: a headless host has no window, so its only watchers are remote account streams.
it('polls without a window only while a remote reader watches', async () => {
vi.useFakeTimers()
try {
mockFreshBackgroundProviderFetches()
vi.mocked(fetchClaudeRateLimits).mockImplementation(async () => okProvider('claude', 12))
const service = new RateLimitService()
service.start({ fetchImmediately: false })
await vi.advanceTimersByTimeAsync(15 * 60 * 1000)
expect(fetchClaudeRateLimits).not.toHaveBeenCalled()
const release = service.addUsageViewer()
await vi.advanceTimersByTimeAsync(15 * 60 * 1000)
expect(fetchClaudeRateLimits).toHaveBeenCalledTimes(1)
release()
await vi.advanceTimersByTimeAsync(15 * 60 * 1000)
expect(fetchClaudeRateLimits).toHaveBeenCalledTimes(1)
service.stop()
} finally {
vi.useRealTimers()
}
})
it('performs a one-shot active-window fetch when startup focus was missed', async () => {
vi.useFakeTimers()
try {
@@ -85,7 +85,7 @@ export abstract class RateLimitServiceConfiguration extends RateLimitServiceAcco
this.detachWindowListeners?.()
this.mainWindow = mainWindow
const refreshOnResume = (): void => {
void this.refreshIfWindowActive()
void this.refreshIfWatched()
}
// Replacing windows must also detach the old closed listener.
const detachWindowListeners = (): void => {
@@ -108,10 +108,25 @@ export abstract class RateLimitServiceConfiguration extends RateLimitServiceAcco
mainWindow.on('restore', refreshOnResume)
mainWindow.on('closed', onClosed)
this.detachWindowListeners = detachWindowListeners
// Quota probes spawn CLIs/use network; defer past first paint, then show/focus refresh.
this.scheduleDeferredStartupRefresh()
}
/** Counts a paired client's usage stream so polls run without a focused window; returns its release. */
addUsageViewer(): () => void {
this.usageViewers += 1
let released = false
return () => {
if (!released) {
released = true
this.usageViewers -= 1
}
}
}
start(options: { fetchImmediately?: boolean } = {}): void {
if (options.fetchImmediately !== false) {
this.clearDeferredStartupRefresh()
void this.fetchAll()
} else {
this.scheduleDeferredStartupRefresh()
@@ -42,7 +42,7 @@ export abstract class RateLimitServicePolling extends RateLimitServiceFetchQueue
this.clearDeferredStartupRefresh()
this.deferredStartupRefreshTimer = setTimeout(() => {
this.deferredStartupRefreshTimer = null
void this.refreshIfWindowActive()
void this.refreshIfWatched()
}, DEFERRED_STARTUP_ACTIVE_REFRESH_MS)
}
@@ -54,6 +54,10 @@ export abstract class RateLimitServicePolling extends RateLimitServiceFetchQueue
}
protected shouldBackgroundPoll(): boolean {
// Poll only while someone watches: a paired client's stream, or the focused desktop window.
if (this.usageViewers > 0) {
return true
}
if (!this.mainWindow || this.mainWindow.isDestroyed()) {
return false
}
@@ -174,7 +178,7 @@ export abstract class RateLimitServicePolling extends RateLimitServiceFetchQueue
}
}
protected async refreshIfWindowActive(): Promise<void> {
protected async refreshIfWatched(): Promise<void> {
if (!this.shouldBackgroundPoll()) {
return
}
@@ -71,6 +71,8 @@ export abstract class RateLimitServiceState {
zcode: 0
}
protected mainWindow: BrowserWindow | null = null
/** Paired clients streaming usage (accounts.subscribe); headless hosts have no window to watch. */
protected usageViewers = 0
protected detachWindowListeners: (() => void) | null = null
protected isFetching = false
protected fullFetchQueued = false
@@ -75,11 +75,6 @@ const DECLARED_HOST_DIFFERENCES: readonly DeclaredDifference[] = [
missingOn: 'orcad',
reason: 'Pairs with attestAgentHookCompatibilityAuthority.'
},
{
wiring: 'ctor:orchestrationEnvironmentTransport',
missingOn: 'orcad',
reason: 'Cross-environment orchestration dials paired environments from the desktop only.'
},
{
wiring: 'ctor:skillTransactionRecovery',
missingOn: 'orcad',
@@ -160,6 +155,68 @@ const DECLARED_HOST_DIFFERENCES: readonly DeclaredDifference[] = [
}
]
type WindowAttachStart = {
reason: string
/** Where each headless host starts its own equivalent; omitted only when none needs one. */
headless?: Record<Root, { file: string; call: string }>
}
/** Background work started when a desktop window attaches; headless hosts never attach one (#37256). */
const WINDOW_ATTACH_STARTS: Readonly<Record<string, WindowAttachStart>> = {
automations: {
reason: 'The desktop binds automations to its window; headless hosts start them at launch.',
headless: {
electron: {
file: 'src/main/startup/main-process-runtime-launch.ts',
call: 'automations?.start()'
},
orcad: { file: 'src/main/orcad/orcad-entry.ts', call: 'startOrcadAutomations(' }
}
},
'rateLimits.attach': {
reason: 'A focused window is one usage watcher; the shared account composition runs the poller.'
},
initTccPromptNotice: { reason: 'macOS privacy-prompt notices render in the window.' },
installPluginDesktopFrameServices: { reason: 'Plugin frames render inside the window.' },
attachMainWindowServices: {
reason: 'Window IPC and renderer wiring; its starts are listed here.'
},
hydrateLocalPtyRegistryAtBoot: {
reason: 'Headless PTY registration hydrates the same registry.',
headless: {
electron: {
file: 'src/main/startup/main-process-runtime-launch.ts',
call: 'registerHeadlessPtyRuntime('
},
orcad: { file: 'src/main/orcad/orcad-entry.ts', call: 'registerHeadlessPtyRuntime(' }
}
},
scheduleMainWindowAutoUpdaterSetup: {
reason: 'Serve sets up its own updater; orcad is updated by the client that manages it.'
},
scheduleWorktreeBaseDirectoryWatcherSync: {
reason: 'Unwired gap (follow-up): external worktree changes on a headless host push nothing.'
},
startFolderRepoGitUpgradeWatch: {
reason:
'Unwired gap (follow-up): git init in a headless folder project needs a manual type change.'
},
scheduleHistoryGc: {
reason: 'Unwired gap (follow-up): headless hosts never prune history of vanished worktrees.'
},
'openCodeHookService.configDirGc.schedule': {
reason: 'Unwired gap (follow-up): headless hosts never prune stale OpenCode overlays.'
},
'openCode2HookService.configDirGc.schedule': {
reason: 'Unwired gap (follow-up): headless hosts never prune stale OpenCode overlays.'
}
}
const WINDOW_ATTACH_SOURCES = [
{ file: 'src/main/startup/main-window-core-services.ts', fn: 'attachMainWindowCoreServices' },
{ file: 'src/main/window/attach-main-window-services.ts', fn: 'attachMainWindowServices' }
] as const
const UNWIRED_CONSTRUCTION_OPTIONS: Readonly<Record<string, string>> = {}
const POSITIONAL_CONSTRUCTOR_PARAMETERS = ['stats'] as const
@@ -206,6 +263,10 @@ const SPREAD_SOURCES: Record<string, { file: string; anchor: string }> = {
file: 'src/main/orcad/orcad-account-services.ts',
anchor: 'runtimeDeps: {'
},
'pairedEnvironments.runtimeOptions': {
file: 'src/main/orcad/orcad-paired-environment-options.ts',
anchor: 'runtimeOptions: {'
},
'orcadWsPortOptions(': { file: 'src/main/orcad/orcad-ws-port-policy.ts', anchor: 'wsPort: port' }
}
@@ -399,6 +460,31 @@ function sourceFilesUnder(dir: string): string[] {
.map((file) => join(dir, file).split(sep).join('/'))
}
function functionBody(source: string, name: string): string {
const at = source.indexOf(`export function ${name}(`)
if (at === -1) {
throw new Error(`function not found: ${name}`)
}
const open = source.indexOf('(', at)
const paramsEnd = open + parenGroup(source, open).length + 1
return braceGroup(source, source.indexOf('{', paramsEnd))
}
/** Calls whose final member starts, schedules, hydrates, initializes, attaches or installs something. */
function windowAttachStarts(): Set<string> {
const starts = new Set<string>()
for (const { file, fn } of WINDOW_ATTACH_SOURCES) {
const body = functionBody(read(file), fn)
for (const [, callee] of body.matchAll(
/(?<![\w$.])((?:[A-Za-z_$][\w$]*\??\.)*(?:start|schedule|hydrate|init|attach|install)[\w$]*)(?:\?\.)?\(/g
)) {
const path = callee.replaceAll('?', '')
starts.add(path.endsWith('.start') ? path.slice(0, -'.start'.length) : path)
}
}
return starts
}
function declaredConstructionOptions(): string[] {
const source = read('src/main/runtime/construction/runtime-construction-dependencies.ts')
const inner = objectLiteralAfter(source, 'export type RuntimeConstructionDependencies = {')
@@ -420,6 +506,9 @@ describe('runtime composition parity (Electron vs orcad)', () => {
'ctor:getLocalProvider',
'ctor:onTerminalAgentStatus',
'ctor:agentSessionClaimSigner',
// #36853: without it a headless Run home refuses worker-start --on with server_required.
'ctor:orchestrationEnvironmentTransport',
'ctor:getPairedDeviceName',
'rpc-option:webClientRoot',
'rpc-option:wsPort',
'runtime.setAccountServices',
@@ -492,6 +581,16 @@ describe('runtime composition parity (Electron vs orcad)', () => {
}
})
it('accounts for every service a desktop window starts, which headless hosts never attach', () => {
expect([...windowAttachStarts()].sort()).toEqual(Object.keys(WINDOW_ATTACH_STARTS).sort())
for (const [start, { headless }] of Object.entries(WINDOW_ATTACH_STARTS)) {
for (const [root, counterpart] of Object.entries(headless ?? {})) {
expect(read(counterpart.file), `${root} ${start}`).toContain(counterpart.call)
}
}
})
// #37256: one composition runs the usage poller on desktop, Electron serve and orcad alike.
it('lists each advertised-but-unserved capability against what the host advertises', () => {
for (const difference of DECLARED_HOST_DIFFERENCES) {
expect(difference.reason, difference.wiring).not.toBe('')
@@ -562,6 +562,7 @@ describe('remote runtime request connection integration', () => {
},
getAccountsSnapshot: () => ({ claude: null, codex: null }),
getAccountsStreamSnapshot: () => ({ claude: null, codex: null }),
getUsageRateLimits: () => null,
refreshAccountsForMobile: async () => {
for (const listener of accountsListeners) {
listener({ claude: null, codex: null })
@@ -4,6 +4,7 @@ import { join } from 'node:path'
import type { OrcaRuntimeService } from '../../orca-runtime'
import { eraseRpcMethods, isStreamingMethod, type RpcContext } from '../core'
import { ACCOUNT_METHODS } from './accounts'
import { RateLimitService } from '../../../rate-limits/service'
import { createEmptyRateLimitState } from '../../../../shared/rate-limit-state-factory'
import type {
HostAccountSignInChannel,
@@ -337,6 +338,62 @@ describe('account RPC methods', () => {
})
})
// #37256: a paired client's stream keeps a windowless host polling; the host's own renderer follows window focus.
it.each([
{ caller: { kind: 'paired-device', deviceId: 'phone-1' } as const, holds: true },
{ caller: { kind: 'desktop' } as const, holds: false }
])('holds a usage viewer for a $caller.kind stream: $holds', async ({ caller, holds }) => {
const rateLimits = new RateLimitService()
const releaseViewer = vi.fn()
const addUsageViewer = vi.spyOn(rateLimits, 'addUsageViewer').mockReturnValue(releaseViewer)
let cleanup: (() => void) | undefined
const runtime: Pick<
OrcaRuntimeService,
| 'getAccountsStreamSnapshot'
| 'getUsageRateLimits'
| 'onAccountsChanged'
| 'registerSubscriptionCleanup'
| 'cleanupSubscription'
| 'refreshAccountsForMobileSubscriber'
> = {
getAccountsStreamSnapshot: vi.fn(() => ({
claude: { accounts: [], activeAccountId: null },
codex: { accounts: [], activeAccountId: null },
rateLimits: createEmptyRateLimitState()
})),
getUsageRateLimits: () => rateLimits,
onAccountsChanged: vi.fn(() => vi.fn()),
registerSubscriptionCleanup: vi.fn((_id: string, next: () => void) => {
cleanup = next
}),
cleanupSubscription: vi.fn(() => {
const release = cleanup
cleanup = undefined
release?.()
}),
refreshAccountsForMobileSubscriber: vi.fn().mockResolvedValue(undefined)
}
const subscribe = method('accounts.subscribe')
if (!isStreamingMethod(subscribe)) {
throw new Error('accounts.subscribe must be a streaming method')
}
const stream: {
handler(
params: unknown,
context: { runtime: typeof runtime; caller?: RpcContext['caller']; signal?: AbortSignal },
emit: (frame: unknown) => void
): Promise<void>
} = subscribe
const controller = new AbortController()
const running = stream.handler(undefined, { runtime, caller, signal: controller.signal }, vi.fn())
await vi.waitFor(() => expect(runtime.refreshAccountsForMobileSubscriber).toHaveBeenCalled())
expect(addUsageViewer).toHaveBeenCalledTimes(holds ? 1 : 0)
controller.abort()
await running
expect(releaseViewer).toHaveBeenCalledTimes(holds ? 1 : 0)
})
it('rejects the retired Claude import even for a local socket, before touching its supplied path', async () => {
const old = method('accounts.addClaudeFromConfigDir')
if (isStreamingMethod(old)) {
+7 -1
View File
@@ -240,7 +240,7 @@ const ACCOUNT_METHOD_DEFINITIONS = [
name: 'accounts.subscribe',
permission: 'workspace',
params: null,
handler: async (_params, { runtime, connectionId, signal }, emit) => {
handler: async (_params, { runtime, connectionId, signal, caller }, emit) => {
if (signal?.aborted) {
return
}
@@ -248,6 +248,11 @@ const ACCOUNT_METHOD_DEFINITIONS = [
const unsubscribe = runtime.onAccountsChanged((snapshot) => {
emit({ type: 'snapshot', snapshot })
})
// A paired client watching keeps polls running without a window; the host's own renderer follows window focus.
const releaseUsageViewer =
caller?.kind === 'paired-device'
? runtime.getUsageRateLimits()?.addUsageViewer()
: undefined
// Key cleanup by socket plus sequence so sibling sockets and concurrent subscribes cannot evict each other.
const seq = ++accountsSubscriptionSeq
@@ -258,6 +263,7 @@ const ACCOUNT_METHOD_DEFINITIONS = [
() => {
signal?.removeEventListener('abort', release)
unsubscribe()
releaseUsageViewer?.()
stopped.resolve()
},
connectionId
@@ -10,6 +10,8 @@ export type RuntimeDesktopSurface = {
isAwayForMobileNotifications?(): boolean | undefined
/** Show a native notification. Returns false when the host cannot, so callers can say so. */
showNotification(input: { title: string; body: string }): boolean
/** Sends to every open renderer window. Absent where there is no desktop. */
sendToRenderers?(channel: string, payload: unknown): void
/** The renderer window with this id, or null when there is no desktop. */
findWindowById(id: number): BrowserWindow | null
isResourceViewVisible?(sender: WebContents): boolean
@@ -22,15 +22,9 @@ import { resolveHostCodexSessionSourceHome } from '../codex/history/rollouts/cod
import { agentHookRuntimeDeps } from '../agent-hooks/agent-hook-runtime-deps'
import { getDaemonProvider } from '../daemon/daemon-provider-state'
import type { TerminalSideEffectBatch } from '../../shared/terminal/output/terminal-side-effect-facts'
import { resolveEnvironment } from '../../shared/remote-runtime/host-store/runtime-environment-store'
import { resolveTuiAgentLaunchEnv } from '../../shared/agent-launch/choices/tui-agent-launch-defaults'
import { getPreferredPairedRuntimeConnection } from '../../shared/remote-runtime/host-catalog/runtime-environments'
import {
type OrchestrationEnvironmentTransport,
pairedOrchestrationPeers
} from '../runtime/orchestration/environment-transport'
import { callRuntimeEnvironment } from '../ipc/runtime-environments/runtime-environment-transport-routing'
import { createOrchestrationEnvironmentTransport } from '../ipc/runtime-environments/orchestration-environment-transport'
import { mainProcessState as state } from './main-process-state'
import { suspendEphemeralVmWorkspace } from '../ephemeral-vm/workspace-suspend'
import { installWslOrcadAccountShim } from './wsl-orcad-account-env-wiring'
@@ -68,28 +62,6 @@ export function initializeMainProcessRuntime(): OrcaRuntimeService {
if (!store || !stats) {
throw new Error('Store and stats must be initialized before runtime')
}
const orchestrationEnvironmentTransport: OrchestrationEnvironmentTransport = {
resolve: (selector) => {
const environment = resolveEnvironment(getRuntimeEnvironmentRegistryRoot(), selector)
const connection = getPreferredPairedRuntimeConnection(environment)
return {
environmentId: environment.id,
name: environment.name,
...pairedOrchestrationPeers(connection.hostPublicKeyB64, connection.credential.deviceToken),
pairingRevision: environment.pairingRevision ?? environment.createdAt
}
},
call: (selector, method, params, timeoutMs, envelope, expectedPairingRevision) =>
callRuntimeEnvironment(
getRuntimeEnvironmentRegistryRoot(),
selector,
method,
params,
timeoutMs,
expectedPairingRevision,
envelope
)
}
// Process-scope subscription covers desktop, headless serve and fleet status.
const observedPaneIdentities = new AgentStatusObservedPaneIdentities()
const runtime = new OrcaRuntimeService(store, stats, {
@@ -134,7 +106,7 @@ export function initializeMainProcessRuntime(): OrcaRuntimeService {
homePath,
resolveTuiAgentLaunchEnv('codex', store.getSettings())
),
orchestrationEnvironmentTransport,
orchestrationEnvironmentTransport: createOrchestrationEnvironmentTransport(),
workspaceSleepPlatform: {
callEnvironment: (environmentId, method, params, timeoutMs) =>
callRuntimeEnvironment(
@@ -161,6 +161,4 @@ export function attachMainWindowCoreServices(
// Attach durable diagnostic pulls now, defer process launch past first paint.
initTccPromptNotice(window, { deferWatchUntilReadyToShow: true })
rateLimits.attach(window)
// Quota probes spawn CLIs/use network; defer to show/focus refresh so they do not compete with first paint.
rateLimits.start({ fetchImmediately: false })
}
@@ -107,7 +107,7 @@ export function useOwnerUsageRateLimits(local: RateLimitState): OwnerUsageRateLi
}
let active = true
const reopen = (): void => setStreamGeneration((value) => value + 1)
// Why: orcad does not poll on its own, so a long-lived stream would freeze the meters.
// Why: older hosts never poll for a paired stream, so a long-lived one would freeze the meters.
const staleTimer = setTimeout(reopen, REMOTE_USAGE_STALE_REOPEN_MS)
let reconnectTimer: ReturnType<typeof setTimeout> | undefined
const lost = (): void => {