From 5ceb549cdbd56d1c9841d29dccdddf7d75a5216b Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Sun, 5 Jul 2026 15:30:07 -0700 Subject: [PATCH] Avoid auto-opening native chat for draft prompt followups Followup paths paste the prompt as an unsubmitted draft. When prompt delivery is configured as 'auto-submit', this previously opened the native chat view with no actual submitted turn to render. By gating the initial view mode using 'draft' delivery on followup paths, we ensure the tab starts in terminal mode instead. --- .../native-chat/native-chat-availability.ts | 2 +- .../native-chat/native-chat-pending.ts | 3 + .../src/lib/launch-agent-in-new-tab.ts | 11 +++- .../src/lib/worktree-activation.test.ts | 55 +++++++++++++++++++ 4 files changed, 69 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/components/native-chat/native-chat-availability.ts b/src/renderer/src/components/native-chat/native-chat-availability.ts index cccbb956f3c..4e520af1b9a 100644 --- a/src/renderer/src/components/native-chat/native-chat-availability.ts +++ b/src/renderer/src/components/native-chat/native-chat-availability.ts @@ -2,7 +2,7 @@ import type { Tab, TuiAgent } from '../../../../shared/types' import type { AgentType } from '../../../../shared/agent-status-types' import { isNativeChatSupportedAgent } from '@/lib/native-chat-supported-agent' -export { isNativeChatSupportedAgent } from '@/lib/native-chat-supported-agent' +export { isNativeChatSupportedAgent } /** Inputs that decide whether a tab may toggle into the native chat view. * Kept as a plain shape (not the live store) so the decision stays pure and diff --git a/src/renderer/src/components/native-chat/native-chat-pending.ts b/src/renderer/src/components/native-chat/native-chat-pending.ts index e76349a9a62..3021dc7bba4 100644 --- a/src/renderer/src/components/native-chat/native-chat-pending.ts +++ b/src/renderer/src/components/native-chat/native-chat-pending.ts @@ -176,6 +176,9 @@ export function launchPromptAsMessage( } } +// Why: prune only once an assistant turn has landed after the matching user +// text — keeping the optimistic bubble through the user-only phase avoids a +// first-turn flash before the transcript's own copy of the turn catches up. export function shouldPruneLaunchPrompt( entry: NativeChatLaunchPrompt, messages: NativeChatMessage[] diff --git a/src/renderer/src/lib/launch-agent-in-new-tab.ts b/src/renderer/src/lib/launch-agent-in-new-tab.ts index f815d450c9d..42d577b4d14 100644 --- a/src/renderer/src/lib/launch-agent-in-new-tab.ts +++ b/src/renderer/src/lib/launch-agent-in-new-tab.ts @@ -268,10 +268,19 @@ export function launchAgentInNewTab(args: LaunchAgentInNewTabArgs): LaunchAgentI // lands after mount the agent binary never starts; the user sees a bare shell. // Since both calls happen synchronously in the same React batch, the queue // is in place by the time the pane commits. + // Why: the followup path pastes the prompt as an unsubmitted draft (submit + // stays false), so gate the initial chat view like a `draft` launch — + // otherwise a default `auto-submit` followup would open native chat with no + // submitted turn to render. + const viewModePromptDelivery = + hasPrompt && isFollowupPath && promptDelivery === 'auto-submit' ? 'draft' : promptDelivery const tab = store.createTab(worktreeId, groupId, undefined, { launchAgent: agent, quickCommandLabel, - ...initialAgentTabViewModeProps(store.settings, { agent, promptDelivery }) + ...initialAgentTabViewModeProps(store.settings, { + agent, + promptDelivery: viewModePromptDelivery + }) }) store.queueTabStartupCommand(tab.id, { command: startupPlan.launchCommand, diff --git a/src/renderer/src/lib/worktree-activation.test.ts b/src/renderer/src/lib/worktree-activation.test.ts index 107a573eeeb..53e6a5fe8ed 100644 --- a/src/renderer/src/lib/worktree-activation.test.ts +++ b/src/renderer/src/lib/worktree-activation.test.ts @@ -495,6 +495,61 @@ describe('ensureWorktreeHasInitialTerminal', () => { }) }) + it('opens the startup default tab in native chat when configured', () => { + let createdIndex = 0 + const createTab = vi.fn(() => ({ id: `tab-${++createdIndex}` })) + const store = createMockStore({ + createTab, + settings: { + experimentalNativeChat: true, + openAgentTabsInChatByDefault: true + } + }) + + ensureWorktreeHasInitialTerminal( + store, + 'wt-1', + { command: 'claude', launchAgent: 'claude' }, + undefined, + undefined, + { runCommands: true, tabs: [{ title: 'Claude', command: 'claude' }] } + ) + + expect(createTab).toHaveBeenNthCalledWith(1, 'wt-1', undefined, undefined, { + pendingActivationSpawn: true, + recordInteraction: false, + launchAgent: 'claude', + viewMode: 'chat' + }) + }) + + it('keeps a draft startup default tab in terminal mode even when native chat is configured', () => { + let createdIndex = 0 + const createTab = vi.fn(() => ({ id: `tab-${++createdIndex}` })) + const store = createMockStore({ + createTab, + settings: { + experimentalNativeChat: true, + openAgentTabsInChatByDefault: true + } + }) + + ensureWorktreeHasInitialTerminal( + store, + 'wt-1', + { command: 'claude', launchAgent: 'claude', draftPrompt: 'Review before sending' }, + undefined, + undefined, + { runCommands: true, tabs: [{ title: 'Claude', command: 'claude' }] } + ) + + expect(createTab).toHaveBeenNthCalledWith(1, 'wt-1', undefined, undefined, { + pendingActivationSpawn: true, + recordInteraction: false, + launchAgent: 'claude' + }) + }) + it('gates startup behind setup completion when both are provided in new-tab mode', () => { setSetupScriptLaunchMode('new-tab') let createdIndex = 0