refactor(terminal): publish new tabs together with their startup command (#28848)

Seven call sites created a terminal tab and then queued its startup command
in a second store write. Pass the command through createTab's existing
pendingStartup option instead, so the tab and its command land in one write.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jinwoo Hong
2026-10-14 17:15:46 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent a93a236577
commit c8f080764b
26 changed files with 271 additions and 254 deletions
@@ -62,7 +62,28 @@ export function registerTerminalRequestIpcBridge(unsubs: (() => void)[]): void {
recordInteraction: false,
...(data.cwd ? { startupCwd: data.cwd } : {})
}
const tab = store.createTab(worktreeId, data.targetGroupId, data.shellOverride, tabOptions)
const pendingStartup = data.command
? {
command: data.command,
...(data.env ? { env: data.env } : {}),
...(data.envToDelete ? { envToDelete: data.envToDelete } : {}),
...(data.launchConfig ? { launchConfig: data.launchConfig } : {}),
...(data.resumeProviderSession
? { resumeProviderSession: data.resumeProviderSession }
: {}),
...(data.launchToken ? { launchToken: data.launchToken } : {}),
...(data.launchAgent ? { launchAgent: data.launchAgent } : {}),
...(data.startupCommandDelivery
? { startupCommandDelivery: data.startupCommandDelivery }
: {})
}
: undefined
const tab = store.createTab(
worktreeId,
data.targetGroupId,
data.shellOverride,
pendingStartup ? { ...tabOptions, pendingStartup } : tabOptions
)
if (!shouldActivate) {
// Why: renderer-backed Codex startup must mount its new TerminalPane without switching UI or connecting every saved tab.
requestBackgroundTerminalWorktreeMount({ worktreeId, tabIds: [tab.id] })
@@ -89,22 +110,6 @@ export function registerTerminalRequestIpcBridge(unsubs: (() => void)[]): void {
if (data.title) {
store.setTabCustomTitle(tab.id, data.title, { recordInteraction: false })
}
if (data.command) {
store.queueTabStartupCommand(tab.id, {
command: data.command,
...(data.env ? { env: data.env } : {}),
...(data.envToDelete ? { envToDelete: data.envToDelete } : {}),
...(data.launchConfig ? { launchConfig: data.launchConfig } : {}),
...(data.resumeProviderSession
? { resumeProviderSession: data.resumeProviderSession }
: {}),
...(data.launchToken ? { launchToken: data.launchToken } : {}),
...(data.launchAgent ? { launchAgent: data.launchAgent } : {}),
...(data.startupCommandDelivery
? { startupCommandDelivery: data.startupCommandDelivery }
: {})
})
}
window.api.ui.replyTerminalCreate({
requestId: data.requestId,
tabId: tab.id,
@@ -231,7 +231,15 @@ describe('useIpcEvents updater integration', () => {
activate: false,
recordInteraction: false,
launchAgent: 'codex',
startupCwd: '/repo/packages/app'
startupCwd: '/repo/packages/app',
pendingStartup: {
command: 'codex',
launchConfig: {
agentArgs: '--model gpt-5',
agentEnv: { CODEX_PROFILE: 'request' }
},
launchAgent: 'codex'
}
})
expect(setActiveView).not.toHaveBeenCalled()
expect(setActiveWorktree).not.toHaveBeenCalled()
@@ -251,14 +259,7 @@ describe('useIpcEvents updater integration', () => {
expect(setTabCustomTitle).toHaveBeenCalledWith('tab-new', 'Codex', {
recordInteraction: false
})
expect(queueTabStartupCommand).toHaveBeenCalledWith('tab-new', {
command: 'codex',
launchConfig: {
agentArgs: '--model gpt-5',
agentEnv: { CODEX_PROFILE: 'request' }
},
launchAgent: 'codex'
})
expect(queueTabStartupCommand).not.toHaveBeenCalled()
expect(replyTerminalCreate).toHaveBeenCalledWith({
requestId: 'req-renderer-backed',
tabId: 'tab-new',
@@ -282,7 +283,8 @@ describe('useIpcEvents updater integration', () => {
expect(createTab).toHaveBeenCalledWith('wt-2', 'group-left', undefined, {
launchAgent: 'codex',
viewMode: 'terminal'
viewMode: 'terminal',
pendingStartup: { command: 'codex', launchAgent: 'codex' }
})
expect(replyTerminalCreate).toHaveBeenCalledWith({
requestId: 'req-runtime-session',
@@ -444,7 +446,8 @@ describe('useIpcEvents updater integration', () => {
expect(createTab).toHaveBeenCalledWith('wt-2', undefined, undefined, {
activate: false,
recordInteraction: false
recordInteraction: false,
pendingStartup: { command: 'codex' }
})
expect(setActiveView).not.toHaveBeenCalled()
expect(setActiveWorktree).not.toHaveBeenCalled()
@@ -12,6 +12,7 @@ import {
createdTabOptions,
createLaunchFunnelStore,
hostLaunchRequest,
queuedStartupCommand,
queuedStartupPayload,
resetLaunchFunnelStore
} from './agent-launch-funnel-test-harness'
@@ -143,10 +144,6 @@ describe('agent launch caller placement and telemetry', () => {
'tab-1',
profile.args.initialCwd
)
// Ordering matters: the pane snapshots the queued cwd on first render.
expect(store.queueTabInitialCwd.mock.invocationCallOrder[0]).toBeLessThan(
store.queueTabStartupCommand.mock.invocationCallOrder[0] ?? Number.MAX_SAFE_INTEGER
)
return
}
expect(store.queueTabInitialCwd).not.toHaveBeenCalled()
@@ -173,16 +170,15 @@ describe('agent launch caller placement and telemetry', () => {
})
})
it('creates the tab before queueing its startup command', async () => {
it('publishes the tab together with its startup command', async () => {
const { launchAgentInNewTab } = await import('./launch-agent-in-new-tab')
launchAgentInNewTab({ requestId: 'request-2', agent: 'codex', worktreeId: 'wt-1' })
// Why: the terminal pane snapshots pending startup in useState on first render, so a startup
// queued after mount is never seen.
expect(store.createTab.mock.invocationCallOrder[0]).toBeLessThan(
store.queueTabStartupCommand.mock.invocationCallOrder[0] ?? 0
)
// Why: the terminal pane snapshots pending startup in useState on first render, so the tab
// must never exist without its startup command.
expect(store.createTab).toHaveBeenCalledTimes(1)
expect(queuedStartupCommand(store)).toEqual(expect.any(String))
})
it('seeds working status for a Command Code prompt that rides argv', async () => {
@@ -11,7 +11,11 @@ import {
callerProfileCases,
type AgentLaunchCallerProfile
} from './agent-launch-caller-profiles-test-harness'
import { createLaunchFunnelStore, resetLaunchFunnelStore } from './agent-launch-funnel-test-harness'
import {
createLaunchFunnelStore,
queuedStartupCommand,
resetLaunchFunnelStore
} from './agent-launch-funnel-test-harness'
import type * as LaunchAdmissionModule from './structured-agent-session-launch-admission'
const store = createLaunchFunnelStore()
@@ -270,10 +274,7 @@ describe('agent launch caller routing', () => {
await expect(result?.structuredSettlement).resolves.toEqual({ kind: 'terminal' })
expect(store.createTab).toHaveBeenCalledOnce()
expect(store.createTab.mock.calls[0]?.slice(0, 2)).toEqual(['wt-1', 'group-1'])
expect(store.queueTabStartupCommand).toHaveBeenCalledOnce()
expect(store.queueTabStartupCommand.mock.calls[0]?.[1]?.command).toBe(
"claude '--model' 'sonnet'"
)
expect(queuedStartupCommand(store)).toBe("claude '--model' 'sonnet'")
// The typed prompt goes to that terminal once, and the caller hears it arrived.
await expect(result?.promptDeliveryResult).resolves.toEqual({
delivered: true,
@@ -339,7 +340,6 @@ describe('agent launch caller routing', () => {
expect(result).toBeNull()
expect(beforeSurfaceOpen).toHaveBeenCalledExactlyOnceWith({ kind: 'local-terminal' })
expect(store.createTab).not.toHaveBeenCalled()
expect(store.queueTabStartupCommand).not.toHaveBeenCalled()
})
it('aborts a host-published open when beforeSurfaceOpen refuses', async () => {
@@ -45,7 +45,6 @@ export function createLaunchFunnelStore(): {
tabBarOrderByWorktree: Record<string, string[]>
createTab: ReturnType<typeof vi.fn>
queueTabInitialCwd: ReturnType<typeof vi.fn>
queueTabStartupCommand: ReturnType<typeof vi.fn>
setActiveTabType: ReturnType<typeof vi.fn>
setTabBarOrder: ReturnType<typeof vi.fn>
setAgentStatus: ReturnType<typeof vi.fn>
@@ -65,7 +64,6 @@ export function createLaunchFunnelStore(): {
tabBarOrderByWorktree: {},
createTab: vi.fn(),
queueTabInitialCwd: vi.fn(),
queueTabStartupCommand: vi.fn(),
setActiveTabType: vi.fn(),
setTabBarOrder: vi.fn(),
setAgentStatus: vi.fn(),
@@ -117,16 +115,18 @@ export function resetLaunchFunnelStore(
store.createTab.mockImplementation(createTabInto(store))
}
/** The startup command `queueTabStartupCommand` received, or undefined when no tab was queued. */
/** The startup command the window planned for the new tab, or undefined when the host plans it. */
export function queuedStartupCommand(store: LaunchFunnelStore): string | undefined {
return store.queueTabStartupCommand.mock.calls[0]?.[1]?.command
return queuedStartupPayload(store)?.command
}
/** The whole startup-command payload, for callers that assert more than the command string. */
/** The whole window-planned startup payload, for callers that assert more than the command string. */
export function queuedStartupPayload(
store: LaunchFunnelStore
): Record<string, unknown> | undefined {
return store.queueTabStartupCommand.mock.calls[0]?.[1]
): { command?: string; [key: string]: unknown } | undefined {
const options: { pendingStartup?: { command?: string; agentLaunch?: unknown } } =
store.createTab.mock.calls[0]?.[3] ?? {}
return options.pendingStartup?.agentLaunch ? undefined : options.pendingStartup
}
/** The tab-creation options object (`createTab`'s 4th argument). */
@@ -4,7 +4,10 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const mockCreateTab = vi.fn()
const mockQueueTabStartupCommand = vi.fn()
/** Startup commands published with the created tabs, in creation order. */
function queuedStartups() {
return mockCreateTab.mock.calls.flatMap((call) => call[3]?.pendingStartup ?? [])
}
type StoreRepo = {
id: string
@@ -57,7 +60,6 @@ const store = {
ptyIdsByTabId: {} as Record<string, string[]>,
createTab: mockCreateTab,
closeTab: vi.fn(),
queueTabStartupCommand: mockQueueTabStartupCommand,
setActiveTabType: vi.fn(),
setTabBarOrder: vi.fn(),
setAgentStatus: vi.fn(),
@@ -112,7 +114,7 @@ async function launchOnLinux(): Promise<void> {
}
function queuedCommand(): string {
return mockQueueTabStartupCommand.mock.calls[0]?.[1]?.command
return queuedStartups()[0]?.command
}
describe('launchAgentInNewTab execution host resolution', () => {
@@ -6,7 +6,10 @@ import { setLocalRuntimeCapabilitiesForTests } from '@/runtime/local-runtime-cap
import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../shared/constants'
const mockCreateTab = vi.fn()
const mockQueueTabStartupCommand = vi.fn()
/** Startup commands published with the created tabs, in creation order. */
function queuedStartups() {
return mockCreateTab.mock.calls.flatMap((call) => call[3]?.pendingStartup ?? [])
}
const mockSetActiveTabType = vi.fn()
const mockSeedNativeChatAppliedSessionOptions = vi.fn()
@@ -42,7 +45,6 @@ const store = {
tabBarOrderByWorktree: {},
createTab: mockCreateTab,
queueTabInitialCwd: vi.fn(),
queueTabStartupCommand: mockQueueTabStartupCommand,
setActiveTabType: mockSetActiveTabType,
setTabBarOrder: vi.fn()
}
@@ -130,7 +132,8 @@ describe('launchAgentInNewTab terminal tab activation', () => {
undefined,
{
launchAgent: 'codex',
quickCommandLabel: undefined
quickCommandLabel: undefined,
pendingStartup: expect.anything()
}
)
expect(mockSetActiveTabType).toHaveBeenCalledExactlyOnceWith(
@@ -138,6 +141,6 @@ describe('launchAgentInNewTab terminal tab activation', () => {
FLOATING_TERMINAL_WORKTREE_ID
)
// Chat UI model preferences belong to structured chats, not a terminal fallback.
expect(mockQueueTabStartupCommand.mock.calls[0]?.[1]).not.toHaveProperty('sessionOptions')
expect(queuedStartups()[0]).not.toHaveProperty('sessionOptions')
})
})
@@ -7,7 +7,10 @@ const mockIsWebRuntimeSessionActive = vi.fn(() => false)
const mockCreateWebRuntimeAgentSessionTerminal = vi.fn()
const mockCreateWebRuntimeAgentSessionTerminalWithLaunchDraft = vi.fn()
const mockCreateTab = vi.fn()
const mockQueueTabStartupCommand = vi.fn()
/** Startup commands published with the created tabs, in creation order. */
function queuedStartups() {
return mockCreateTab.mock.calls.flatMap((call) => call[3]?.pendingStartup ?? [])
}
const mockPasteDraftWhenAgentReady = vi.fn()
const store = {
@@ -67,7 +70,6 @@ const store = {
ptyIdsByTabId: {} as Record<string, string[]>,
createTab: mockCreateTab,
closeTab: vi.fn(),
queueTabStartupCommand: mockQueueTabStartupCommand,
setActiveTabType: vi.fn(),
setTabBarOrder: vi.fn(),
setAgentStatus: vi.fn(),
@@ -202,8 +204,7 @@ describe('launchAgentInNewTab Windows shell quoting', () => {
launchPlatform: 'win32'
})
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect(queuedStartups()).toContainEqual(
expect.objectContaining({
command: "claude '--dangerously-skip-permissions' --prefill 'review Bob''s change'"
})
@@ -221,8 +222,7 @@ describe('launchAgentInNewTab Windows shell quoting', () => {
launchPlatform: 'win32'
})
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect(queuedStartups()).toContainEqual(
expect.objectContaining({
command: 'claude "--dangerously-skip-permissions"'
})
@@ -240,8 +240,7 @@ describe('launchAgentInNewTab Windows shell quoting', () => {
launchPlatform: 'win32'
})
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect(queuedStartups()).toContainEqual(
expect.objectContaining({
command: "claude '--dangerously-skip-permissions'"
})
@@ -261,8 +260,7 @@ describe('launchAgentInNewTab Windows shell quoting', () => {
launchPlatform: 'win32'
})
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect(queuedStartups()).toContainEqual(
expect.objectContaining({
command: 'codex "--model" "gpt-5" "fix the spinner"',
agentArgsOverride: '--model gpt-5'
@@ -283,8 +281,7 @@ describe('launchAgentInNewTab Windows shell quoting', () => {
launchPlatform: 'win32'
})
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect(queuedStartups()).toContainEqual(
expect.objectContaining({
command: `claude '--dangerously-skip-permissions' --prefill 'review Bob'"'"'s change'`
})
@@ -301,8 +298,7 @@ describe('launchAgentInNewTab Windows shell quoting', () => {
launchAgentInNewTab({ requestId: 'request-7', agent: 'claude', worktreeId: 'wt-1' })
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect(queuedStartups()).toContainEqual(
expect.objectContaining({
command: "claude '--dangerously-skip-permissions'"
})
@@ -339,8 +335,7 @@ describe('launchAgentInNewTab Windows shell quoting', () => {
promptDelivery: 'draft'
})
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect(queuedStartups()).toContainEqual(
expect.objectContaining({
command: `claude '--dangerously-skip-permissions' --prefill 'review Bob'"'"'s change'`
})
@@ -374,7 +369,7 @@ describe('launchAgentInNewTab Windows shell quoting', () => {
launchAgentInNewTab({ requestId: 'request-9', agent: 'codex', worktreeId: 'wt-1' })
const queued = mockQueueTabStartupCommand.mock.calls.at(-1)?.[1] as { command: string }
const queued = queuedStartups().at(-1)
expect(queued.command).toContain(`'don'"'"'t'`)
expect(queued.command).not.toContain("'don''t'")
})
@@ -4,7 +4,10 @@ import { toAppSshPtyId } from '../../../shared/ssh-pty-id'
import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../shared/constants'
const mockCreateTab = vi.fn()
const mockQueueTabStartupCommand = vi.fn()
/** Startup commands published with the created tabs, in creation order. */
function queuedStartups() {
return mockCreateTab.mock.calls.flatMap((call) => call[3]?.pendingStartup ?? [])
}
const mockSetActiveTabType = vi.fn()
const mockSetTabViewMode = vi.fn()
const mockSetTabBarOrder = vi.fn()
@@ -84,7 +87,6 @@ const store = {
ptyIdsByTabId: {} as Record<string, string[]>,
createTab: mockCreateTab,
closeTab: vi.fn(),
queueTabStartupCommand: mockQueueTabStartupCommand,
setActiveTabType: mockSetActiveTabType,
setTabViewMode: mockSetTabViewMode,
setTabBarOrder: mockSetTabBarOrder,
@@ -215,7 +217,8 @@ describe('launchAgentInNewTab', () => {
launchAgentInNewTab({ requestId: 'request-1', agent: 'codex', worktreeId: 'wt-1' })
expect(mockCreateTab).toHaveBeenCalledWith('wt-1', undefined, undefined, {
launchAgent: 'codex'
launchAgent: 'codex',
pendingStartup: expect.anything()
})
})
it('keeps Floating Workspace authority on native Windows beside an active WSL project', async () => {
@@ -241,7 +244,7 @@ describe('launchAgentInNewTab', () => {
FLOATING_TERMINAL_WORKTREE_ID,
undefined,
undefined,
{ launchAgent: 'codex' }
{ launchAgent: 'codex', pendingStartup: expect.anything() }
)
})
@@ -265,10 +268,10 @@ describe('launchAgentInNewTab', () => {
expect(mockCreateTab).toHaveBeenCalledWith('wt-1', undefined, undefined, {
launchAgent: 'codex',
quickCommandLabel: undefined
quickCommandLabel: undefined,
pendingStartup: expect.anything()
})
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect(queuedStartups()).toContainEqual(
expect.objectContaining({
command: expect.not.stringContaining('large generated prompt')
})
@@ -302,7 +305,8 @@ describe('launchAgentInNewTab', () => {
expect(mockCreateTab).toHaveBeenCalledWith('wt-1', undefined, undefined, {
launchAgent: 'grok',
quickCommandLabel: undefined
quickCommandLabel: undefined,
pendingStartup: expect.anything()
})
expect(mockSeedNativeChatLaunchPrompt).toHaveBeenCalledWith({
tabId: 'tab-1',
@@ -327,7 +331,8 @@ describe('launchAgentInNewTab', () => {
expect(mockCreateTab).toHaveBeenCalledWith('wt-1', undefined, undefined, {
launchAgent: 'grok',
quickCommandLabel: undefined
quickCommandLabel: undefined,
pendingStartup: expect.anything()
})
})
@@ -398,7 +403,8 @@ describe('launchAgentInNewTab', () => {
expect(mockCreateTab).toHaveBeenCalledWith('wt-1', undefined, undefined, {
launchAgent: 'codex',
quickCommandLabel: 'Review'
quickCommandLabel: 'Review',
pendingStartup: expect.anything()
})
})
@@ -427,7 +433,7 @@ describe('launchAgentInNewTab', () => {
quickCommandLabel: 'Review'
})
const launch = mockQueueTabStartupCommand.mock.calls[0]?.[1]
const launch = queuedStartups()[0]
expect(launch.command).toContain("'--profile' 'team'")
expect(launch.command).not.toContain("'-m'")
expect(launch.command).not.toContain('model_reasoning_effort=')
@@ -459,7 +465,7 @@ describe('launchAgentInNewTab', () => {
quickCommandLabel: 'Review'
})
const launch = mockQueueTabStartupCommand.mock.calls[0]?.[1]
const launch = queuedStartups()[0]
expect(launch.command).not.toContain("'-m'")
expect(launch.command).not.toContain('model_reasoning_effort=')
expect(launch.sessionOptions).toBeUndefined()
@@ -597,8 +603,7 @@ describe('launchAgentInNewTab', () => {
prompt: 'fix the spinner'
})
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect(queuedStartups()).toContainEqual(
expect.objectContaining({
command: "command-code --trust '--yolo' 'fix the spinner'",
initialAgentStatus: {
@@ -637,8 +642,7 @@ describe('launchAgentInNewTab', () => {
})
expect(result).not.toHaveProperty('promptDeliveryResult')
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect(queuedStartups()).toContainEqual(
expect.objectContaining({
command: "claude '--dangerously-skip-permissions'"
})
@@ -704,8 +708,7 @@ describe('launchAgentInNewTab', () => {
await Promise.resolve()
await Promise.resolve()
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect(queuedStartups()).toContainEqual(
expect.objectContaining({
command: "command-code --trust '--yolo'"
})
@@ -885,8 +888,7 @@ describe('launchAgentInNewTab', () => {
promptDelivery: 'submit-after-ready'
})
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect(queuedStartups()).toContainEqual(
expect.objectContaining({
command: "codex '--model' 'gpt-5.5'"
})
+19 -20
View File
@@ -265,35 +265,34 @@ function launchAgentInNewTabInternal(args: LaunchAgentInNewTabArgs): LaunchAgent
promptDeliveryResult: launched.promptDeliveryResult
}
}
// Why: queue startup BEFORE TerminalPane mounts — it snapshots pendingStartupByTabId in useState on first render.
const tab = store.createTab(worktreeId, groupId, undefined, {
launchAgent: agent,
quickCommandLabel,
...(pendingActivationSpawn ? { pendingActivationSpawn: true } : {}),
...(activate === false ? { activate: false } : {})
...(activate === false ? { activate: false } : {}),
pendingStartup: {
command: startupPlan.launchCommand,
...(startupPlan.env ? { env: startupPlan.env } : {}),
launchConfig: startupPlan.launchConfig,
launchAgent: agent,
...(agentArgs !== undefined ? { agentArgsOverride: agentArgs } : {}),
...(startupPlan.startupCommandDelivery
? { startupCommandDelivery: startupPlan.startupCommandDelivery }
: {}),
...(agent === 'command-code' && hasPrompt && promptDelivery === 'auto-submit'
? { initialAgentStatus: { agent, prompt: trimmedPrompt } }
: {}),
telemetry: {
agent_kind: tuiAgentToAgentKind(agent),
launch_source: launchSource ?? 'tab_bar_quick_launch',
request_kind: 'new'
}
}
})
if (initialCwd?.trim()) {
// Why: queue before mount so local, WSL, and SSH continuations preserve their subdirectory.
store.queueTabInitialCwd(tab.id, initialCwd)
}
store.queueTabStartupCommand(tab.id, {
command: startupPlan.launchCommand,
...(startupPlan.env ? { env: startupPlan.env } : {}),
launchConfig: startupPlan.launchConfig,
launchAgent: agent,
...(agentArgs !== undefined ? { agentArgsOverride: agentArgs } : {}),
...(startupPlan.startupCommandDelivery
? { startupCommandDelivery: startupPlan.startupCommandDelivery }
: {}),
...(agent === 'command-code' && hasPrompt && promptDelivery === 'auto-submit'
? { initialAgentStatus: { agent, prompt: trimmedPrompt } }
: {}),
telemetry: {
agent_kind: tuiAgentToAgentKind(agent),
launch_source: launchSource ?? 'tab_bar_quick_launch',
request_kind: 'new'
}
})
// Why: fire-and-forget the paste-after-ready delivery so callers keep the synchronous { tabId, startupPlan } signature.
// Why: safe to call unconditionally — the helper short-circuits (no paste) for native-prefill agents already holding the draft.
if (hasPrompt && promptDelivery === 'draft' && pasteDraftAfterLaunch === null) {
@@ -3,7 +3,10 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
const mockCreateTab = vi.fn()
const mockCreateEmptySplitGroup = vi.fn()
const mockCloseEmptyGroup = vi.fn()
const mockQueueTabStartupCommand = vi.fn()
/** Startup commands published with the created tabs, in creation order. */
function queuedStartups() {
return mockCreateTab.mock.calls.flatMap((call) => call[3]?.pendingStartup ?? [])
}
const mockSetActiveTabType = vi.fn()
const mockSetTabBarOrder = vi.fn()
const runtimeMocks = vi.hoisted(() => ({
@@ -16,7 +19,6 @@ const mockState = {
createTab: mockCreateTab,
createEmptySplitGroup: mockCreateEmptySplitGroup,
closeEmptyGroup: mockCloseEmptyGroup,
queueTabStartupCommand: mockQueueTabStartupCommand,
setActiveTabType: mockSetActiveTabType,
setTabBarOrder: mockSetTabBarOrder,
tabsByWorktree: {} as Record<string, { id: string }[]>,
@@ -83,8 +85,10 @@ describe('launchAiVaultSessionInNewTab', () => {
command: 'claude --resume session-1'
})
expect(mockCreateTab).toHaveBeenCalledWith('wt-1', 'group-1')
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith('tab-1', {
expect(mockCreateTab).toHaveBeenCalledWith('wt-1', 'group-1', undefined, {
pendingStartup: expect.anything()
})
expect(queuedStartups()).toContainEqual({
command: 'claude --resume session-1',
telemetry: {
agent_kind: 'claude',
@@ -115,9 +119,10 @@ describe('launchAiVaultSessionInNewTab', () => {
})
expect(mockCreateTab).toHaveBeenCalledWith('wt-1', undefined, undefined, {
startupCwd: 'C:\\Users\\alice\\repo'
startupCwd: 'C:\\Users\\alice\\repo',
pendingStartup: expect.anything()
})
expect(mockQueueTabStartupCommand).toHaveBeenCalledWith('tab-1', {
expect(queuedStartups()).toContainEqual({
command: "claude '--dangerously-skip-permissions' '--effort' 'max' '--resume' 'session-1'",
env: { ANTHROPIC_BASE_URL: 'https://claude.example.test' },
envToDelete: ['CODEX_HOME'],
@@ -146,7 +151,9 @@ describe('launchAiVaultSessionInNewTab', () => {
})
expect(mockCreateEmptySplitGroup).toHaveBeenCalledWith('wt-1', 'group-1', 'right')
expect(mockCreateTab).toHaveBeenCalledWith('wt-1', 'group-new')
expect(mockCreateTab).toHaveBeenCalledWith('wt-1', 'group-new', undefined, {
pendingStartup: expect.anything()
})
})
it('creates runtime-hosted resume terminals through the paired host', async () => {
@@ -188,7 +195,7 @@ describe('launchAiVaultSessionInNewTab', () => {
activate: true
})
expect(mockCreateTab).not.toHaveBeenCalled()
expect(mockQueueTabStartupCommand).not.toHaveBeenCalled()
expect(queuedStartups()).toEqual([])
if (result.tabId === null) {
await expect(result.runtimeLaunch).resolves.toEqual({ status: 'created' })
+13 -13
View File
@@ -69,19 +69,19 @@ export function launchAiVaultSessionInNewTab(args: {
}
}
const tab = args.cwd
? store.createTab(args.worktreeId, targetGroupId, undefined, { startupCwd: args.cwd })
: store.createTab(args.worktreeId, targetGroupId)
store.queueTabStartupCommand(tab.id, {
command: args.command,
...(args.env ? { env: args.env } : {}),
...(args.envToDelete ? { envToDelete: args.envToDelete } : {}),
...(args.launchConfig ? { launchConfig: args.launchConfig, launchAgent: args.agent } : {}),
...(args.providerSession ? { resumeProviderSession: args.providerSession } : {}),
telemetry: {
agent_kind: tuiAgentToAgentKind(args.agent),
launch_source: 'sidebar',
request_kind: 'resume'
const tab = store.createTab(args.worktreeId, targetGroupId, undefined, {
...(args.cwd ? { startupCwd: args.cwd } : {}),
pendingStartup: {
command: args.command,
...(args.env ? { env: args.env } : {}),
...(args.envToDelete ? { envToDelete: args.envToDelete } : {}),
...(args.launchConfig ? { launchConfig: args.launchConfig, launchAgent: args.agent } : {}),
...(args.providerSession ? { resumeProviderSession: args.providerSession } : {}),
telemetry: {
agent_kind: tuiAgentToAgentKind(args.agent),
launch_source: 'sidebar',
request_kind: 'resume'
}
}
})
store.setActiveTabType('terminal', args.worktreeId)
@@ -3,7 +3,6 @@ import { runQuickCommandInNewTab } from './run-quick-command-in-new-tab'
type MockStoreState = {
createTab: ReturnType<typeof vi.fn>
queueTabStartupCommand: ReturnType<typeof vi.fn>
setActiveTabType: ReturnType<typeof vi.fn>
setTabBarOrder: ReturnType<typeof vi.fn>
setRecentQuickCommandForGroup: ReturnType<typeof vi.fn>
@@ -37,7 +36,6 @@ vi.mock('@/lib/launch-agent-in-new-tab', () => ({
function createStoreState(): MockStoreState {
return {
createTab: vi.fn(() => ({ id: 'tab-new' })),
queueTabStartupCommand: vi.fn(),
setActiveTabType: vi.fn(),
setTabBarOrder: vi.fn(),
setRecentQuickCommandForGroup: vi.fn(),
@@ -73,10 +71,8 @@ describe('runQuickCommandInNewTab', () => {
expect(result).toEqual({ tabId: 'tab-new' })
expect(mockState.createTab).toHaveBeenCalledWith('wt-1', 'group-1', undefined, {
quickCommandLabel: 'Build'
})
expect(mockState.queueTabStartupCommand).toHaveBeenCalledWith('tab-new', {
command: 'cd packages; bun run build; cd ..'
quickCommandLabel: 'Build',
pendingStartup: { command: 'cd packages; bun run build; cd ..' }
})
expect(mockState.setRecentQuickCommandForGroup).toHaveBeenCalledWith('group-1', 'build')
})
@@ -139,8 +135,9 @@ describe('runQuickCommandInNewTab', () => {
groupId: 'group-1'
})
expect(mockState.queueTabStartupCommand).toHaveBeenCalledWith('tab-new', {
command: 'git status'
expect(mockState.createTab).toHaveBeenCalledWith('wt-1', 'group-1', undefined, {
quickCommandLabel: 'Status',
pendingStartup: { command: 'git status' }
})
})
@@ -174,7 +171,7 @@ describe('runQuickCommandInNewTab', () => {
launchSource: 'quick_command',
quickCommandLabel: 'Review'
})
expect(mockState.queueTabStartupCommand).not.toHaveBeenCalled()
expect(mockState.createTab).not.toHaveBeenCalled()
expect(mockState.setRecentQuickCommandForGroup).toHaveBeenCalledWith('group-1', 'agent-review')
})
@@ -330,6 +327,6 @@ describe('runQuickCommandInNewTab', () => {
expect(result).toBeNull()
expect(mocks.launchAgentInNewTab).not.toHaveBeenCalled()
expect(mockState.queueTabStartupCommand).not.toHaveBeenCalled()
expect(mockState.createTab).not.toHaveBeenCalled()
})
})
@@ -103,11 +103,8 @@ export function runQuickCommandInNewTab({
}
const store = useAppStore.getState()
const tab = store.createTab(worktreeId, targetGroupId, undefined, {
quickCommandLabel: command.label
})
store.queueTabStartupCommand(tab.id, {
command: flattenTerminalQuickCommand(command).command
quickCommandLabel: command.label,
pendingStartup: { command: flattenTerminalQuickCommand(command).command }
})
// Why: match `+` button's createNewTerminalTab — without this, a worktree
@@ -2,7 +2,8 @@ import { describe, expect, it, vi } from 'vitest'
import { ensureWorktreeHasInitialTerminal } from './worktree-initial-terminal-seeding'
import {
createMockStore,
registerWorktreeActivationReset
registerWorktreeActivationReset,
createdTabStartups
} from './worktree-activation-test-harness'
registerWorktreeActivationReset()
@@ -20,10 +21,11 @@ describe('ensureWorktreeHasInitialTerminal', () => {
)
expect(store.createTab).toHaveBeenCalledWith('wt-1', undefined, undefined, {
pendingStartup: expect.anything(),
pendingActivationSpawn: true
})
expect(store.setActiveTab).toHaveBeenCalledWith('tab-1')
expect(store.queueTabStartupCommand).toHaveBeenCalledWith('tab-1', {
expect(createdTabStartups(store)['tab-1']).toEqual({
command: 'claude "Fix this bug"'
})
expect(store.queueTabSetupSplit).not.toHaveBeenCalled()
@@ -49,10 +51,11 @@ describe('ensureWorktreeHasInitialTerminal', () => {
)
expect(store.createTab).toHaveBeenCalledWith('wt-1', undefined, undefined, {
pendingStartup: expect.anything(),
pendingActivationSpawn: true,
launchAgent: 'claude'
})
expect(store.queueTabStartupCommand).toHaveBeenCalledWith('tab-1', {
expect(createdTabStartups(store)['tab-1']).toEqual({
command: 'claude',
launchAgent: 'claude'
})
@@ -82,6 +85,7 @@ describe('ensureWorktreeHasInitialTerminal', () => {
)
expect(store.createTab).toHaveBeenCalledWith('wt-1', undefined, undefined, {
pendingStartup: expect.anything(),
pendingActivationSpawn: true,
launchAgent: 'claude',
...expectedViewMode
@@ -114,6 +118,7 @@ describe('ensureWorktreeHasInitialTerminal', () => {
)
expect(store.createTab).toHaveBeenCalledWith('wt-1', undefined, undefined, {
pendingStartup: expect.anything(),
pendingActivationSpawn: true,
launchAgent: 'claude',
...expectedViewMode
@@ -141,6 +146,7 @@ describe('ensureWorktreeHasInitialTerminal', () => {
)
expect(createTab).toHaveBeenNthCalledWith(1, 'wt-1', undefined, undefined, {
pendingStartup: expect.anything(),
pendingActivationSpawn: true,
recordInteraction: false,
launchAgent: 'claude'
@@ -173,6 +179,7 @@ describe('ensureWorktreeHasInitialTerminal', () => {
)
expect(createTab).toHaveBeenNthCalledWith(1, 'wt-1', undefined, undefined, {
pendingStartup: expect.anything(),
pendingActivationSpawn: true,
recordInteraction: false,
launchAgent: 'claude',
@@ -200,10 +207,11 @@ describe('ensureWorktreeHasInitialTerminal', () => {
)
expect(store.createTab).toHaveBeenCalledWith('wt-1', undefined, undefined, {
pendingStartup: expect.anything(),
pendingActivationSpawn: true,
launchAgent: 'claude'
})
expect(store.queueTabStartupCommand).toHaveBeenCalledWith('tab-1', {
expect(createdTabStartups(store)['tab-1']).toEqual({
command: 'claude',
telemetry: {
agent_kind: 'claude-code',
@@ -228,10 +236,11 @@ describe('ensureWorktreeHasInitialTerminal', () => {
)
expect(store.createTab).toHaveBeenCalledWith('wt-1', undefined, undefined, {
pendingStartup: expect.anything(),
pendingActivationSpawn: true,
launchAgent: 'codex'
})
expect(store.queueTabStartupCommand).toHaveBeenCalledWith('tab-1', {
expect(createdTabStartups(store)['tab-1']).toEqual({
command: 'codex',
launchAgent: 'codex'
})
@@ -2,7 +2,8 @@ import { describe, expect, it, vi } from 'vitest'
import { ensureWorktreeHasInitialTerminal } from './worktree-initial-terminal-seeding'
import {
createMockStore,
registerWorktreeActivationReset
registerWorktreeActivationReset,
createdTabStartups
} from './worktree-activation-test-harness'
registerWorktreeActivationReset()
@@ -17,7 +18,7 @@ describe('ensureWorktreeHasInitialTerminal', () => {
pendingActivationSpawn: true
})
expect(store.setActiveTab).toHaveBeenCalledWith('tab-1')
expect(store.queueTabStartupCommand).not.toHaveBeenCalled()
expect(createdTabStartups(store)).toEqual({})
expect(store.queueTabSetupSplit).not.toHaveBeenCalled()
})
@@ -39,9 +40,10 @@ describe('ensureWorktreeHasInitialTerminal', () => {
ensureWorktreeHasInitialTerminal(store, 'wt-1', { command: 'claude "Fix this bug"' })
expect(store.createTab).toHaveBeenCalledWith('wt-1', undefined, undefined, {
pendingActivationSpawn: true
pendingActivationSpawn: true,
pendingStartup: expect.anything()
})
expect(store.queueTabStartupCommand).toHaveBeenCalledWith('tab-1', {
expect(createdTabStartups(store)['tab-1']).toEqual({
command: 'claude "Fix this bug"'
})
})
@@ -71,7 +73,8 @@ describe('ensureWorktreeHasInitialTerminal', () => {
expect(createTab).toHaveBeenCalledTimes(2)
expect(createTab).toHaveBeenNthCalledWith(1, 'wt-1', undefined, undefined, {
pendingActivationSpawn: true,
recordInteraction: false
recordInteraction: false,
pendingStartup: expect.anything()
})
expect(store.setTabCustomTitle).toHaveBeenCalledWith('tab-1', 'Claude', {
recordInteraction: false
@@ -81,8 +84,8 @@ describe('ensureWorktreeHasInitialTerminal', () => {
})
expect(store.setTabColor).toHaveBeenCalledWith('tab-1', '#f97316')
expect(store.setTabColor).toHaveBeenCalledWith('tab-2', '#9ca3af')
expect(store.queueTabStartupCommand).toHaveBeenCalledWith('tab-1', { command: 'claude' })
expect(store.queueTabStartupCommand).toHaveBeenCalledWith('tab-2', { command: 'pnpm dev' })
expect(createdTabStartups(store)['tab-1']).toEqual({ command: 'claude' })
expect(createdTabStartups(store)['tab-2']).toEqual({ command: 'pnpm dev' })
expect(store.setActiveTab).toHaveBeenLastCalledWith('tab-1')
})
@@ -111,18 +114,20 @@ describe('ensureWorktreeHasInitialTerminal', () => {
expect(createTab).toHaveBeenNthCalledWith(1, 'wt-1', undefined, undefined, {
pendingActivationSpawn: true,
recordInteraction: false,
activate: false
activate: false,
pendingStartup: expect.anything()
})
expect(createTab).toHaveBeenNthCalledWith(2, 'wt-1', undefined, undefined, {
recordInteraction: false,
activate: false
activate: false,
pendingStartup: expect.anything()
})
expect(store.setActiveTab).not.toHaveBeenCalled()
expect(store.setTabCustomTitle).toHaveBeenCalledWith('tab-2', 'Setup', {
recordInteraction: false
})
expect(store.queueTabStartupCommand).toHaveBeenCalledWith('tab-1', { command: 'pnpm dev' })
expect(store.queueTabStartupCommand).toHaveBeenCalledWith('tab-2', {
expect(createdTabStartups(store)['tab-1']).toEqual({ command: 'pnpm dev' })
expect(createdTabStartups(store)['tab-2']).toEqual({
command: 'bash /tmp/repo/.git/orca/setup-runner.sh',
env: { ORCA_WORKTREE_PATH: '/tmp/worktrees/wt-1' }
})
@@ -136,7 +141,7 @@ describe('ensureWorktreeHasInitialTerminal', () => {
tabs: [{ title: 'Server', command: 'pnpm dev' }]
})
expect(store.queueTabStartupCommand).not.toHaveBeenCalled()
expect(createdTabStartups(store)).toEqual({})
expect(store.setTabCustomTitle).toHaveBeenCalledWith('tab-1', 'Server', {
recordInteraction: false
})
@@ -159,7 +164,7 @@ describe('ensureWorktreeHasInitialTerminal', () => {
expect(store.setTabCustomTitle).not.toHaveBeenCalledWith('tab-1', 'Claude', {
recordInteraction: false
})
expect(store.queueTabStartupCommand).not.toHaveBeenCalledWith('tab-1', {
expect(createdTabStartups(store)['tab-1']).not.toEqual({
command: 'claude'
})
})
@@ -176,7 +181,7 @@ describe('ensureWorktreeHasInitialTerminal', () => {
expect(store.createTab).not.toHaveBeenCalled()
expect(store.setActiveTab).not.toHaveBeenCalled()
expect(store.queueTabStartupCommand).not.toHaveBeenCalled()
expect(createdTabStartups(store)).toEqual({})
expect(store.queueTabSetupSplit).not.toHaveBeenCalled()
expect(store.queueTabIssueCommandSplit).not.toHaveBeenCalled()
})
@@ -3,7 +3,8 @@ import { ensureWorktreeHasInitialTerminal } from './worktree-initial-terminal-se
import {
createMockStore,
registerWorktreeActivationReset,
setSetupScriptLaunchMode
setSetupScriptLaunchMode,
createdTabStartups
} from './worktree-activation-test-harness'
registerWorktreeActivationReset()
@@ -67,7 +68,7 @@ describe('ensureWorktreeHasInitialTerminal', () => {
}
)
expect(store.queueTabStartupCommand).not.toHaveBeenCalled()
expect(createdTabStartups(store)).toEqual({})
expect(store.queueTabSetupSplit).toHaveBeenCalledWith('tab-1', {
command: 'bash /tmp/repo/.git/orca/setup-runner.sh',
env: { ORCA_ROOT_PATH: '/tmp/repo' },
@@ -84,7 +85,7 @@ describe('ensureWorktreeHasInitialTerminal', () => {
ensureWorktreeHasInitialTerminal(store, 'wt-1')
expect(store.queueTabStartupCommand).not.toHaveBeenCalled()
expect(createdTabStartups(store)).toEqual({})
expect(store.queueTabIssueCommandSplit).not.toHaveBeenCalled()
})
})
@@ -64,7 +64,6 @@ describe('activateAndRevealWorktree', () => {
setTabCustomTitle: vi.fn(),
setTabColor: vi.fn(),
markDefaultTerminalTabsApplied: vi.fn(),
queueTabStartupCommand: vi.fn(),
queueTabInitialCwd,
queueTabSetupSplit: vi.fn(),
queueTabIssueCommandSplit: vi.fn(),
@@ -134,7 +133,6 @@ describe('activateAndRevealWorktree', () => {
setTabCustomTitle: vi.fn(),
setTabColor: vi.fn(),
markDefaultTerminalTabsApplied: vi.fn(),
queueTabStartupCommand: vi.fn(),
queueTabInitialCwd: vi.fn(),
queueTabSetupSplit: vi.fn(),
queueTabIssueCommandSplit: vi.fn(),
@@ -4,7 +4,8 @@ import { ensureWorktreeHasInitialTerminal } from './worktree-initial-terminal-se
import {
createMockStore,
registerWorktreeActivationReset,
setSetupScriptLaunchMode
setSetupScriptLaunchMode,
createdTabStartups
} from './worktree-activation-test-harness'
registerWorktreeActivationReset()
@@ -29,7 +30,7 @@ describe('ensureWorktreeHasInitialTerminal', () => {
expect(store.setTabCustomTitle).toHaveBeenCalledWith('tab-2', 'Setup', {
recordInteraction: false
})
expect(store.queueTabStartupCommand).toHaveBeenCalledWith('tab-2', {
expect(createdTabStartups(store)['tab-2']).toEqual({
command: 'bash /tmp/repo/.git/orca/setup-runner.sh',
env: {
ORCA_ROOT_PATH: '/tmp/repo',
@@ -53,7 +54,7 @@ describe('ensureWorktreeHasInitialTerminal', () => {
}
})
expect(store.queueTabStartupCommand).toHaveBeenCalledWith('tab-2', {
expect(createdTabStartups(store)['tab-2']).toEqual({
command: 'bash /c/repo/.git/orca/setup-runner.sh',
env: {
ORCA_ROOT_PATH: 'C:\\repo',
@@ -83,7 +84,7 @@ describe('ensureWorktreeHasInitialTerminal', () => {
expect(store.setTabCustomTitle).toHaveBeenCalledWith('tab-2', 'Setup', {
recordInteraction: false
})
expect(store.queueTabStartupCommand).toHaveBeenCalledWith('tab-2', {
expect(createdTabStartups(store)['tab-2']).toEqual({
command: 'bash -lc wrapped-setup',
env: { ORCA_ROOT_PATH: '/tmp/repo' }
})
@@ -112,14 +113,12 @@ describe('ensureWorktreeHasInitialTerminal', () => {
expect(result).toBe('tab-1')
expect(createTab).toHaveBeenCalledTimes(1)
expect(store.queueTabStartupCommand).toHaveBeenCalledWith(
'tab-2',
expect(createdTabStartups(store)['tab-2']).toEqual(
expect.objectContaining({
command: expect.stringContaining('bash /tmp/repo/.git/orca/setup-runner.sh')
})
)
expect(store.queueTabStartupCommand).toHaveBeenCalledWith(
'tab-2',
expect(createdTabStartups(store)['tab-2']).toEqual(
expect.objectContaining({
command: expect.stringContaining('printf')
})
@@ -141,7 +140,7 @@ describe('ensureWorktreeHasInitialTerminal', () => {
}
})
expect(store.queueTabStartupCommand).toHaveBeenCalledWith('tab-2', {
expect(createdTabStartups(store)['tab-2']).toEqual({
command: 'bash /mnt/c/repo/.git/orca/setup-runner.sh',
env: {
ORCA_ROOT_PATH: 'C:\\repo',
@@ -167,8 +166,7 @@ describe('ensureWorktreeHasInitialTerminal', () => {
}
)
expect(store.queueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect(createdTabStartups(store)['tab-1']).toEqual(
expect.objectContaining({
env: expect.objectContaining({
[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]: expect.stringContaining(
@@ -177,22 +175,19 @@ describe('ensureWorktreeHasInitialTerminal', () => {
})
})
)
expect(store.queueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect(createdTabStartups(store)['tab-1']).toEqual(
expect.objectContaining({
env: expect.objectContaining({
[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]: expect.stringContaining('exec claude')
})
})
)
expect(store.queueTabStartupCommand).toHaveBeenCalledWith(
'tab-2',
expect(createdTabStartups(store)['tab-2']).toEqual(
expect.objectContaining({
command: expect.stringContaining('printf')
})
)
expect(store.queueTabStartupCommand).toHaveBeenCalledWith(
'tab-2',
expect(createdTabStartups(store)['tab-2']).toEqual(
expect.objectContaining({
command: expect.stringContaining('bash /tmp/repo/.git/orca/setup-runner.sh')
})
@@ -216,10 +211,10 @@ describe('ensureWorktreeHasInitialTerminal', () => {
}
)
expect(store.queueTabStartupCommand).toHaveBeenCalledWith('tab-1', {
expect(createdTabStartups(store)['tab-1']).toEqual({
command: 'claude'
})
expect(store.queueTabStartupCommand).toHaveBeenCalledWith('tab-2', {
expect(createdTabStartups(store)['tab-2']).toEqual({
command: 'bash /tmp/repo/.git/orca/setup-runner.sh',
env: { ORCA_ROOT_PATH: '/tmp/repo' }
})
@@ -240,8 +235,7 @@ describe('ensureWorktreeHasInitialTerminal', () => {
}
)
expect(store.queueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect(createdTabStartups(store)['tab-1']).toEqual(
expect.objectContaining({
env: expect.objectContaining({
[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]: expect.stringContaining('exec claude')
@@ -276,8 +270,7 @@ describe('ensureWorktreeHasInitialTerminal', () => {
}
)
expect(store.queueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect(createdTabStartups(store)['tab-1']).toEqual(
expect.objectContaining({
env: expect.objectContaining({
[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]: expect.stringContaining(
@@ -344,7 +337,7 @@ describe('ensureWorktreeHasInitialTerminal', () => {
expect(store.setTabCustomTitle).toHaveBeenCalledWith('tab-2', 'Setup', {
recordInteraction: false
})
expect(store.queueTabStartupCommand).toHaveBeenCalledWith('tab-2', {
expect(createdTabStartups(store)['tab-2']).toEqual({
command: 'bash /tmp/repo/.git/orca/setup-runner.sh',
env: { ORCA_ROOT_PATH: '/tmp/repo' }
})
@@ -24,6 +24,18 @@ export type WorktreeActivationStore = Partial<WorktreeRuntimeOwnerState> & {
recordInteraction?: boolean
viewMode?: Tab['viewMode']
activate?: boolean
pendingStartup?: {
command: string
env?: Record<string, string>
launchConfig?: SleepingAgentLaunchConfig
resumeProviderSession?: AgentProviderSessionMetadata
launchToken?: string
launchAgent?: TuiAgent
draftPrompt?: string
initialAgentStatus?: { agent: TuiAgent; prompt: string }
showSessionRestoredBanner?: boolean
telemetry?: AgentStartedTelemetry
}
}
) => { id: string }
setActiveTab: (tabId: string) => void
@@ -35,21 +47,6 @@ export type WorktreeActivationStore = Partial<WorktreeRuntimeOwnerState> & {
setTabColor: (tabId: string, color: string | null) => void
markDefaultTerminalTabsApplied: (worktreeId: string) => void
reconcileWorktreeTabModel: (worktreeId: string) => { renderableTabCount: number }
queueTabStartupCommand: (
tabId: string,
startup: {
command: string
env?: Record<string, string>
launchConfig?: SleepingAgentLaunchConfig
resumeProviderSession?: AgentProviderSessionMetadata
launchToken?: string
launchAgent?: TuiAgent
draftPrompt?: string
initialAgentStatus?: { agent: TuiAgent; prompt: string }
showSessionRestoredBanner?: boolean
telemetry?: AgentStartedTelemetry
}
) => void
queueTabSetupSplit: (
tabId: string,
startup: { command: string; env?: Record<string, string>; direction: SetupSplitDirection }
@@ -9,7 +9,8 @@ import {
import {
createMockStore,
registerWorktreeActivationReset,
setSetupScriptLaunchMode
setSetupScriptLaunchMode,
createdTabStartups
} from './worktree-activation-test-harness'
const initialAppStoreState = useAppStore.getState()
@@ -49,7 +50,7 @@ describe('seeding beside a caller-provided chat surface', () => {
expect(store.setTabCustomTitle).toHaveBeenCalledWith('tab-1', 'Setup', {
recordInteraction: false
})
expect(store.queueTabStartupCommand).toHaveBeenCalledWith('tab-1', {
expect(createdTabStartups(store)['tab-1']).toEqual({
command: 'bash /tmp/repo/.git/orca/setup-runner.sh',
env: setup.envVars
})
@@ -46,13 +46,12 @@ export function registerWorktreeActivationReset(): void {
export type MockActivationStore = {
tabsByWorktree: Record<string, { id: string }[]>
defaultTerminalTabsAppliedByWorktreeId: Record<string, true>
createTab: Mock<() => { id: string }>
createTab: Mock<(...args: unknown[]) => { id: string }>
setActiveTab: Mock<() => void>
setTabCustomTitle: Mock<() => void>
setTabColor: Mock<() => void>
markDefaultTerminalTabsApplied: Mock<() => void>
reconcileWorktreeTabModel: Mock<() => { renderableTabCount: number }>
queueTabStartupCommand: Mock<() => void>
queueTabInitialCwd: Mock<() => void>
queueTabSetupSplit: Mock<() => void>
queueTabIssueCommandSplit: Mock<() => void>
@@ -68,10 +67,27 @@ export function createMockStore(overrides: Record<string, unknown> = {}): MockAc
setTabColor: vi.fn(),
markDefaultTerminalTabsApplied: vi.fn(),
reconcileWorktreeTabModel: vi.fn(() => ({ renderableTabCount: 0 })),
queueTabStartupCommand: vi.fn(),
queueTabInitialCwd: vi.fn(),
queueTabSetupSplit: vi.fn(),
queueTabIssueCommandSplit: vi.fn(),
...overrides
}
}
/** The startup command each created tab was published with, keyed by the created tab's id. */
export function createdTabStartups(store: MockActivationStore): Record<string, unknown> {
const startups: Record<string, unknown> = {}
store.createTab.mock.calls.forEach((call, index) => {
const result = store.createTab.mock.results[index]
const options = call[3]
if (
result?.type === 'return' &&
options &&
typeof options === 'object' &&
'pendingStartup' in options
) {
startups[result.value.id] = options.pendingStartup
}
})
return startups
}
@@ -4,7 +4,8 @@ import { ensureWorktreeHasInitialTerminal } from './worktree-initial-terminal-se
import type { AppStoreState } from './worktree-activation-test-harness'
import {
createMockStore,
registerWorktreeActivationReset
registerWorktreeActivationReset,
createdTabStartups
} from './worktree-activation-test-harness'
import { useAppStore } from '@/store'
import {
@@ -299,14 +300,12 @@ describe('ensureWorktreeHasInitialTerminal', () => {
expect(store.setTabCustomTitle).toHaveBeenCalledWith('tab-2', 'Setup', {
recordInteraction: false
})
expect(store.queueTabStartupCommand).toHaveBeenCalledWith(
'tab-2',
expect(createdTabStartups(store)['tab-2']).toEqual(
expect.objectContaining({
command: expect.stringContaining('bash /tmp/repo/.git/orca/setup-runner.sh')
})
)
expect(store.queueTabStartupCommand).toHaveBeenCalledWith(
'tab-2',
expect(createdTabStartups(store)['tab-2']).toEqual(
expect.objectContaining({
command: expect.stringContaining('printf')
})
@@ -41,25 +41,31 @@ export function applyDefaultTerminalTabs(
: isStartupTab && startup?.telemetry
? (agentKindToTuiAgent(startup.telemetry.agent_kind) ?? undefined)
: undefined
const templateCommand = template.command?.trim()
const pendingStartup = isStartupTab
? startup
: templateCommand && defaultTabs.runCommands
? { command: templateCommand }
: undefined
const tab = store.createTab(worktreeId, undefined, undefined, {
pendingActivationSpawn: true,
recordInteraction: false,
...(launchAgent ? { launchAgent } : {}),
...(opts?.activateCreatedTabs === false ? { activate: false } : {})
...(opts?.activateCreatedTabs === false ? { activate: false } : {}),
...(pendingStartup ? { pendingStartup } : {})
})
if (index === 0) {
firstTabId = tab.id
}
if (isStartupTab && launchAgent) {
seedNativeChatAppliedSessionOptions(tab.id, launchAgent, startup.sessionOptions)
}
if (template.title) {
store.setTabCustomTitle(tab.id, template.title, { recordInteraction: false })
}
if (template.color) {
store.setTabColor(tab.id, template.color)
}
const templateCommand = template.command?.trim()
if (templateCommand && defaultTabs.runCommands && !(index === 0 && startup)) {
store.queueTabStartupCommand(tab.id, { command: templateCommand })
}
}
if (!firstTabId) {
@@ -68,17 +74,6 @@ export function applyDefaultTerminalTabs(
if (opts?.activateCreatedTabs !== false) {
store.setActiveTab(firstTabId)
}
if (startup) {
const startupAgent =
startup.launchAgent ??
(startup.telemetry
? (agentKindToTuiAgent(startup.telemetry.agent_kind) ?? undefined)
: undefined)
if (startupAgent) {
seedNativeChatAppliedSessionOptions(firstTabId, startupAgent, startup.sessionOptions)
}
store.queueTabStartupCommand(firstTabId, startup)
}
queueSetupAndIssueCommands(
store,
worktreeId,
@@ -281,22 +281,19 @@ export function ensureWorktreeHasInitialTerminal(
const terminalTab = store.createTab(worktreeId, undefined, undefined, {
pendingActivationSpawn: true,
...(launchAgent ? { launchAgent } : {}),
...(opts?.activateCreatedTabs === false ? { activate: false } : {})
...(opts?.activateCreatedTabs === false ? { activate: false } : {}),
// Why: the initial pane begins in the requested agent session instead of an idle shell.
...(sequencedStartup ? { pendingStartup: sequencedStartup } : {})
})
if (opts?.activateCreatedTabs !== false) {
store.setActiveTab(terminalTab.id)
}
// Why: queue the seeded startup on the initial pane so the terminal begins in the requested agent session instead of an idle shell.
if (sequencedStartup) {
if (launchAgent) {
seedNativeChatAppliedSessionOptions(
terminalTab.id,
launchAgent,
sequencedStartup.sessionOptions
)
}
store.queueTabStartupCommand(terminalTab.id, sequencedStartup)
if (sequencedStartup && launchAgent) {
seedNativeChatAppliedSessionOptions(
terminalTab.id,
launchAgent,
sequencedStartup.sessionOptions
)
}
queueSetupAndIssueCommands(
store,
@@ -34,7 +34,8 @@ export function queueSetupAndIssueCommands(
if (mode === 'new-tab') {
const setupTab = store.createTab(worktreeId, undefined, undefined, {
recordInteraction: false,
...(opts?.activateCreatedTabs === false ? { activate: false } : {})
...(opts?.activateCreatedTabs === false ? { activate: false } : {}),
pendingStartup: setupCommand
})
// Why: createTab auto-activates the new tab; revert so focus stays on the primary terminal while Setup runs in the background.
if (opts?.activateCreatedTabs !== false && terminalTabId) {
@@ -42,7 +43,6 @@ export function queueSetupAndIssueCommands(
}
// Why: customTitle overrides the auto "Terminal N" label everywhere the tab renders, so it's the authoritative label source.
store.setTabCustomTitle(setupTab.id, 'Setup', { recordInteraction: false })
store.queueTabStartupCommand(setupTab.id, setupCommand)
} else if (terminalTabId) {
store.queueTabSetupSplit(terminalTabId, {
...setupCommand,