fix(cloud): CLOUD-CHAT-FIRST-TAB a create whose first agent opens as a chat seeds no shell in front of it (chat counts as the first surface; the create holds the seeder off while its chat opens)

This commit is contained in:
Neil
2026-11-13 07:22:17 -08:00
parent 881b82e401
commit caffab508b
4 changed files with 144 additions and 1 deletions
@@ -0,0 +1,35 @@
import { useAppStore } from '@/store/index'
import { getExecutionHostIdForWorktree } from '@/lib/workspace/ownership/worktree-runtime-owner'
import { parseExecutionHostId } from '../../../../../shared/execution-host'
import {
beginWebRuntimeInitialTerminalBootstrap,
endWebRuntimeInitialTerminalBootstrap
} from '@/runtime/web-session-tabs-sync/initial-terminal/web-runtime-initial-terminal-bootstrap'
/**
* CLOUD-CHAT-FIRST-TAB: a create on a paired host (an Orca Cloud VM) whose first agent opens as a
* chat claims the workspace's initial-terminal bootstrap while that chat launches, like the
* after-wake first agent does. Without it the mirror's first empty frame seeds a plain shell,
* activated, in front of the chat. Released once the launch has answered: by then the chat tab
* counts as the workspace's first surface, and a launch that opened nothing gets its shell.
*/
export function claimFirstSurfaceForStructuredCreate(worktreeId: string): () => void {
const host = parseExecutionHostId(
getExecutionHostIdForWorktree(useAppStore.getState(), worktreeId)
)
if (host?.kind !== 'runtime') {
return () => undefined
}
const { environmentId } = host
if (!beginWebRuntimeInitialTerminalBootstrap(environmentId, worktreeId)) {
// Someone else's create is already in flight; it owns the release.
return () => undefined
}
let released = false
return () => {
if (!released) {
released = true
endWebRuntimeInitialTerminalBootstrap(environmentId, worktreeId)
}
}
}
@@ -1,4 +1,5 @@
import { useAppStore } from '@/store/index'
import { claimFirstSurfaceForStructuredCreate } from './structured-create-first-surface-claim'
import { translate } from '@/i18n/i18n'
import { activateAndRevealWorktree } from '@/lib/workspace/activation/worktree-activation'
import type { ActivateAndRevealResult } from '@/lib/workspace/activation/worktree-activation-outcome'
@@ -49,6 +50,18 @@ type LaunchStructuredWorktreeSessionArgs = {
export async function launchStructuredWorktreeSession(
args: LaunchStructuredWorktreeSessionArgs
): Promise<WorktreeCreationStructuredSessionResult> {
// CLOUD-CHAT-FIRST-TAB: no seeded shell in front of the chat while it opens.
const releaseFirstSurface = claimFirstSurfaceForStructuredCreate(args.worktreeId)
try {
return await launchClaimedStructuredWorktreeSession(args)
} finally {
releaseFirstSurface()
}
}
async function launchClaimedStructuredWorktreeSession(
args: LaunchStructuredWorktreeSessionArgs
): Promise<WorktreeCreationStructuredSessionResult> {
let { activation, primaryTabId } = args
const settled = { accepted: true, cancelled: false }
@@ -0,0 +1,90 @@
// CLOUD-CHAT-FIRST-TAB (END-PASS n51): an Orca Cloud create whose first agent opens as a chat got a
// second plain "Terminal" tab, in front: the mirror's first empty frame saw no terminal tabs (a chat
// is not one) and seeded a shell. A chat now counts as the workspace's first surface, and the create
// holds the seeder off while its chat launches.
import { afterEach, describe, expect, it, vi } from 'vitest'
const state = vi.hoisted(() => ({
tabsByWorktree: {} as Record<string, unknown[]>,
unifiedTabsByWorktree: {} as Record<string, { id: string; contentType: string }[]>,
owner: 'runtime:env-vm' as string
}))
vi.mock('../../store', () => ({ useAppStore: { getState: () => state } }))
vi.mock('@/store/index', () => ({ useAppStore: { getState: () => state } }))
vi.mock('@/lib/workspace/ownership/worktree-runtime-owner', () => ({
getExecutionHostIdForWorktree: () => state.owner
}))
vi.mock('./frame-drawn-server-workspace-set', () => ({ isFrameDrawnServerWorkspace: () => false }))
vi.mock('./initial-terminal/web-runtime-initial-terminal-bootstrap-dispatch', () => ({
dispatchWebRuntimeInitialTerminalBootstrap: async () => true
}))
const { createInitialTerminalSeeder } = await import('./initial-terminal-seeder')
const { claimFirstSurfaceForStructuredCreate } =
await import('@/lib/workspace/creation/structured-create-first-surface-claim')
const { resetWebRuntimeInitialTerminalBootstrapForTests } =
await import('./initial-terminal/web-runtime-initial-terminal-bootstrap')
const WT = 'wt-cloud'
// The host's first published answer: the workspace has no tabs yet.
const emptyFrame = {
type: 'snapshot',
worktree: WT,
tabs: [],
publicationEpoch: 'epoch-1'
} as const
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the decision reads only type, worktree, tabs and publicationEpoch
const frame = emptyFrame as unknown as Parameters<
ReturnType<typeof createInitialTerminalSeeder>['decide']
>[0]
afterEach(() => {
state.tabsByWorktree = {}
state.unifiedTabsByWorktree = {}
state.owner = 'runtime:env-vm'
resetWebRuntimeInitialTerminalBootstrapForTests()
})
describe('the initial-terminal seeder beside a first chat', () => {
it('an empty new workspace still gets its shell', () => {
expect(createInitialTerminalSeeder('env-vm', WT).decide(frame, true)).toBe(true)
})
it('a workspace whose first tab is a chat gets no shell beside it', () => {
state.unifiedTabsByWorktree[WT] = [{ id: 'agent-session:s1', contentType: 'agent-session' }]
expect(createInitialTerminalSeeder('env-vm', WT).decide(frame, true)).toBe(false)
})
it('a non-chat unified tab (an editor) does not count as the first surface', () => {
state.unifiedTabsByWorktree[WT] = [{ id: 'editor:1', contentType: 'editor' }]
expect(createInitialTerminalSeeder('env-vm', WT).decide(frame, true)).toBe(true)
})
})
describe('claimFirstSurfaceForStructuredCreate', () => {
it('holds the seeder off while the chat launches, and lets go once it has answered', () => {
const release = claimFirstSurfaceForStructuredCreate(WT)
// The chat tab is not there yet: the empty frame must not seed a shell.
expect(createInitialTerminalSeeder('env-vm', WT).decide(frame, true)).toBe(false)
release()
// A launch that opened nothing gets its shell after all.
expect(createInitialTerminalSeeder('env-vm', WT).decide(frame, true)).toBe(true)
// Releasing twice is harmless.
release()
})
it('a create on this machine claims nothing', () => {
state.owner = 'local'
const release = claimFirstSurfaceForStructuredCreate(WT)
expect(createInitialTerminalSeeder('env-vm', WT).decide(frame, true)).toBe(true)
release()
})
it('a claim someone else holds is left to them', () => {
const first = claimFirstSurfaceForStructuredCreate(WT)
const second = claimFirstSurfaceForStructuredCreate(WT)
second()
expect(createInitialTerminalSeeder('env-vm', WT).decide(frame, true)).toBe(false)
first()
})
})
@@ -52,7 +52,12 @@ export function createInitialTerminalSeeder(
requestedInitialTerminal ||
isWebRuntimeInitialTerminalBootstrapInFlight(environmentId, worktreeId),
snapshotIsFresh: true,
localTerminalCount: (state.tabsByWorktree[worktreeId] ?? []).length,
// CLOUD-CHAT-FIRST-TAB: a chat is the workspace's first surface too; no shell is seeded beside it.
localTerminalCount:
(state.tabsByWorktree[worktreeId] ?? []).length +
(state.unifiedTabsByWorktree?.[worktreeId] ?? []).filter(
(tab) => tab.contentType === 'agent-session'
).length,
hasPersistedTerminalState: Object.hasOwn(state.tabsByWorktree, worktreeId),
firstHostAnswer
})