mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
refactor(dashboard): remove unreachable popout spawn/sleep IPC (#32954)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,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 = {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 =
|
||||
|
||||
Reference in New Issue
Block a user