perf(rpc): stop the dispatcher importing every RPC method by default (#39669)

* perf(rpc): stop the dispatcher importing every RPC method by default

Callers pass the method list. A test that builds a dispatcher no longer loads
the full method registry, cutting the dispatcher's import graph from ~4k to
~550 modules.

* chore(lint): keep the RPC dispatcher and plugin RPC off the method registry

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
OrcaWin
2026-11-07 18:52:59 -08:00
committed by GitHub
co-authored by OrcaWin
parent 9c45420e91
commit 3e9ea1ec57
7 changed files with 48 additions and 10 deletions
+33
View File
@@ -212,6 +212,39 @@
"rules": {
"max-lines": ["error", { "max": 800, "skipBlankLines": true, "skipComments": true }]
}
},
{
"files": [
"src/main/runtime/rpc/dispatcher.ts",
"src/main/plugins/plugin-runtime-rpc.ts",
"src/main/plugins/plugin-runtime-binding.ts"
],
"rules": {
"no-restricted-imports": [
"error",
{
"paths": [
{
"name": "@linear/sdk",
"allowTypeImports": true,
"message": "Value-importing @linear/sdk hoists its ~2.6MB CJS bundle into the eager top-level require block and defeats the lazy loader. Use `import type` plus loadLinearSdk() from src/main/linear/linear-sdk.ts."
},
{
"name": "ssh2",
"allowTypeImports": true,
"message": "Value-importing ssh2 hoists it into the eager main-process require block and blocks startup (#35731). Use `import type` plus loadSsh2() from src/main/ssh/ssh2-module-load.ts."
}
],
"patterns": [
{
"group": ["**/rpc-method-registry"],
"allowTypeImports": true,
"message": "The registry imports every RPC method (~3.4k modules); take the method list as a parameter so importers stay light."
}
]
}
]
}
}
],
"ignorePatterns": [
+4 -2
View File
@@ -7,6 +7,7 @@ import { installPluginHookRunner } from './hooks/plugin-hook-participants'
import { installPluginResidentWorkerWarmer } from './workers/plugin-resident-worker'
import type { OrcaRuntimeService } from '../runtime/orca-runtime'
import { pluginRuntimeRpc } from './plugin-runtime-rpc'
import type { DispatcherOptions } from '../runtime/rpc/dispatcher'
import type { PluginRuntimeRpc } from './workers/plugin-host-workspace-method-bindings'
import {
pluginTerminalTitle,
@@ -47,10 +48,11 @@ export function pluginRuntimeDelegate(
export function bindPluginRuntime(
service: PluginService,
runtime: OrcaRuntimeService,
hookServer: { getStatusSnapshot(): readonly PluginAgentStatusRow[] }
hookServer: { getStatusSnapshot(): readonly PluginAgentStatusRow[] },
methods: DispatcherOptions['methods']
): PluginRuntimeDelegate {
const statuses = () => hookServer.getStatusSnapshot()
const delegate = pluginRuntimeDelegate(runtime, statuses, pluginRuntimeRpc(runtime))
const delegate = pluginRuntimeDelegate(runtime, statuses, pluginRuntimeRpc(runtime, methods))
service.setRuntimeDelegate(delegate)
installPluginHookRunner(service, runtime)
installPluginResidentWorkerWarmer(service)
+2 -2
View File
@@ -19,11 +19,11 @@ export class PluginRuntimeRpcError extends Error {
*/
export function pluginRuntimeRpc(
runtime: OrcaRuntimeService,
methods?: DispatcherOptions['methods']
methods: DispatcherOptions['methods']
): PluginRuntimeRpc {
let dispatcher: RpcDispatcher | null = null
return async (method, params, options = {}) => {
dispatcher ??= new RpcDispatcher({ runtime, ...(methods ? { methods } : {}) })
dispatcher ??= new RpcDispatcher({ runtime, methods })
// The dispatcher maps a throw to an error answer; keep the original so its code reaches the plugin.
let refusal: unknown = null
const response = await dispatcher.dispatch(
+2 -3
View File
@@ -11,7 +11,6 @@ import {
} from './core'
import { successResponse } from './errors'
import { ALL_RPC_METHODS } from './methods/rpc-method-registry'
import { withoutMethodsThisHostDoesNotServe } from './methods/terminal/session-terminal-scrollback'
import { emulatorProbe, emulatorProbeError } from '../../emulator/emulator-probe'
import type { OrcaRuntimeService } from '../orca-runtime'
@@ -37,7 +36,7 @@ import { resolveOrcadFeatureRequirement, rpcMethodFeatureWait } from './orcad-fe
export type DispatcherOptions = {
runtime: OrcaRuntimeService
methods?: readonly RpcAnyMethodDeclaration[]
methods: readonly RpcAnyMethodDeclaration[]
/** Pins every call to this scope, for in-process bridges that relay a non-owner caller. */
callerScope?: RpcCallerScope
}
@@ -56,7 +55,7 @@ export class RpcDispatcher {
private readonly legacyOrchestration: OrchestrationLegacyCompatibility
private readonly pinnedCallerScope: RpcCallerScope | undefined
constructor({ runtime, methods = ALL_RPC_METHODS, callerScope }: DispatcherOptions) {
constructor({ runtime, methods, callerScope }: DispatcherOptions) {
this.runtime = runtime
this.pinnedCallerScope = callerScope
this.registry = buildRegistry(withoutMethodsThisHostDoesNotServe(methods))
@@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest'
import { OrcaRuntimeService } from '../orca-runtime'
import type { RpcRequest, RpcResponse } from './core'
import { RpcDispatcher } from './dispatcher'
import { ALL_RPC_METHODS } from './methods/rpc-method-registry'
import type { RpcCallerScope } from './rpc-caller-scope'
const PAIRED: RpcCallerScope = { kind: 'runtime-paired', grants: [] }
@@ -16,7 +17,7 @@ async function dispatchAs(
params?: unknown,
runtime = new OrcaRuntimeService()
): Promise<RpcResponse> {
return new RpcDispatcher({ runtime }).dispatch(
return new RpcDispatcher({ runtime, methods: ALL_RPC_METHODS }).dispatch(
request(method, params),
scope ? { callerScope: scope } : undefined
)
@@ -50,6 +51,7 @@ describe('a pinned in-process bridge dispatcher', () => {
it('ignores a wider per-call scope', async () => {
const dispatcher = new RpcDispatcher({
runtime: new OrcaRuntimeService(),
methods: ALL_RPC_METHODS,
callerScope: PAIRED
})
const response = await dispatcher.dispatch(request('computer.click', { app: 'Finder' }), {
+2 -1
View File
@@ -45,6 +45,7 @@ import { mainProcessState as state } from './main-process-state'
import type { OrcaRuntimeService } from '../runtime/orca-runtime'
import { PluginStatusBarState } from '../plugins/status-bar/plugin-status-bar-state'
import { createDesktopPluginAppearance } from '../plugins/appearance/desktop-plugin-appearance'
import { ALL_RPC_METHODS } from '../runtime/rpc/methods/rpc-method-registry'
const PLUGIN_AUTO_UPDATE_STARTUP_DELAY_MS = 60_000
// Why: the official catalog fetch must not compete with first paint; opening the marketplace seeds sooner.
@@ -240,7 +241,7 @@ export async function initializeMainProcessPlugins(runtime: OrcaRuntimeService):
}
})
// Bound here, not in the IPC handlers: a headless serve never opens a window to register them.
const pluginDelegate = bindPluginRuntime(pluginService, runtime, agentHookServer)
const pluginDelegate = bindPluginRuntime(pluginService, runtime, agentHookServer, ALL_RPC_METHODS)
bindPluginRpcServices(store, pluginService, {
marketplace: state.pluginMarketplaceService,
installer: state.pluginMarketplaceInstaller
@@ -44,6 +44,7 @@ import { runGatedWslMoveDesktopRewritePass } from '../persistence/moving-wsl-pro
import { ORCA_LAYOUT_OWNER_IS_MAIN } from '../../shared/layout-owner-switch'
import { repairDuplicatePtyPanesOnStart } from '../runtime/moved-pane-owner-rekey'
import { WslUsageReporter } from '../telemetry/wsl-usage-reporter'
import { ALL_RPC_METHODS } from '../runtime/rpc/methods/rpc-method-registry'
// Headless serve has no first window; timer fallback must still sweep trash.
const WORKTREE_TRASH_SWEEP_FALLBACK_MS = 15_000
@@ -73,7 +74,7 @@ export async function initializeReadyRuntimeServices(): Promise<void> {
runtime.setAgentBrowserBridge(state.agentBrowserBridge)
// Reclaim browser daemons orphaned by prior crashes/SIGKILL.
void state.agentBrowserBridge.sweepOrphanedSessions()
const browserClientAutomationDispatcher = new RpcDispatcher({ runtime })
const browserClientAutomationDispatcher = new RpcDispatcher({ runtime, methods: ALL_RPC_METHODS })
configureBrowserClientPageAutomationRuntime({
browserManager,
getAgentBrowserBridge: () => state.agentBrowserBridge,