mirror of
https://github.com/stablyai/orca.git
synced 2026-09-28 08:02:43 +00:00
fix(workspaces): seed shells only for blank selection
This commit is contained in:
@@ -172,7 +172,7 @@ export async function createRuntimeFolderWorktree(args: {
|
||||
undefined,
|
||||
args.startup && !didSpawnStartup ? args.startup : undefined
|
||||
)
|
||||
} else if (deps.ptySpawnAvailable && !didSpawnStartup) {
|
||||
} else if (deps.ptySpawnAvailable && !didSpawnStartup && !args.createdWithAgent) {
|
||||
try {
|
||||
await deps.createTerminal(`id:${worktree.id}`, { surfaceOwner: false })
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { Repo } from '../../shared/repo-types'
|
||||
import type { Worktree } from '../../shared/worktree/types'
|
||||
import { startRuntimeLocalWorktreeTerminals } from './runtime-local-worktree-terminal-startup'
|
||||
|
||||
const repo: Repo = {
|
||||
id: 'repo-1',
|
||||
path: '/repo',
|
||||
displayName: 'repo',
|
||||
badgeColor: 'blue',
|
||||
addedAt: 1
|
||||
}
|
||||
|
||||
const worktree: Worktree = {
|
||||
id: 'worktree-1',
|
||||
repoId: repo.id,
|
||||
path: '/worktree',
|
||||
head: 'abc',
|
||||
branch: 'feature',
|
||||
isBare: false,
|
||||
isMainWorktree: false,
|
||||
displayName: 'feature',
|
||||
comment: '',
|
||||
linkedIssue: null,
|
||||
linkedPR: null,
|
||||
linkedLinearIssue: null,
|
||||
isArchived: false,
|
||||
isUnread: false,
|
||||
isPinned: false,
|
||||
sortOrder: 0,
|
||||
lastActivityAt: 1
|
||||
}
|
||||
|
||||
type StartupArgs = Parameters<typeof startRuntimeLocalWorktreeTerminals>[0]
|
||||
|
||||
function createPorts() {
|
||||
const createTerminal = vi.fn<StartupArgs['ports']['createTerminal']>().mockResolvedValue({
|
||||
handle: 'term-1',
|
||||
worktreeId: worktree.id,
|
||||
title: null
|
||||
})
|
||||
const ports: StartupArgs['ports'] = {
|
||||
canSpawn: true,
|
||||
markTrusted: vi.fn(),
|
||||
createTerminal,
|
||||
pasteDraft: vi.fn(),
|
||||
sendFollowup: vi.fn(),
|
||||
provision: vi.fn().mockResolvedValue({ setupSpawned: false, setupTerminalHandle: null }),
|
||||
activate: vi.fn()
|
||||
}
|
||||
return { createTerminal, ports }
|
||||
}
|
||||
|
||||
describe('startRuntimeLocalWorktreeTerminals default shell seeding', () => {
|
||||
it.each([
|
||||
['Blank Terminal', undefined, 1],
|
||||
['an agent', 'codex' as const, 0]
|
||||
])('seeds a background shell for %s selection only', async (_label, agent, expectedCalls) => {
|
||||
const { createTerminal, ports } = createPorts()
|
||||
|
||||
await startRuntimeLocalWorktreeTerminals({
|
||||
request: { repoSelector: `id:${repo.id}`, name: worktree.displayName },
|
||||
repo,
|
||||
worktree,
|
||||
...(agent ? { createdWithAgent: agent } : {}),
|
||||
ports
|
||||
})
|
||||
|
||||
expect(createTerminal).toHaveBeenCalledTimes(expectedCalls)
|
||||
if (expectedCalls > 0) {
|
||||
expect(createTerminal).toHaveBeenCalledWith(`id:${worktree.id}`, { surfaceOwner: false })
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -163,7 +163,7 @@ export async function startRuntimeLocalWorktreeTerminals(args: {
|
||||
didSpawnSetup = true
|
||||
}
|
||||
}
|
||||
} else if (ports.canSpawn) {
|
||||
} else if (ports.canSpawn && !args.createdWithAgent) {
|
||||
try {
|
||||
await ports.createTerminal(`id:${worktree.id}`, { surfaceOwner: false })
|
||||
} catch (error) {
|
||||
|
||||
@@ -222,7 +222,7 @@ export async function createRuntimeRemoteManagedWorktree(
|
||||
didSpawnSetup = true
|
||||
}
|
||||
}
|
||||
} else if (!shouldActivate && deps.canSpawn()) {
|
||||
} else if (!shouldActivate && deps.canSpawn() && !args.createdWithAgent) {
|
||||
try {
|
||||
await deps.createTerminal(`path:${result.worktree.path}`, { surfaceOwner: false })
|
||||
} catch (err) {
|
||||
|
||||
@@ -116,6 +116,7 @@ describe('submitFolderWorkspaceCreate', () => {
|
||||
})
|
||||
expect(onOpenChange).toHaveBeenCalledWith(false)
|
||||
expect(mocks.activateAndRevealFolderWorkspace).toHaveBeenCalledWith('folder-workspace-1', {
|
||||
agent: null,
|
||||
runtimeEnvironmentId: null
|
||||
})
|
||||
expect(consoleError).toHaveBeenCalledWith(
|
||||
@@ -532,6 +533,7 @@ describe('submitFolderWorkspaceCreate', () => {
|
||||
linkedTask: linkedWorkItem
|
||||
})
|
||||
expect(mocks.activateAndRevealFolderWorkspace).toHaveBeenCalledWith('folder-workspace-1', {
|
||||
agent: null,
|
||||
runtimeEnvironmentId: null
|
||||
})
|
||||
expect(mocks.ensureAgentStartupInTerminal).not.toHaveBeenCalled()
|
||||
@@ -659,6 +661,7 @@ describe('submitFolderWorkspaceCreate', () => {
|
||||
})
|
||||
expect(onOpenChange).toHaveBeenCalledWith(false)
|
||||
expect(mocks.activateAndRevealFolderWorkspace).toHaveBeenCalledWith('folder-workspace-1', {
|
||||
agent: null,
|
||||
runtimeEnvironmentId: null
|
||||
})
|
||||
})
|
||||
|
||||
@@ -207,6 +207,7 @@ export async function submitFolderWorkspaceCreate({
|
||||
onOpenChange(false)
|
||||
try {
|
||||
let activation = activateAndRevealFolderWorkspace(workspace.id, {
|
||||
agent: quickAgent,
|
||||
...(!structuredLaunch && startup ? { startup } : {}),
|
||||
...(structuredLaunch ? { providesInitialSurface: true } : {}),
|
||||
runtimeEnvironmentId
|
||||
@@ -229,6 +230,7 @@ export async function submitFolderWorkspaceCreate({
|
||||
connectionId: workspace.connectionId ?? projectGroup.connectionId
|
||||
})
|
||||
const fallbackActivation = activateAndRevealFolderWorkspace(workspace.id, {
|
||||
agent: quickAgent,
|
||||
...(startup ? { startup } : {}),
|
||||
runtimeEnvironmentId
|
||||
})
|
||||
|
||||
@@ -219,6 +219,7 @@ export function useFullCreationExecution(input: FullCreationExecutionInput) {
|
||||
|
||||
const initialActivation = activateAndRevealWorktree(worktree.id, {
|
||||
sidebarRevealBehavior: 'auto',
|
||||
agent: tuiAgent,
|
||||
setup: result.setup,
|
||||
defaultTabs: result.defaultTabs,
|
||||
issueCommand,
|
||||
@@ -229,6 +230,7 @@ export function useFullCreationExecution(input: FullCreationExecutionInput) {
|
||||
|
||||
const settlement = await settleFullCreationStructuredLaunch({
|
||||
plan: launchPlan,
|
||||
agent: tuiAgent,
|
||||
worktreeId: worktree.id,
|
||||
startup,
|
||||
pendingFirstAgentMessageRename,
|
||||
|
||||
@@ -36,6 +36,7 @@ const plan = (overrides: Partial<AgentSessionLaunchVerdict> = {}) =>
|
||||
|
||||
const baseArgs = {
|
||||
plan: plan(),
|
||||
agent: 'codex' as const,
|
||||
worktreeId: 'worktree-1',
|
||||
startup: { command: 'codex' } as never,
|
||||
pendingFirstAgentMessageRename: true,
|
||||
@@ -94,6 +95,7 @@ describe('settleFullCreationStructuredLaunch', () => {
|
||||
})
|
||||
expect(mocks.activateAndRevealWorktree).toHaveBeenCalledWith('worktree-1', {
|
||||
sidebarRevealBehavior: 'auto',
|
||||
agent: 'codex',
|
||||
createNewTerminalForStartup: true,
|
||||
startup: baseArgs.startup
|
||||
})
|
||||
|
||||
@@ -3,12 +3,14 @@ import type { WorktreeStartupPayload } from '@/lib/worktree-startup-payload'
|
||||
import { activateAndRevealWorktree } from '@/lib/worktree-activation'
|
||||
import type { StructuredAgentLaunchSettlement } from '@/lib/structured-agent-launch-settlement'
|
||||
import { activateStructuredAgentSessionById } from '@/lib/structured-agent-session-tab-activation'
|
||||
import type { TuiAgent } from '../../../../shared/tui-agent'
|
||||
|
||||
/** Full-create dialog: the structured launch plus what this flow did before structured chat
|
||||
* existed. Returns null when the plan's route is not structured. */
|
||||
export async function settleFullCreationStructuredLaunch(args: {
|
||||
/** Planned before the worktree existed; `worktreeId` names the one that was created. */
|
||||
plan: AgentSessionLaunchPlan
|
||||
agent: TuiAgent
|
||||
worktreeId: string
|
||||
startup: WorktreeStartupPayload | undefined
|
||||
pendingFirstAgentMessageRename: boolean
|
||||
@@ -27,6 +29,7 @@ export async function settleFullCreationStructuredLaunch(args: {
|
||||
}
|
||||
const activation = activateAndRevealWorktree(args.worktreeId, {
|
||||
sidebarRevealBehavior: 'auto',
|
||||
agent: args.agent,
|
||||
createNewTerminalForStartup: true,
|
||||
...(args.startup ? { startup: args.startup } : {})
|
||||
})
|
||||
|
||||
@@ -32,6 +32,23 @@ function seedClosedLastTerminal(worktreeId: string): void {
|
||||
}
|
||||
|
||||
describe('activating a workspace whose last terminal was closed', () => {
|
||||
it.each([
|
||||
['Blank Terminal', null, 1],
|
||||
['an agent', 'codex' as const, 0]
|
||||
])('seeds a default shell for %s selection only', (_label, agent, expectedTabCount) => {
|
||||
const worktree = makeWorktree()
|
||||
seedEmptyActivatableWorktree(worktree)
|
||||
|
||||
const result = activateAndRevealWorktree(worktree.id, {
|
||||
agent,
|
||||
notifyHostRuntime: false
|
||||
})
|
||||
|
||||
expect(result).not.toBe(false)
|
||||
expect(result === false ? null : result.primaryTabId === null).toBe(expectedTabCount === 0)
|
||||
expect(useAppStore.getState().tabsByWorktree[worktree.id] ?? []).toHaveLength(expectedTabCount)
|
||||
})
|
||||
|
||||
it.each([true, false])(
|
||||
'forwards providesInitialSurface=%s through the async activation gate',
|
||||
async (providesInitialSurface) => {
|
||||
@@ -234,6 +251,22 @@ function seedEmptiedFolderWorkspaceOnTwoHosts(): void {
|
||||
}
|
||||
|
||||
describe('activating a folder workspace whose last terminal was closed', () => {
|
||||
it.each([
|
||||
['Blank Terminal', null, 1],
|
||||
['an agent', 'codex' as const, 0]
|
||||
])('seeds a default shell for %s selection only', (_label, agent, expectedTabCount) => {
|
||||
seedEmptiedFolderWorkspaceOnTwoHosts()
|
||||
|
||||
const result = activateAndRevealFolderWorkspace(FOLDER_ID, {
|
||||
agent,
|
||||
executionHostId: 'local'
|
||||
})
|
||||
|
||||
expect(result).not.toBe(false)
|
||||
expect(useAppStore.getState().activeWorktreeId).toBe(FOLDER_KEY)
|
||||
expect(useAppStore.getState().tabsByWorktree[FOLDER_KEY]).toHaveLength(expectedTabCount)
|
||||
})
|
||||
|
||||
it.each([true, false])(
|
||||
'forwards providesInitialSurface=%s through the async activation gate',
|
||||
async (providesInitialSurface) => {
|
||||
|
||||
@@ -23,7 +23,10 @@ const SURFACE_PROVIDING_CALLERS = [
|
||||
]
|
||||
|
||||
// The activation seam itself: declares the option and forwards it into the tombstone gate.
|
||||
const SEAM_FILES = ['src/renderer/src/lib/worktree-activation.ts']
|
||||
const SEAM_FILES = [
|
||||
'src/renderer/src/lib/worktree-activation-surface-selection.ts',
|
||||
'src/renderer/src/lib/worktree-activation.ts'
|
||||
]
|
||||
|
||||
function listSourceFiles(dir: string): string[] {
|
||||
return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { TuiAgent } from '../../../shared/tui-agent'
|
||||
|
||||
export type WorktreeActivationSurfaceSelection = {
|
||||
/** The create picker's selection; null means Blank Terminal. */
|
||||
agent?: TuiAgent | null
|
||||
/** A navigation caller is about to open its own editor, diff, or other non-terminal surface. */
|
||||
providesInitialSurface?: boolean
|
||||
}
|
||||
|
||||
export function activationProvidesInitialSurface(
|
||||
selection?: WorktreeActivationSurfaceSelection
|
||||
): boolean {
|
||||
return selection?.providesInitialSurface === true || selection?.agent != null
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { activateAndRevealWorktree } from './worktree-activation'
|
||||
import { ensureWorktreeHasInitialTerminal } from './worktree-initial-terminal-seeding'
|
||||
import type { AppStoreState } from './worktree-activation-test-harness'
|
||||
import {
|
||||
@@ -6,9 +7,54 @@ import {
|
||||
registerWorktreeActivationReset
|
||||
} from './worktree-activation-test-harness'
|
||||
import { useAppStore } from '@/store'
|
||||
import {
|
||||
makeCreatedAgentWorktree,
|
||||
seedEmptyActivatableWorktree
|
||||
} from './worktree-activation-created-agent-test-state'
|
||||
import { resetWebRuntimeWakeTerminalRespawnForTests } from '@/runtime/web-runtime-wake-terminal-respawn'
|
||||
|
||||
registerWorktreeActivationReset()
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals()
|
||||
resetWebRuntimeWakeTerminalRespawnForTests()
|
||||
})
|
||||
|
||||
describe('activateAndRevealWorktree', () => {
|
||||
it('does not ask a paired host to seed a shell for an agent selection', async () => {
|
||||
const worktree = {
|
||||
...makeCreatedAgentWorktree(),
|
||||
hostId: 'local' as const,
|
||||
runtimeOwnerEnvironmentId: 'web-runtime-1'
|
||||
}
|
||||
const callRuntimeEnvironment = vi.fn().mockResolvedValue({
|
||||
ok: false,
|
||||
error: { code: 'test', message: 'stop after recording the request' }
|
||||
})
|
||||
;(globalThis as { __ORCA_WEB_CLIENT__?: boolean }).__ORCA_WEB_CLIENT__ = true
|
||||
vi.stubGlobal('window', {
|
||||
api: { runtimeEnvironments: { call: callRuntimeEnvironment } }
|
||||
})
|
||||
seedEmptyActivatableWorktree(worktree)
|
||||
useAppStore.setState((state) => ({
|
||||
settings: state.settings
|
||||
? { ...state.settings, activeRuntimeEnvironmentId: 'web-runtime-1' }
|
||||
: ({ activeRuntimeEnvironmentId: 'web-runtime-1' } as unknown as typeof state.settings)
|
||||
}))
|
||||
|
||||
activateAndRevealWorktree(worktree.id, { agent: 'codex' })
|
||||
await vi.waitFor(() =>
|
||||
expect(callRuntimeEnvironment).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ method: 'worktree.activate' })
|
||||
)
|
||||
)
|
||||
|
||||
expect(callRuntimeEnvironment).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({ method: 'session.tabs.createTerminal' })
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ensureWorktreeHasInitialTerminal', () => {
|
||||
it('does not create a local fallback tab in the paired web runtime client', () => {
|
||||
;(globalThis as { __ORCA_WEB_CLIENT__?: boolean }).__ORCA_WEB_CLIENT__ = true
|
||||
|
||||
@@ -36,6 +36,10 @@ import {
|
||||
} from '@/lib/worktree-initial-terminal-seeding'
|
||||
import { ensureWebRuntimeWorktreeTerminalAfterWake } from '@/lib/web-runtime-worktree-terminal-after-wake'
|
||||
import { applyWorktreeNavViewEntry } from '@/lib/worktree-nav-view-history-replay'
|
||||
import {
|
||||
activationProvidesInitialSurface,
|
||||
type WorktreeActivationSurfaceSelection
|
||||
} from './worktree-activation-surface-selection'
|
||||
|
||||
/**
|
||||
* Shared activation sequence used by the worktree palette and add-repo/worktree dialogs.
|
||||
@@ -80,14 +84,12 @@ function canInspectAgentActivationInventory(): boolean {
|
||||
|
||||
export function activateAndRevealFolderWorkspace(
|
||||
folderWorkspaceId: string,
|
||||
opts?: {
|
||||
opts?: WorktreeActivationSurfaceSelection & {
|
||||
sidebarRevealBehavior?: PendingSidebarWorktreeReveal['behavior']
|
||||
revealInSidebar?: boolean
|
||||
startup?: WorktreeStartupPayload
|
||||
runtimeEnvironmentId?: string | null
|
||||
executionHostId?: ExecutionHostId
|
||||
/** See activateAndRevealWorktree — same contract for folder workspaces. */
|
||||
providesInitialSurface?: boolean
|
||||
}
|
||||
): ActivateAndRevealResult | false {
|
||||
const state = useAppStore.getState()
|
||||
@@ -133,6 +135,7 @@ export function activateAndRevealFolderWorkspace(
|
||||
state.setActiveFolderWorkspace(folderWorkspaceId, opts?.executionHostId)
|
||||
|
||||
const workspaceKey = folderWorkspaceKey(folderWorkspaceId)
|
||||
const providesInitialSurface = activationProvidesInitialSurface(opts)
|
||||
state.markWorktreeVisited(workspaceKey)
|
||||
if (!state.isNavigatingHistory) {
|
||||
state.recordWorktreeVisit(workspaceKey)
|
||||
@@ -151,17 +154,13 @@ export function activateAndRevealFolderWorkspace(
|
||||
if (shouldGateAgentActivation) {
|
||||
void gateWorktreeAgentActivation(workspaceKey).then((outcome) => {
|
||||
if (outcome === 'empty') {
|
||||
reseedGatedEmptyWorkspace(workspaceKey, opts?.providesInitialSurface)
|
||||
reseedGatedEmptyWorkspace(workspaceKey, providesInitialSurface)
|
||||
}
|
||||
})
|
||||
}
|
||||
const primaryTabId = shouldGateAgentActivation
|
||||
? null
|
||||
: ensureFolderWorkspaceInitialTerminal(
|
||||
folderWorkspace,
|
||||
opts?.startup,
|
||||
opts?.providesInitialSurface
|
||||
)
|
||||
: ensureFolderWorkspaceInitialTerminal(folderWorkspace, opts?.startup, providesInitialSurface)
|
||||
|
||||
if (opts?.revealInSidebar !== false) {
|
||||
state.revealWorktreeInSidebar(
|
||||
@@ -175,7 +174,7 @@ export function activateAndRevealFolderWorkspace(
|
||||
|
||||
export function activateAndRevealWorktree(
|
||||
worktreeId: string,
|
||||
opts?: {
|
||||
opts?: WorktreeActivationSurfaceSelection & {
|
||||
startup?: WorktreeStartupPayload
|
||||
initialCwd?: string
|
||||
setup?: WorktreeSetupLaunch
|
||||
@@ -188,12 +187,6 @@ export function activateAndRevealWorktree(
|
||||
backendStartupTerminalSpawned?: boolean
|
||||
/** Install a preserved fallback startup beside setup/default terminals already seeded. */
|
||||
createNewTerminalForStartup?: boolean
|
||||
/** Set by callers that navigate here only to open their own non-terminal surface
|
||||
* (an editor file, a diff). Activation then leaves a closed-last-terminal workspace
|
||||
* empty instead of adding a shell the user never asked for. Caveat: on a
|
||||
* runtime-owned workspace with a live web session the host owns terminal creation,
|
||||
* so ensureWebRuntimeWorktreeTerminalAfterWake may still seed one (matches main). */
|
||||
providesInitialSurface?: boolean
|
||||
/** Keep sidebar filters intact when navigating to a hidden target. */
|
||||
clearSidebarFilters?: boolean
|
||||
}
|
||||
@@ -206,6 +199,7 @@ export function activateAndRevealWorktree(
|
||||
const hasActivationWork = Boolean(
|
||||
opts?.startup || opts?.setup || opts?.defaultTabs || opts?.issueCommand
|
||||
)
|
||||
const providesInitialSurface = activationProvidesInitialSurface(opts)
|
||||
// Why: a plain reselect should still reveal the sidebar row but must not restamp focus recency or wake persistence.
|
||||
const isPlainAlreadyActiveTerminal =
|
||||
!hasActivationWork &&
|
||||
@@ -266,7 +260,7 @@ export function activateAndRevealWorktree(
|
||||
if (shouldGateAgentActivation) {
|
||||
void gateWorktreeAgentActivation(worktreeId).then((outcome) => {
|
||||
if (outcome === 'empty') {
|
||||
reseedGatedEmptyWorkspace(worktreeId, opts?.providesInitialSurface)
|
||||
reseedGatedEmptyWorkspace(worktreeId, providesInitialSurface)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -274,7 +268,7 @@ export function activateAndRevealWorktree(
|
||||
// 4. Ensure a focusable surface exists for externally-created worktrees
|
||||
const primaryTabId = shouldGateAgentActivation
|
||||
? null
|
||||
: opts?.providesInitialSurface === true && !hasActivationWork
|
||||
: providesInitialSurface && !hasActivationWork
|
||||
? null
|
||||
: ensureWorktreeHasInitialTerminal(
|
||||
useAppStore.getState(),
|
||||
@@ -286,8 +280,8 @@ export function activateAndRevealWorktree(
|
||||
{
|
||||
...(opts?.backendStartupTerminalSpawned ? { backendStartupTerminalSpawned: true } : {}),
|
||||
...(opts?.createNewTerminalForStartup ? { createNewTerminalForStartup: true } : {}),
|
||||
...(opts?.providesInitialSurface === true ? { callerProvidesSurface: true } : {}),
|
||||
reseedEmptiedWorkspace: opts?.providesInitialSurface !== true
|
||||
...(providesInitialSurface ? { callerProvidesSurface: true } : {}),
|
||||
reseedEmptiedWorkspace: !providesInitialSurface
|
||||
}
|
||||
)
|
||||
if (primaryTabId && opts?.initialCwd) {
|
||||
@@ -325,7 +319,11 @@ export function activateAndRevealWorktree(
|
||||
}
|
||||
}
|
||||
|
||||
if (opts?.notifyHostRuntime !== false && !opts?.backendStartupTerminalSpawned) {
|
||||
if (
|
||||
opts?.notifyHostRuntime !== false &&
|
||||
!opts?.backendStartupTerminalSpawned &&
|
||||
opts?.agent == null
|
||||
) {
|
||||
ensureWebRuntimeWorktreeTerminalAfterWake(worktreeId)
|
||||
}
|
||||
|
||||
@@ -340,9 +338,8 @@ export function activateAndRevealWorktree(
|
||||
*/
|
||||
export function activateAndRevealWorkspace(
|
||||
workspaceId: string,
|
||||
opts?: {
|
||||
opts?: WorktreeActivationSurfaceSelection & {
|
||||
executionHostId?: ExecutionHostId
|
||||
providesInitialSurface?: boolean
|
||||
revealInSidebar?: boolean
|
||||
/** Worktree-only: folder workspaces are never filter-hidden. */
|
||||
clearSidebarFilters?: boolean
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { WorktreeCreationRequest } from './pending-worktree-creation'
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
activateAndRevealWorktree: vi.fn(),
|
||||
completeWorktreeCreation: vi.fn(),
|
||||
ensureWorktreeHasInitialTerminal: vi.fn()
|
||||
}))
|
||||
|
||||
const store = {
|
||||
activePendingCreationId: null as string | null,
|
||||
activeView: 'tasks' as 'tasks' | 'terminal',
|
||||
createWorktree: vi.fn(),
|
||||
pendingWorktreeCreations: {} as Record<string, unknown>,
|
||||
repos: []
|
||||
}
|
||||
|
||||
vi.mock('@/store', () => ({ useAppStore: { getState: () => store } }))
|
||||
vi.mock('@/lib/worktree-activation', () => ({
|
||||
activateAndRevealWorktree: mocks.activateAndRevealWorktree
|
||||
}))
|
||||
vi.mock('@/lib/worktree-initial-terminal-seeding', () => ({
|
||||
ensureWorktreeHasInitialTerminal: mocks.ensureWorktreeHasInitialTerminal
|
||||
}))
|
||||
vi.mock('@/lib/worktree-creation-completion', () => ({
|
||||
completeWorktreeCreation: mocks.completeWorktreeCreation
|
||||
}))
|
||||
|
||||
import { executeWorktreeCreation } from './worktree-creation-flow-execute'
|
||||
|
||||
const request: WorktreeCreationRequest = {
|
||||
repoId: 'repo-1',
|
||||
name: 'feature',
|
||||
setupDecision: 'inherit',
|
||||
agent: 'codex',
|
||||
agentLaunchRoute: 'terminal-tui',
|
||||
pendingFirstAgentMessageRename: false,
|
||||
note: '',
|
||||
startupPlan: null,
|
||||
quickPrompt: '',
|
||||
quickTelemetry: null
|
||||
}
|
||||
|
||||
describe('executeWorktreeCreation agent seeding', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
store.activePendingCreationId = null
|
||||
store.activeView = 'tasks'
|
||||
store.pendingWorktreeCreations = { 'creation-1': { creationId: 'creation-1' } }
|
||||
store.createWorktree.mockResolvedValue({
|
||||
worktree: { id: 'worktree-1', repoId: request.repoId }
|
||||
})
|
||||
})
|
||||
|
||||
it('does not seed a background shell when the request carries an agent selection', async () => {
|
||||
await executeWorktreeCreation('creation-1', request)
|
||||
|
||||
expect(mocks.activateAndRevealWorktree).not.toHaveBeenCalled()
|
||||
expect(mocks.ensureWorktreeHasInitialTerminal).not.toHaveBeenCalled()
|
||||
expect(mocks.completeWorktreeCreation).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ primaryTabId: null })
|
||||
)
|
||||
})
|
||||
|
||||
it('passes the agent selection through an active reveal', async () => {
|
||||
store.activePendingCreationId = 'creation-1'
|
||||
store.activeView = 'terminal'
|
||||
mocks.activateAndRevealWorktree.mockReturnValue({ primaryTabId: null })
|
||||
|
||||
await executeWorktreeCreation('creation-1', request)
|
||||
|
||||
expect(mocks.activateAndRevealWorktree).toHaveBeenCalledWith(
|
||||
'worktree-1',
|
||||
expect.objectContaining({ agent: 'codex' })
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -168,6 +168,7 @@ export async function executeWorktreeCreation(
|
||||
if (shouldActivateOnCompletion && !structuredLaunch) {
|
||||
activation = activateAndRevealWorktree(worktree.id, {
|
||||
sidebarRevealBehavior: 'auto',
|
||||
...(preparedRequest.agent !== null ? { agent: preparedRequest.agent } : {}),
|
||||
...(result.setup ? { setup: result.setup } : {}),
|
||||
...(result.defaultTabs ? { defaultTabs: result.defaultTabs } : {}),
|
||||
...(startupOpt ? { startup: startupOpt } : {}),
|
||||
@@ -181,7 +182,7 @@ export async function executeWorktreeCreation(
|
||||
startupOpt || result.setup || preparedRequest.issueCommand || result.defaultTabs
|
||||
)
|
||||
primaryTabId =
|
||||
structuredLaunch && !hasExplicitTerminalWork
|
||||
preparedRequest.agent !== null && !hasExplicitTerminalWork
|
||||
? null
|
||||
: ensureWorktreeHasInitialTerminal(
|
||||
useAppStore.getState(),
|
||||
@@ -192,7 +193,7 @@ export async function executeWorktreeCreation(
|
||||
result.defaultTabs,
|
||||
{
|
||||
activateCreatedTabs: false,
|
||||
...(structuredLaunch ? { callerProvidesSurface: true } : {}),
|
||||
...(preparedRequest.agent !== null ? { callerProvidesSurface: true } : {}),
|
||||
...(backendSpawned ? { backendStartupTerminalSpawned: true } : {})
|
||||
}
|
||||
)
|
||||
|
||||
@@ -606,7 +606,7 @@ describe('staged background worktree creation', () => {
|
||||
delete store.pendingWorktreeCreations['creation-1']
|
||||
store.activePendingCreationId = null
|
||||
resolveTrust()
|
||||
await vi.waitFor(() => expect(ensureWorktreeHasInitialTerminal).toHaveBeenCalledTimes(1))
|
||||
await vi.waitFor(() => expect(store.removePendingWorktreeCreation).toHaveBeenCalled())
|
||||
|
||||
expect(activateAndRevealWorktree).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user