refactor(dashboard): remove unreachable popout spawn/sleep IPC (#32954)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
OrcaWin
2026-10-23 17:33:42 -07:00
committed by GitHub
co-authored by OrcaWin
parent da620ad632
commit cdba9a7703
15 changed files with 10 additions and 234 deletions
@@ -1,7 +1,4 @@
import {
DASHBOARD_MAX_LAUNCH_WORKTREES,
type DashboardSpawnAgentArgs
} from '../../shared/dashboard-snapshot'
import { DASHBOARD_MAX_LAUNCH_WORKTREES } from '../../shared/dashboard-snapshot'
import { isTuiAgent } from '../../shared/tui-agent-config'
const MAX_ID_LENGTH = 4_096
@@ -11,14 +8,6 @@ function isBoundedId(value: unknown): value is string {
return typeof value === 'string' && value.length > 0 && value.length <= MAX_ID_LENGTH
}
export function isDashboardSpawnAgentArgs(value: unknown): value is DashboardSpawnAgentArgs {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return false
}
const args = value as Record<string, unknown>
return isBoundedId(args.worktreeId) && isTuiAgent(args.agent)
}
export function isDashboardLaunchOptions(value: unknown): boolean {
if (value === undefined) {
return true
@@ -18,7 +18,6 @@ vi.mock('../../shared/repo-icon', async (importOriginal) => {
import {
admitDashboardSnapshot,
isDashboardRevealAgentArgs,
isDashboardSpawnAgentArgs,
isDashboardSnapshot
} from './dashboard-payload-validation'
@@ -276,7 +275,7 @@ describe('dashboard payload validation', () => {
).toBe(false)
})
it('validates bounded launch choices and spawn requests', () => {
it('validates bounded launch choices', () => {
expect(
isDashboardSnapshot({
...SNAPSHOT,
@@ -290,10 +289,6 @@ describe('dashboard payload validation', () => {
})
).toBe(false)
expect(isDashboardSnapshot({ ...SNAPSHOT, launchableAgentsByWorktreeId: [] })).toBe(false)
expect(isDashboardSpawnAgentArgs({ worktreeId: 'worktree-1', agent: 'codex' })).toBe(true)
expect(isDashboardSpawnAgentArgs({ worktreeId: '', agent: 'codex' })).toBe(false)
expect(isDashboardSpawnAgentArgs({ worktreeId: 'worktree-1', agent: 'unknown' })).toBe(false)
})
it('bounds the conversation name', () => {
@@ -1,7 +1,6 @@
import {
DASHBOARD_MAX_LABEL_LENGTH,
type DashboardRevealAgentArgs,
type DashboardSleepWorkspaceArgs,
type DashboardSnapshot
} from '../../shared/dashboard-snapshot'
import { BoundedMap } from '../../shared/bounded-map'
@@ -19,7 +18,6 @@ import {
isDashboardWorkspaceList
} from './dashboard-workspace-payload-validation'
import { isDashboardFilterOptions } from './dashboard-filter-payload-validation'
export { isDashboardSpawnAgentArgs } from './dashboard-agent-launch-validation'
const MAX_DASHBOARD_CARDS = 1_000
const MAX_DASHBOARD_REPO_ICONS = 500
@@ -85,15 +83,6 @@ export function isDashboardPaneKey(value: unknown): value is string {
return isBoundedString(value, MAX_ID_LENGTH)
}
export function isDashboardSleepWorkspaceArgs(
value: unknown
): value is DashboardSleepWorkspaceArgs {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return false
}
return isBoundedString((value as Record<string, unknown>).worktreeId, MAX_ID_LENGTH)
}
export function isDashboardSnapshot(value: unknown): value is DashboardSnapshot {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return false
-25
View File
@@ -253,31 +253,6 @@ describe('registerDashboardPopoutHandlers', () => {
])
})
it('relays only valid agent launches from the popout', () => {
const args = { worktreeId: 'worktree-1', agent: 'codex' }
handlers.get('dashboardPopout:spawnAgent')!({ sender: untrustedSender } as never, args)
handlers.get('dashboardPopout:spawnAgent')!({ sender: popoutSender } as never, {
...args,
agent: 'unknown'
})
expect(sendToTrustedMock).not.toHaveBeenCalled()
handlers.get('dashboardPopout:spawnAgent')!({ sender: popoutSender } as never, args)
expect(sendToTrustedMock).toHaveBeenCalledWith('ui:spawnDashboardAgent', args)
})
it('relays only valid sleep requests from the popout', () => {
const args = { worktreeId: 'worktree-1' }
handlers.get('dashboardPopout:sleepWorkspace')!({ sender: untrustedSender } as never, args)
handlers.get('dashboardPopout:sleepWorkspace')!({ sender: popoutSender } as never, {
worktreeId: ''
})
expect(sendToTrustedMock).not.toHaveBeenCalled()
handlers.get('dashboardPopout:sleepWorkspace')!({ sender: popoutSender } as never, args)
expect(sendToTrustedMock).toHaveBeenCalledWith('ui:sleepDashboardWorkspace', args)
})
it('reveals an agent in only the trusted main window', () => {
const main = makeWindow(mainSender)
getTrustedWindowMock.mockReturnValue(main)
+1 -27
View File
@@ -15,9 +15,7 @@ import { getTrustedUIRendererWindow, isTrustedUIRenderer, sendToTrustedUIRendere
import {
admitDashboardSnapshot,
isDashboardPaneKey,
isDashboardRevealAgentArgs,
isDashboardSleepWorkspaceArgs,
isDashboardSpawnAgentArgs
isDashboardRevealAgentArgs
} from './dashboard-payload-validation'
// Replay immediately on mount; clear on close to avoid showing a previous session.
@@ -37,8 +35,6 @@ export function registerDashboardPopoutHandlers(
ipcMain.removeHandler('dashboard:getPopoutOpen')
ipcMain.removeHandler('dashboardPopout:revealAgent')
ipcMain.removeHandler('dashboardPopout:ackAgent')
ipcMain.removeHandler('dashboardPopout:spawnAgent')
ipcMain.removeHandler('dashboardPopout:sleepWorkspace')
onDashboardPopoutOpenChanged((open) => {
if (!open) {
@@ -141,26 +137,4 @@ export function registerDashboardPopoutHandlers(
}
}
})
ipcMain.handle('dashboardPopout:spawnAgent', (event, args: unknown): void => {
if (
!isDashboardPopoutRenderer(event.sender) ||
!isDashboardEnabled(store) ||
!isDashboardSpawnAgentArgs(args)
) {
return
}
sendToTrustedUIRenderer('ui:spawnDashboardAgent', args)
})
ipcMain.handle('dashboardPopout:sleepWorkspace', (event, args: unknown): void => {
if (
!isDashboardPopoutRenderer(event.sender) ||
!isDashboardEnabled(store) ||
!isDashboardSleepWorkspaceArgs(args)
) {
return
}
sendToTrustedUIRenderer('ui:sleepDashboardWorkspace', args)
})
}
+1 -10
View File
@@ -1,9 +1,4 @@
import type {
DashboardRevealAgentArgs,
DashboardSleepWorkspaceArgs,
DashboardSnapshot,
DashboardSpawnAgentArgs
} from '../../shared/dashboard-snapshot'
import type { DashboardRevealAgentArgs, DashboardSnapshot } from '../../shared/dashboard-snapshot'
import type {
TerminalPreviewConnectResult,
TerminalPreviewDataPayload
@@ -18,14 +13,10 @@ export type DashboardApi = {
onRevealAgent: (callback: (args: DashboardRevealAgentArgs) => void) => () => void
/** `explicit` only for a click on the card; watching an open dialog is a view. */
onAckAgent: (callback: (paneKey: string, intent: 'view' | 'explicit') => void) => () => void
onSpawnAgent: (callback: (args: DashboardSpawnAgentArgs) => void) => () => void
onSleepWorkspace: (callback: (args: DashboardSleepWorkspaceArgs) => void) => () => void
requestSnapshot: () => Promise<void>
onSnapshot: (callback: (snapshot: DashboardSnapshot) => void) => () => void
revealAgent: (args: DashboardRevealAgentArgs) => Promise<void>
ackAgent: (paneKey: string, intent: 'view' | 'explicit') => Promise<void>
spawnAgent: (args: DashboardSpawnAgentArgs) => Promise<void>
sleepWorkspace: (args: DashboardSleepWorkspaceArgs) => Promise<void>
}
export type TerminalPreviewApi = {
+2 -23
View File
@@ -1,10 +1,5 @@
import { ipcRenderer } from 'electron'
import type {
DashboardRevealAgentArgs,
DashboardSleepWorkspaceArgs,
DashboardSnapshot,
DashboardSpawnAgentArgs
} from '../../shared/dashboard-snapshot'
import type { DashboardRevealAgentArgs, DashboardSnapshot } from '../../shared/dashboard-snapshot'
import type { PreloadApi } from '../api-types'
export const dashboardApi = {
@@ -37,18 +32,6 @@ export const dashboardApi = {
ipcRenderer.on('ui:ackDashboardAgent', listener)
return () => ipcRenderer.removeListener('ui:ackDashboardAgent', listener)
},
onSpawnAgent: (callback: (args: DashboardSpawnAgentArgs) => void): (() => void) => {
const listener = (_event: Electron.IpcRendererEvent, args: DashboardSpawnAgentArgs): void =>
callback(args)
ipcRenderer.on('ui:spawnDashboardAgent', listener)
return () => ipcRenderer.removeListener('ui:spawnDashboardAgent', listener)
},
onSleepWorkspace: (callback: (args: DashboardSleepWorkspaceArgs) => void): (() => void) => {
const listener = (_event: Electron.IpcRendererEvent, args: DashboardSleepWorkspaceArgs): void =>
callback(args)
ipcRenderer.on('ui:sleepDashboardWorkspace', listener)
return () => ipcRenderer.removeListener('ui:sleepDashboardWorkspace', listener)
},
requestSnapshot: (): Promise<void> => ipcRenderer.invoke('dashboard:requestSnapshot'),
onSnapshot: (callback: (snapshot: DashboardSnapshot) => void): (() => void) => {
@@ -60,9 +43,5 @@ export const dashboardApi = {
revealAgent: (args: DashboardRevealAgentArgs): Promise<void> =>
ipcRenderer.invoke('dashboardPopout:revealAgent', args),
ackAgent: (paneKey: string, intent: 'view' | 'explicit'): Promise<void> =>
ipcRenderer.invoke('dashboardPopout:ackAgent', { paneKey, intent }),
spawnAgent: (args: DashboardSpawnAgentArgs): Promise<void> =>
ipcRenderer.invoke('dashboardPopout:spawnAgent', args),
sleepWorkspace: (args: DashboardSleepWorkspaceArgs): Promise<void> =>
ipcRenderer.invoke('dashboardPopout:sleepWorkspace', args)
ipcRenderer.invoke('dashboardPopout:ackAgent', { paneKey, intent })
} satisfies PreloadApi['dashboard']
@@ -1,49 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const mocks = vi.hoisted(() => ({
getKnownWorktreeById: vi.fn(),
setActiveWorktree: vi.fn(),
launchAgentInNewTab: vi.fn(),
getExecutionHostIdForWorktree: vi.fn()
}))
vi.mock('@/store', () => ({
useAppStore: {
getState: () => ({
settings: null,
getKnownWorktreeById: mocks.getKnownWorktreeById,
setActiveWorktree: mocks.setActiveWorktree
})
}
}))
vi.mock('@/lib/launch-agent-in-new-tab', () => ({
launchAgentInNewTab: mocks.launchAgentInNewTab
}))
vi.mock('@/lib/worktree-runtime-owner', () => ({
getExecutionHostIdForWorktree: mocks.getExecutionHostIdForWorktree
}))
import { launchDashboardAgent } from './launch-dashboard-agent'
describe('launchDashboardAgent', () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.getExecutionHostIdForWorktree.mockReturnValue('ssh:docs')
mocks.getKnownWorktreeById.mockReturnValue({ id: 'folder:docs' })
mocks.launchAgentInNewTab.mockReturnValue({
surface: { kind: 'local-terminal', tabId: 'tab-1' }
})
})
it('activates a folder or git workspace on its execution host before launching', () => {
expect(launchDashboardAgent({ worktreeId: 'folder:docs', agent: 'codex' })).toBe(true)
expect(mocks.getKnownWorktreeById).toHaveBeenCalledWith('folder:docs', 'ssh:docs')
expect(mocks.setActiveWorktree).toHaveBeenCalledWith('folder:docs', 'ssh:docs')
expect(mocks.launchAgentInNewTab).toHaveBeenCalledWith({
requestId: expect.any(String),
agent: 'codex',
worktreeId: 'folder:docs',
launchSource: 'unknown'
})
})
})
@@ -1,25 +0,0 @@
import { launchAgentInNewTab } from '@/lib/launch-agent-in-new-tab'
import { newAgentLaunchRequestId } from '@/lib/agent-launch-request-id'
import { useAppStore } from '@/store'
import { getExecutionHostIdForWorktree } from '@/lib/worktree-runtime-owner'
import type { DashboardSpawnAgentArgs } from '../../../../shared/dashboard-snapshot'
import { isTuiAgentEnabled } from '../../../../shared/tui-agent-selection'
/** Starts the requested agent through the same host-aware tab path as Quick Launch. */
export function launchDashboardAgent({ worktreeId, agent }: DashboardSpawnAgentArgs): boolean {
const state = useAppStore.getState()
const executionHostId = getExecutionHostIdForWorktree(state, worktreeId)
const worktree = state.getKnownWorktreeById(worktreeId, executionHostId)
if (!worktree || !isTuiAgentEnabled(agent, state.settings?.disabledTuiAgents)) {
return false
}
state.setActiveWorktree(worktreeId, executionHostId)
return (
launchAgentInNewTab({
requestId: newAgentLaunchRequestId(),
agent,
worktreeId,
launchSource: 'unknown'
}) !== null
)
}
@@ -8,11 +8,9 @@ import {
selectAgentLaunchRouteView
} from '@/lib/agent-launch-route-input'
import { revealDashboardAgent } from './reveal-dashboard-agent'
import { runSleepWorktree } from '../sidebar/sleep-worktree-flow'
import type { RepoIcon } from '../../../../shared/repo-icon'
import { buildDashboardSnapshot, type DashboardSnapshotState } from './build-dashboard-snapshot'
import { createWorktreeAgentRowsCache } from './worktree-agent-rows-cache'
import { launchDashboardAgent } from './launch-dashboard-agent'
import { selectWorktreesByRepo } from '@/store/workspace-record-selectors'
// Why: cap snapshot rebuilds during bursts of agent-status pings. The board is a
@@ -127,24 +125,6 @@ export function watchSnapshotInputs(onChanged: () => void): () => void {
* the agent's worktree and focus its pane in this (main) window.
*/
export function useDashboardPopoutBridge(enabled: boolean): void {
useEffect(() => {
if (!enabled) {
return
}
return window.api.dashboard.onSpawnAgent?.(launchDashboardAgent)
}, [enabled])
// Sleeping from the popout runs the shared teardown here, where the store and
// the live terminal panes are — the popout only names the workspace.
useEffect(() => {
if (!enabled) {
return
}
return window.api.dashboard.onSleepWorkspace?.(({ worktreeId }) => {
void runSleepWorktree(worktreeId)
})
}, [enabled])
useEffect(() => {
if (!enabled) {
return
@@ -74,7 +74,6 @@ const load = perClientLoader(async () => ({
quick: await import('@/lib/run-quick-command-in-new-tab'),
vault: await import('@/lib/launch-ai-vault-session'),
resume: await import('@/lib/ai-vault-resume-command'),
dashboard: await import('@/components/dashboard/launch-dashboard-agent'),
pane: await import('./pty-connection'),
paneOptions: await import('./terminal-pane-manager-options'),
defaults: await import('@/lib/pane-manager/pane-terminal-options'),
@@ -112,10 +111,6 @@ async function openTab(c: WindowLaunchCase) {
launchSource: 'terminal_context_menu',
initialCwd: producer.initialCwd
})
} else if (producer.kind === 'plus' && producer.via === 'dashboard') {
// Its workspace activation asks for a PR refresh, which this harness has no bridge for.
Object.assign(window.api, { gh: {} })
modules.dashboard.launchDashboardAgent({ worktreeId, agent: producer.agent })
} else if (producer.kind === 'plus') {
// Hand-built copies of what the "+" menu and the new-agent shortcut pass.
modules.launch.launchAgentInNewTab({
@@ -30,11 +30,6 @@ export type AgentLaunchCallerProfile = {
const PROMPT = 'Explain the failing check and propose a fix.'
export const AGENT_LAUNCH_CALLER_PROFILES: readonly AgentLaunchCallerProfile[] = [
{
id: 'dashboard-spawn',
caller: 'src/renderer/src/components/dashboard/launch-dashboard-agent.ts',
args: { agent: 'codex', worktreeId: 'wt-1', launchSource: 'unknown' }
},
{
id: 'empty-workspace-default-chat',
caller: 'src/renderer/src/lib/empty-workspace-default-agent-chat.ts',
-11
View File
@@ -223,14 +223,3 @@ export type DashboardRevealAgentArgs = {
tabId: string
leafId: string | null
}
export type DashboardSpawnAgentArgs = {
worktreeId: string
agent: TuiAgent
}
/** Puts a workspace to sleep from the dashboard: the main renderer owns the
* teardown sequence, so the pop-out only names the target. */
export type DashboardSleepWorkspaceArgs = {
worktreeId: string
}
@@ -1,5 +1,5 @@
// Test-only (never imported by production code): the window-lane launch parity table for row 1, the
// "+" launches C2 moves onto the pane trigger. One row per "+" entry point; local hosts only.
// "+" launches C2 moves onto the pane trigger. Covers each "+" entry point; local hosts only.
import { POSIX_PATH, WIN_PATH } from './launch-parity-window-cases.test-fixture'
import type {
LaunchClient,
@@ -55,5 +55,5 @@ export const PLUS_LAUNCH_CASES: WindowLaunchCase[] = [
},
{ shellOverride: 'powershell.exe', terminalWindowsWslDistro: null }
),
plus('dashboard', 'Linux repo', 'linux', { kind: 'repo', path: POSIX_PATH }, quoted(posix), ZSH)
plus('menu', 'Linux repo', 'linux', { kind: 'repo', path: POSIX_PATH }, quoted(posix), ZSH)
]
@@ -44,7 +44,7 @@ export type WindowLaunchProducer =
| { kind: 'folder-composer'; agent: 'claude' | 'codex'; note: string }
/** "+" entry points. Quick Launch items omit launchSource and get `menu`'s source by default. */
export type PlusEntry = 'menu' | 'shortcut' | 'dashboard'
export type PlusEntry = 'menu' | 'shortcut'
const PRODUCER_LAUNCH_SOURCE = {
'quick-command': 'quick_command',
continuation: 'terminal_context_menu',
@@ -53,8 +53,7 @@ const PRODUCER_LAUNCH_SOURCE = {
} as const
const PLUS_LAUNCH_SOURCE: Record<PlusEntry, string> = {
menu: 'tab_bar_quick_launch',
shortcut: 'shortcut',
dashboard: 'unknown'
shortcut: 'shortcut'
}
type ProjectRuntimeSent =