From ab92bbcda3b9c611b008f5e766edf0de36d68d11 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sat, 5 Sep 2026 14:01:40 -0700 Subject: [PATCH 1/2] fix: reseed emptied workspaces after agent inventory settles --- ...ctivation-emptied-workspace-reseed.test.ts | 21 ++++++++++ src/renderer/src/lib/worktree-activation.ts | 38 +++++++------------ .../lib/worktree-initial-terminal-seeding.ts | 24 ++++++++++++ ...completed-worker-retirement-resume.spec.ts | 2 +- 4 files changed, 59 insertions(+), 26 deletions(-) diff --git a/src/renderer/src/lib/worktree-activation-emptied-workspace-reseed.test.ts b/src/renderer/src/lib/worktree-activation-emptied-workspace-reseed.test.ts index 85fa0c0428c..705f31341ef 100644 --- a/src/renderer/src/lib/worktree-activation-emptied-workspace-reseed.test.ts +++ b/src/renderer/src/lib/worktree-activation-emptied-workspace-reseed.test.ts @@ -26,6 +26,27 @@ function seedClosedLastTerminal(worktreeId: string): void { } describe('activating a workspace whose last terminal was closed', () => { + it('re-seeds after the asynchronous agent inventory confirms the workspace is empty', async () => { + const worktree = makeWorktree() + seedEmptyActivatableWorktree(worktree) + seedClosedLastTerminal(worktree.id) + useAppStore.setState({ + workspaceSessionReady: true, + terminalStartupRestorationReady: true + }) + vi.stubGlobal('window', { + api: { + runtime: { call: vi.fn(async () => ({ ok: true, result: { snapshots: [] } })) }, + pty: { listSessions: vi.fn(async () => []) } + } + }) + + activateAndRevealWorktree(worktree.id, { notifyHostRuntime: false }) + await waitForWorktreeAgentActivationGateForTests(worktree.id) + + expect(useAppStore.getState().tabsByWorktree[worktree.id]).toHaveLength(1) + }) + it('re-seeds a terminal when the workspace is opened from elsewhere', () => { const worktree = makeWorktree() seedEmptyActivatableWorktree(worktree) diff --git a/src/renderer/src/lib/worktree-activation.ts b/src/renderer/src/lib/worktree-activation.ts index fbb464fda49..539d3b7e35c 100644 --- a/src/renderer/src/lib/worktree-activation.ts +++ b/src/renderer/src/lib/worktree-activation.ts @@ -1,4 +1,3 @@ -import type { FolderWorkspace } from '../../../shared/folder-workspace-types' import type { WorktreeDefaultTabsLaunch, WorktreeSetupLaunch @@ -30,7 +29,10 @@ import type { ExecutionHostId } from '../../../shared/execution-host' import { findFolderWorkspaceOwner } from './folder-workspace-runtime-owner' import type { WorktreeStartupPayload } from '@/lib/worktree-startup-payload' import type { IssueCommandLaunch } from '@/lib/worktree-setup-issue-command-queue' -import { ensureWorktreeHasInitialTerminal } from '@/lib/worktree-initial-terminal-seeding' +import { + ensureFolderWorkspaceInitialTerminal, + ensureWorktreeHasInitialTerminal +} 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' @@ -45,28 +47,6 @@ export type ActivateAndRevealResult = { primaryTabId: string | null } -function ensureFolderWorkspaceInitialTerminal( - folderWorkspace: FolderWorkspace, - startup?: WorktreeStartupPayload, - providesInitialSurface?: boolean -): string | null { - if (providesInitialSurface === true && startup === undefined) { - return null - } - const state = useAppStore.getState() - const workspaceKey = folderWorkspaceKey(folderWorkspace.id) - const primaryTabId = ensureWorktreeHasInitialTerminal( - state, - workspaceKey, - startup, - undefined, - undefined, - undefined, - { reseedEmptiedWorkspace: providesInitialSurface !== true } - ) - return primaryTabId -} - function canInspectAgentActivationInventory(): boolean { return ( typeof window !== 'undefined' && @@ -270,7 +250,15 @@ export function activateAndRevealWorktree( opts?.providesInitialSurface !== true && currentState.activeWorktreeId === worktreeId ) { - ensureWorktreeHasInitialTerminal(currentState, worktreeId) + ensureWorktreeHasInitialTerminal( + currentState, + worktreeId, + undefined, + undefined, + undefined, + undefined, + { reseedEmptiedWorkspace: true } + ) } }) } diff --git a/src/renderer/src/lib/worktree-initial-terminal-seeding.ts b/src/renderer/src/lib/worktree-initial-terminal-seeding.ts index f2057537565..0290023528b 100644 --- a/src/renderer/src/lib/worktree-initial-terminal-seeding.ts +++ b/src/renderer/src/lib/worktree-initial-terminal-seeding.ts @@ -1,3 +1,5 @@ +import type { FolderWorkspace } from '../../../shared/folder-workspace-types' +import { folderWorkspaceKey } from '../../../shared/workspace-scope' import type { WorktreeDefaultTabsLaunch, WorktreeSetupLaunch @@ -214,3 +216,25 @@ export function ensureWorktreeHasInitialTerminal( return terminalTab.id } + +export function ensureFolderWorkspaceInitialTerminal( + folderWorkspace: FolderWorkspace, + startup?: WorktreeStartupPayload, + providesInitialSurface?: boolean +): string | null { + if (providesInitialSurface === true && startup === undefined) { + return null + } + const state = useAppStore.getState() + const workspaceKey = folderWorkspaceKey(folderWorkspace.id) + const primaryTabId = ensureWorktreeHasInitialTerminal( + state, + workspaceKey, + startup, + undefined, + undefined, + undefined, + { reseedEmptiedWorkspace: providesInitialSurface !== true } + ) + return primaryTabId +} diff --git a/tests/e2e/completed-worker-retirement-resume.spec.ts b/tests/e2e/completed-worker-retirement-resume.spec.ts index 69f6e0af375..6935cbe1895 100644 --- a/tests/e2e/completed-worker-retirement-resume.spec.ts +++ b/tests/e2e/completed-worker-retirement-resume.spec.ts @@ -269,7 +269,7 @@ for (const closeMode of ['terminal-close-cli', 'worker-release'] as const) { const expectedRecovery = { origin: 'live', - state: 'working', + state: 'done', providerSessionId: PROVIDER_SESSION_ID } await expect From e97d3c53082acb0138df35b5bf98018832c589a3 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sat, 5 Sep 2026 14:57:16 -0700 Subject: [PATCH 2/2] test: include terminal seeding in activation seam census --- .../lib/worktree-activation-surface-caller-wiring.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/lib/worktree-activation-surface-caller-wiring.test.ts b/src/renderer/src/lib/worktree-activation-surface-caller-wiring.test.ts index 419e642adcb..dcfcffc5325 100644 --- a/src/renderer/src/lib/worktree-activation-surface-caller-wiring.test.ts +++ b/src/renderer/src/lib/worktree-activation-surface-caller-wiring.test.ts @@ -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.ts', + 'src/renderer/src/lib/worktree-initial-terminal-seeding.ts' +] function listSourceFiles(dir: string): string[] { return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {