Fix task workspace handoff after switching workspaces (#8226)

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinwoo Hong
2026-07-10 22:56:33 -07:00
committed by GitHub
co-authored by Orca
parent 6a176b89fc
commit 5b1fcb0ff0
2 changed files with 71 additions and 5 deletions
@@ -504,6 +504,65 @@ describe('staged background worktree creation', () => {
})
})
it('reveals the completed workspace after the user switches to another workspace', async () => {
let resolveCreate!: (result: { worktree: { id: string; repoId: string } }) => void
store.createWorktree.mockReturnValueOnce(
new Promise((resolve) => {
resolveCreate = resolve
})
)
const started = continueBackgroundWorktreeCreation('creation-1', makeRequest(), {
revealCreationSurface: false
})
expect(started).toBe(true)
await vi.waitFor(() => expect(store.createWorktree).toHaveBeenCalledTimes(1))
// Why: selecting a real workspace clears only the pending surface pointer;
// completion should still finish the task-launch handoff once it is ready.
store.activePendingCreationId = null
resolveCreate({ worktree: { id: 'wt-1', repoId: 'repo-1' } })
await flushAsyncWorktreeCreation()
expect(activateAndRevealWorktree).toHaveBeenCalledWith('wt-1', {
sidebarRevealBehavior: 'auto'
})
expect(ensureWorktreeHasInitialTerminal).not.toHaveBeenCalled()
expect(store.removePendingWorktreeCreation).toHaveBeenCalledWith('creation-1', {
cleanupVm: false
})
})
it('does not reveal a workspace cancelled during post-create trust preflight', async () => {
let resolveTrust!: () => void
const markTrusted = vi.fn(
() =>
new Promise<void>((resolve) => {
resolveTrust = resolve
})
)
globalThis.window = { api: { agentTrust: { markTrusted } } } as never
store.repos = [{ id: 'repo-1', connectionId: null }]
store.createWorktree.mockResolvedValueOnce({
worktree: { id: 'wt-1', repoId: 'repo-1', path: '/repo/wt-1' }
})
const started = continueBackgroundWorktreeCreation(
'creation-1',
makeRequest({ agent: 'codex' }),
{ revealCreationSurface: false }
)
expect(started).toBe(true)
await vi.waitFor(() => expect(markTrusted).toHaveBeenCalledTimes(1))
delete store.pendingWorktreeCreations['creation-1']
store.activePendingCreationId = null
resolveTrust()
await vi.waitFor(() => expect(ensureWorktreeHasInitialTerminal).toHaveBeenCalledTimes(1))
expect(activateAndRevealWorktree).not.toHaveBeenCalled()
})
// Why: one-click "Start workspace from issue" commonly backgrounds, so the
// user-moved-on path is the common delivery for the repo's issue command; it
// must thread through as the 5th positional arg, not be dropped to undefined.
+12 -5
View File
@@ -214,13 +214,20 @@ async function executeWorktreeCreation(
await preflightAgentTrust(preparedRequest, worktree.path, repoConnectionId)
}
// `createWorktree` already inserted the real worktree row. Whether we steal
// the view depends on whether the user is still watching this creation.
const stillActive = isPendingCreationSurfaceVisible(creationId)
// `createWorktree` already inserted the real worktree row. Leaving for an app
// view keeps the create in the background, while selecting another workspace
// means the user still expects this task-launch handoff when it becomes ready;
// the entry guard prevents a late trust preflight from reviving a cancelled create.
const completionState = useAppStore.getState()
const shouldActivateOnCompletion =
completionState.pendingWorktreeCreations[creationId] !== undefined &&
(isPendingCreationSurfaceVisible(creationId) ||
(completionState.activeView === 'terminal' &&
completionState.activePendingCreationId === null))
let activation: ActivateAndRevealResult | false = false
let primaryTabId: string | null
if (stillActive) {
if (shouldActivateOnCompletion) {
activation = activateAndRevealWorktree(worktree.id, {
sidebarRevealBehavior: 'auto',
...(result.setup ? { setup: result.setup } : {}),
@@ -254,7 +261,7 @@ async function executeWorktreeCreation(
startup: preparedRequest.startupPlan
})
}
if (stillActive && !preparedRequest.suppressTerminalFocusOnCompletion) {
if (shouldActivateOnCompletion && !preparedRequest.suppressTerminalFocusOnCompletion) {
queueNewWorkspaceTerminalFocus(worktree.id, activation)
}