fix(native-chat): reconcile backend draft launch tabs

This commit is contained in:
Brennan Benson
2026-07-29 17:20:50 -07:00
parent f1b4057205
commit 93580a2bc4
12 changed files with 310 additions and 18 deletions
+1
View File
@@ -297,6 +297,7 @@ async function spawnLocalStartupAndSetupTerminals(args: {
env: sequencedStartup.env,
...(sequencedStartup.launchConfig ? { launchConfig: sequencedStartup.launchConfig } : {}),
...(isTuiAgent(createdWithAgent) ? { launchAgent: createdWithAgent } : {}),
...(sequencedStartup.viewMode ? { viewMode: sequencedStartup.viewMode } : {}),
startupCommandDelivery: sequencedStartup.startupCommandDelivery,
telemetry: sequencedStartup.telemetry,
activate: true
+2
View File
@@ -1134,6 +1134,7 @@ describe('registerWorktreeHandlers', () => {
startup: {
command: 'claude --prefill test',
env: { ORCA_AGENT_MODE: 'direct' },
viewMode: 'chat',
telemetry: {
agent_kind: 'claude',
launch_source: 'new_workspace_composer',
@@ -1154,6 +1155,7 @@ describe('registerWorktreeHandlers', () => {
command: 'claude --prefill test',
env: { ORCA_AGENT_MODE: 'direct' },
launchAgent: 'claude',
viewMode: 'chat',
startupCommandDelivery: undefined,
telemetry: {
agent_kind: 'claude',
+31 -3
View File
@@ -3752,16 +3752,34 @@ describe('OrcaRuntimeService', () => {
const runtime = new OrcaRuntimeService(runtimeStore as never, undefined, {
getLocalProvider: () => localProvider as never
})
runtime.setPtyController({
spawn: vi.fn(),
write: () => true,
kill: () => true,
getForegroundProcess: async () => null
})
const createTerminal = vi.spyOn(runtime, 'createTerminal').mockResolvedValue({
handle: 'term_folder_startup',
tabId: 'tab-folder-startup',
worktreeId: '',
title: null,
surface: 'background'
})
const notifier = { worktreesChanged: vi.fn() }
runtime.setNotifier(notifier as never)
const result = await runtime.createManagedWorktree({
repoSelector: 'id:folder-repo',
name: 'folder-session',
createdWithAgent: 'codex'
createdWithAgent: 'codex',
startup: { command: 'codex', viewMode: 'chat' }
})
expect(addWorktreeMock).not.toHaveBeenCalled()
expect(createTerminal).toHaveBeenCalledWith(
`id:${result.worktree.id}`,
expect.objectContaining({ command: 'codex', viewMode: 'chat' })
)
expect(result.worktree).toEqual(
expect.objectContaining({
id: expect.stringMatching(/^folder-repo::\/workspace\/folder::workspace:[0-9a-f-]{36}$/),
@@ -5411,16 +5429,21 @@ describe('OrcaRuntimeService', () => {
})
runtime.attachWindow(1)
const createTerminal = vi.spyOn(runtime, 'createTerminal')
try {
const result = await runtime.createManagedWorktree({
repoSelector: TEST_REPO_ID,
name: 'mobile-setup',
setupDecision: 'run',
startup: { command: 'claude' }
startup: { command: 'claude', viewMode: 'chat' }
})
// Why: runtime provisions setup itself (fire-and-forget) and omits it from the RPC result so the caller doesn't double-spawn.
expect(result.setup).toBeUndefined()
expect(createTerminal).toHaveBeenCalledWith(
`path:${result.worktree.path}`,
expect.objectContaining({ viewMode: 'chat' })
)
await vi.waitFor(() => expect(spawn).toHaveBeenCalledTimes(2))
expect(spawn).toHaveBeenNthCalledWith(
1,
@@ -33053,6 +33076,7 @@ describe('OrcaRuntimeService', () => {
getRepo: (id: string) => (id === 'repo-1' ? waitRepo : undefined)
}
const runtime = new OrcaRuntimeService(runtimeStore as never)
const createTerminal = vi.spyOn(runtime, 'createTerminal')
const revealTerminalSession = vi.fn().mockResolvedValue({ tabId: 'tab-headless-startup' })
const spawn = vi
.fn()
@@ -33109,11 +33133,15 @@ describe('OrcaRuntimeService', () => {
repoSelector: 'id:repo-1',
name: 'runtime-headless-startup-setup',
setupDecision: 'run',
startup: { command: 'claude' }
startup: { command: 'claude', viewMode: 'chat' }
})
expect(createSetupRunnerScript).toHaveBeenCalled()
expect(runHook).not.toHaveBeenCalled()
expect(createTerminal).toHaveBeenCalledWith(
`id:${result.worktree.id}`,
expect.objectContaining({ viewMode: 'chat' })
)
// Why: setup is provisioned fire-and-forget; the wait-for-setup guarantee comes from the shell nonce/marker, not JS spawn ordering.
await vi.waitFor(() => expect(spawn).toHaveBeenCalledTimes(2))
const startupCommand = (spawn.mock.calls[0]![0] as { command: string }).command
+3
View File
@@ -18821,6 +18821,7 @@ export class OrcaRuntimeService {
? { launchConfig: effectiveStartup.launchConfig }
: {}),
...(effectiveCreatedWithAgent ? { launchAgent: effectiveCreatedWithAgent } : {}),
...(effectiveStartup.viewMode ? { viewMode: effectiveStartup.viewMode } : {}),
startupCommandDelivery: effectiveStartup.startupCommandDelivery,
telemetry: effectiveStartup.telemetry
})
@@ -19546,6 +19547,7 @@ export class OrcaRuntimeService {
env: sequencedStartup.env,
...(sequencedStartup.launchConfig ? { launchConfig: sequencedStartup.launchConfig } : {}),
...(effectiveCreatedWithAgent ? { launchAgent: effectiveCreatedWithAgent } : {}),
...(sequencedStartup.viewMode ? { viewMode: sequencedStartup.viewMode } : {}),
startupCommandDelivery: sequencedStartup.startupCommandDelivery,
telemetry: sequencedStartup.telemetry
})
@@ -19888,6 +19890,7 @@ export class OrcaRuntimeService {
env: sequencedStartup.env,
...(sequencedStartup.launchConfig ? { launchConfig: sequencedStartup.launchConfig } : {}),
...(args.createdWithAgent ? { launchAgent: args.createdWithAgent } : {}),
...(sequencedStartup.viewMode ? { viewMode: sequencedStartup.viewMode } : {}),
startupCommandDelivery: sequencedStartup.startupCommandDelivery,
telemetry: sequencedStartup.telemetry
})
@@ -22,6 +22,9 @@ import { decideInitialAgentTabViewMode } from './native-chat-initial-view-mode'
const DRAFT_TEXTS = [
'https://github.com/o/r/issues/12',
'Reproduce on Windows first\n\nhttps://github.com/o/r/issues/12',
'Reproduce on Windows first\rhttps://github.com/o/r/issues/12',
'Reproduce first\u2028https://github.com/o/r/issues/12',
'Reproduce first\u2029https://github.com/o/r/issues/12',
'ORC-123: Restore linked quick-create\nhttps://linear.app/o/issue/ORC-123',
'ORC-123 https://linear.app/o/issue/ORC-123\n',
' spaced but single line ',
@@ -68,9 +71,12 @@ describe('launch draft mirrorability', () => {
// The only test that pins the rule itself; the agreement tests above adapt on
// their own. Multi-line send work updates the predicate body and this test.
it('accepts single-line text and rejects anything with a newline', () => {
it('accepts single-line text and rejects any line separator', () => {
expect(canMirrorLaunchDraftToNativeChat('https://github.com/o/r/issues/12')).toBe(true)
expect(canMirrorLaunchDraftToNativeChat('one\ntwo')).toBe(false)
expect(canMirrorLaunchDraftToNativeChat('one\rtwo')).toBe(false)
expect(canMirrorLaunchDraftToNativeChat('one\u2028two')).toBe(false)
expect(canMirrorLaunchDraftToNativeChat('one\u2029two')).toBe(false)
expect(canMirrorLaunchDraftToNativeChat('trailing\n')).toBe(false)
expect(canMirrorLaunchDraftToNativeChat(' ')).toBe(false)
})
@@ -14,5 +14,5 @@
* predicate to accept multi-line flips seeding and view mode together.
*/
export function canMirrorLaunchDraftToNativeChat(text: string): boolean {
return text.trim().length > 0 && !text.includes('\n')
return text.trim().length > 0 && !/[\r\n\u2028\u2029]/.test(text)
}
@@ -5,7 +5,8 @@ import { seedAgentTabStateAfterWorktreeCreate } from './worktree-creation-agent-
const mocks = vi.hoisted(() => ({
seedNativeChatAppliedSessionOptions: vi.fn(),
seedNativeChatLaunchDraftForAgentTab: vi.fn()
seedNativeChatLaunchDraftForAgentTab: vi.fn(),
setWebRuntimeTabProps: vi.fn()
}))
vi.mock('@/components/native-chat/native-chat-session-option-cache', () => ({
@@ -14,11 +15,18 @@ vi.mock('@/components/native-chat/native-chat-session-option-cache', () => ({
vi.mock('@/lib/agent-launch-prompt-delivery', () => ({
seedNativeChatLaunchDraftForAgentTab: mocks.seedNativeChatLaunchDraftForAgentTab
}))
vi.mock('@/runtime/web-runtime-session', () => ({
setWebRuntimeTabProps: mocks.setWebRuntimeTabProps
}))
type AppState = ReturnType<typeof useAppStore.getState>
const initialTabsByWorktree = useAppStore.getState().tabsByWorktree
const initialUnifiedTabsByWorktree = useAppStore.getState().unifiedTabsByWorktree
const initialGetKnownWorktreeById = useAppStore.getState().getKnownWorktreeById
const initialSettings = useAppStore.getState().settings!
const initialWorktreesByRepo = useAppStore.getState().worktreesByRepo
const initialRepos = useAppStore.getState().repos
const DRAFT = 'https://github.com/o/r/issues/12'
@@ -28,9 +36,40 @@ const request = {
launchDraftPrompt: DRAFT
}
function setTabs(tabs: { id: string; launchAgent?: string }[]): void {
function setTabs(
tabs: { id: string; launchAgent?: string; viewMode?: 'terminal' | 'chat' }[],
runtimeOwnerEnvironmentId?: string
): void {
useAppStore.setState({
tabsByWorktree: { 'wt-1': tabs },
worktreesByRepo: {
'repo-1': [
{
id: 'wt-1',
repoId: 'repo-1',
path: '/repo/wt-1',
...(runtimeOwnerEnvironmentId ? { runtimeOwnerEnvironmentId } : {})
}
]
},
repos: [{ id: 'repo-1', path: '/repo', connectionId: null }],
unifiedTabsByWorktree: {
'wt-1': tabs.map((tab, index) => ({
id: tab.id,
entityId: tab.id,
groupId: 'group-1',
worktreeId: 'wt-1',
contentType: 'terminal' as const,
label: `Terminal ${index + 1}`,
customLabel: null,
color: null,
sortOrder: index,
createdAt: index,
isPreview: false,
isPinned: false,
...(tab.viewMode ? { viewMode: tab.viewMode } : {})
}))
},
getKnownWorktreeById: ((id: string) =>
id === 'wt-1' ? { id } : undefined) as unknown as AppState['getKnownWorktreeById']
} as unknown as Partial<AppState>)
@@ -40,15 +79,31 @@ function seededTabIds(): string[] {
return mocks.seedNativeChatLaunchDraftForAgentTab.mock.calls.map((call) => call[0].tabId)
}
function tabViewMode(tabId: string): 'terminal' | 'chat' | undefined {
return useAppStore.getState().unifiedTabsByWorktree['wt-1']?.find((tab) => tab.id === tabId)
?.viewMode
}
beforeEach(() => {
vi.clearAllMocks()
useAppStore.setState({
settings: {
...initialSettings,
experimentalNativeChat: true,
openAgentTabsInChatByDefault: true
}
})
})
afterEach(() => {
resetHookCommandDelayedDeliveryForTests()
useAppStore.setState({
tabsByWorktree: initialTabsByWorktree,
getKnownWorktreeById: initialGetKnownWorktreeById
unifiedTabsByWorktree: initialUnifiedTabsByWorktree,
getKnownWorktreeById: initialGetKnownWorktreeById,
settings: initialSettings,
worktreesByRepo: initialWorktreesByRepo,
repos: initialRepos
} as Partial<AppState>)
})
@@ -74,6 +129,60 @@ describe('seedAgentTabStateAfterWorktreeCreate', () => {
)
})
it('moves a backend-spawned non-mirrorable draft out of an inherited chat view', () => {
setTabs([{ id: 'agent-tab', launchAgent: 'claude', viewMode: 'chat' }])
seedAgentTabStateAfterWorktreeCreate({
request: { ...request, launchDraftPrompt: 'note\rhttps://github.com/o/r/issues/12' },
worktreeId: 'wt-1',
primaryTabId: 'agent-tab',
startupTerminalTabId: 'agent-tab',
backendSpawned: true
})
expect(tabViewMode('agent-tab')).toBe('terminal')
})
it('opens a backend-spawned mirrorable draft in chat after host reconciliation', () => {
setTabs([{ id: 'agent-tab', launchAgent: 'claude', viewMode: 'terminal' }])
seedAgentTabStateAfterWorktreeCreate({
request,
worktreeId: 'wt-1',
primaryTabId: 'agent-tab',
startupTerminalTabId: 'agent-tab',
backendSpawned: true
})
expect(tabViewMode('agent-tab')).toBe('chat')
})
it('keys a raw backend tab id and updates the host before its tab mirror lands', async () => {
setTabs([], 'runtime-1')
seedAgentTabStateAfterWorktreeCreate({
request,
worktreeId: 'wt-1',
primaryTabId: null,
startupTerminalTabId: 'host-agent-tab',
backendSpawned: true
})
expect(seededTabIds()).toEqual(['web-terminal-host-agent-tab'])
await vi.waitFor(() =>
expect(mocks.setWebRuntimeTabProps).toHaveBeenCalledWith({
worktreeId: 'wt-1',
tabId: 'web-terminal-host-agent-tab',
viewMode: 'chat'
})
)
setTabs(
[{ id: 'web-terminal-host-agent-tab', launchAgent: 'claude', viewMode: 'chat' }],
'runtime-1'
)
expect(seededTabIds()).toEqual(['web-terminal-host-agent-tab'])
})
it('seeds the launchAgent-stamped tab when the renderer owns startup', () => {
setTabs([{ id: 'dev-server' }, { id: 'agent-tab', launchAgent: 'claude' }])
@@ -2,6 +2,11 @@ import { useAppStore } from '@/store'
import { seedNativeChatAppliedSessionOptions } from '@/components/native-chat/native-chat-session-option-cache'
import { seedNativeChatLaunchDraftForAgentTab } from '@/lib/agent-launch-prompt-delivery'
import { queueHookCommandsForFirstWorktreeTab } from '@/lib/hook-command-delayed-delivery'
import { decideInitialAgentTabViewMode } from '@/lib/native-chat-initial-view-mode'
import { getConnectionIdFromState } from '@/lib/connection-context'
import { isNativeChatTranscriptLocalReadable } from '@/lib/native-chat-transcript-readability'
import { getRuntimeEnvironmentIdForWorktree } from '@/lib/worktree-runtime-owner'
import { toWebTerminalSurfaceTabId } from '@/runtime/web-terminal-surface-id'
import type { WorktreeCreationRequest } from '@/lib/pending-worktree-creation'
import type { TuiAgent } from '../../../shared/types'
@@ -26,18 +31,69 @@ function resolveLaunchAgentTabId(
backendSpawned: boolean
}
): string | null {
const worktreeTabs = state.tabsByWorktree[args.worktreeId] ?? []
// Main spawned the agent itself, so its startup tab is authoritative.
if (args.backendSpawned && args.startupTerminalTabId) {
return args.startupTerminalTabId
return getRuntimeEnvironmentIdForWorktree(state, args.worktreeId)
? toWebTerminalSurfaceTabId(args.startupTerminalTabId)
: args.startupTerminalTabId
}
const worktreeTabs = state.tabsByWorktree[args.worktreeId] ?? []
const stamped = worktreeTabs.find((tab) => tab.launchAgent === args.agent)?.id
// Why: when the renderer owns startup, `ensureAgentStartupInTerminal` queues it
// on primaryTabId, so that tab is the agent's tab by construction.
return stamped ?? args.primaryTabId ?? args.startupTerminalTabId ?? null
}
function applyAgentTabSeeds(request: SeedRequest, agent: TuiAgent, tabId: string): void {
function applyBackendSpawnedDraftViewMode(args: {
state: AppStoreSnapshot
request: SeedRequest
agent: TuiAgent
tabId: string
worktreeId: string
backendSpawned: boolean
}): void {
const { state, request, agent, tabId, worktreeId, backendSpawned } = args
if (!backendSpawned || !request.launchDraftPrompt) {
return
}
const desiredViewMode =
decideInitialAgentTabViewMode({
experimentalNativeChat: state.settings?.experimentalNativeChat,
openAgentTabsInChatByDefault: state.settings?.openAgentTabsInChatByDefault,
agent,
promptDelivery: 'draft',
launchDraftText: request.launchDraftPrompt,
...(agent === 'grok'
? {
nativeChatTranscriptIsLocalReadable: isNativeChatTranscriptLocalReadable(
getConnectionIdFromState(state, worktreeId)
)
}
: {})
}) ?? 'terminal'
const tab = state.unifiedTabsByWorktree?.[worktreeId]?.find((tab) => tab.id === tabId)
if (!tab && getRuntimeEnvironmentIdForWorktree(state, worktreeId)) {
void import('@/runtime/web-runtime-session').then(({ setWebRuntimeTabProps }) =>
setWebRuntimeTabProps({ worktreeId, tabId, viewMode: desiredViewMode })
)
return
}
const currentViewMode = tab?.viewMode ?? 'terminal'
if (currentViewMode !== desiredViewMode) {
state.setTabViewMode(tabId, desiredViewMode)
}
}
function applyAgentTabSeeds(args: {
state: AppStoreSnapshot
request: SeedRequest
agent: TuiAgent
tabId: string
worktreeId: string
backendSpawned: boolean
}): void {
const { request, agent, tabId } = args
applyBackendSpawnedDraftViewMode(args)
seedNativeChatAppliedSessionOptions(tabId, agent, request.startupPlan?.sessionOptions)
// Why: draft launch context reaches only the TUI input; seed the
// chat-composer copy so it isn't invisible in the chat view.
@@ -57,7 +113,7 @@ export function seedAgentTabStateAfterWorktreeCreate(args: {
startupTerminalTabId: string | null | undefined
backendSpawned: boolean
}): void {
const { request, worktreeId } = args
const { request, worktreeId, backendSpawned } = args
const agent = request.agent
if (!request.startupPlan || !agent) {
return
@@ -65,7 +121,7 @@ export function seedAgentTabStateAfterWorktreeCreate(args: {
const state = useAppStore.getState()
const tabId = resolveLaunchAgentTabId(state, { ...args, agent })
if (tabId) {
applyAgentTabSeeds(request, agent, tabId)
applyAgentTabSeeds({ state, request, agent, tabId, worktreeId, backendSpawned })
return
}
if ((state.tabsByWorktree[worktreeId] ?? []).length > 0) {
@@ -81,7 +137,14 @@ export function seedAgentTabStateAfterWorktreeCreate(args: {
deliver: (state, firstTerminalTabId) => {
const mirroredTabId = resolveLaunchAgentTabId(state, { ...args, agent })
if (mirroredTabId) {
applyAgentTabSeeds(request, agent, mirroredTabId)
applyAgentTabSeeds({
state,
request,
agent,
tabId: mirroredTabId,
worktreeId,
backendSpawned
})
return
}
// Why: same invariant as the synchronous branch — never seed a tab that
@@ -89,7 +152,14 @@ export function seedAgentTabStateAfterWorktreeCreate(args: {
// so accept the first mirrored tab only when it is the worktree's only
// one and therefore unambiguously the agent's.
if ((state.tabsByWorktree[worktreeId] ?? []).length === 1) {
applyAgentTabSeeds(request, agent, firstTerminalTabId)
applyAgentTabSeeds({
state,
request,
agent,
tabId: firstTerminalTabId,
worktreeId,
backendSpawned
})
}
}
})
@@ -13,7 +13,11 @@ const { prepareEphemeralVmWorkspaceTargetMock } = vi.hoisted(() => ({
type TestActiveView = 'terminal' | 'tasks'
const store = {
settings: { activeRuntimeEnvironmentId: null as string | null },
settings: {
activeRuntimeEnvironmentId: null as string | null,
experimentalNativeChat: undefined as boolean | undefined,
openAgentTabsInChatByDefault: undefined as boolean | undefined
},
activeView: 'terminal' as TestActiveView,
activePendingCreationId: 'creation-1' as string | null,
repos: [{ id: 'repo-runtime', connectionId: null }],
@@ -41,7 +45,9 @@ const store = {
setupProjectExistingFolder: vi.fn(),
refreshRuntimeEnvironmentStatus: vi.fn(),
seedNativeChatLaunchDraft: vi.fn(),
tabsByWorktree: {} as Record<string, { id: string; launchAgent?: string }[]>
setTabViewMode: vi.fn(),
tabsByWorktree: {} as Record<string, { id: string; launchAgent?: string }[]>,
unifiedTabsByWorktree: {}
}
vi.mock('@/store', () => ({
@@ -94,12 +100,15 @@ const FLOW_SOURCE = readFileSync(join(__dirname, 'worktree-creation-flow.ts'), '
beforeEach(() => {
vi.clearAllMocks()
store.settings.activeRuntimeEnvironmentId = null
store.settings.experimentalNativeChat = undefined
store.settings.openAgentTabsInChatByDefault = undefined
store.activeView = 'terminal'
store.activePendingCreationId = 'creation-1'
store.repos = []
store.pendingWorktreeCreations = { 'creation-1': makePendingCreation(makeRequest()) }
store.createWorktree.mockImplementation(() => new Promise(() => {}))
store.tabsByWorktree = {}
store.unifiedTabsByWorktree = {}
vi.mocked(ensureWorktreeHasInitialTerminal).mockReturnValue('tab-1')
})
@@ -697,6 +706,38 @@ describe('staged background worktree creation', () => {
)
})
it.each([
['mirrorable local Grok', 'grok', 'https://github.com/o/r/issues/12', 'chat'],
['multi-line Claude', 'claude', 'note\nhttps://github.com/o/r/issues/12', 'terminal']
] as const)('passes %s draft mode to backend startup', async (_label, agent, draft, viewMode) => {
store.settings.experimentalNativeChat = true
store.settings.openAgentTabsInChatByDefault = true
store.repos = [{ id: 'repo-1', connectionId: null }]
continueBackgroundWorktreeCreation(
'creation-1',
makeRequest({
agent,
startup: { command: `${agent} --prefill x`, launchAgent: agent },
startupPlan: {
agent,
launchCommand: `${agent} --prefill x`,
expectedProcess: agent,
followupPrompt: null,
launchConfig: { agentArgs: '', agentEnv: {} }
},
launchDraftPrompt: draft
})
)
await vi.waitFor(() => expect(store.createWorktree).toHaveBeenCalled())
const createCall = store.createWorktree.mock.calls[0] as unknown[] | undefined
expect(createCall?.[16]).toEqual({
command: `${agent} --prefill x`,
launchAgent: agent,
viewMode
})
})
it('carries launchDraftText into activation for an argv-prefill launch', async () => {
// Why: the draft rides inside `launchCommand` here, so the plan sets no
// draftPrompt — without launchDraftText the initial view-mode decision
@@ -26,6 +26,7 @@ import type {
} from '@/lib/pending-worktree-creation'
import { createBrowserUuid } from '@/lib/browser-uuid'
import { seedAgentTabStateAfterWorktreeCreate } from '@/lib/worktree-creation-agent-seeds'
import { resolveBackendDraftStartup } from '@/lib/worktree-draft-startup-view-mode'
type ContinueBackgroundWorktreeCreationOptions = {
revealCreationSurface?: boolean
@@ -141,6 +142,7 @@ async function executeWorktreeCreation(
let result: CreateWorktreeResult
try {
const backendStartup = resolveBackendDraftStartup(preparedRequest)
result = await useAppStore
.getState()
.createWorktree(
@@ -160,7 +162,7 @@ async function executeWorktreeCreation(
preparedRequest.workspaceStatus,
preparedRequest.linkedGitLabMR,
preparedRequest.linkedGitLabIssue,
preparedRequest.startup,
backendStartup,
preparedRequest.pendingFirstAgentMessageRename,
creationId,
preparedRequest.linkedLinearIssueWorkspaceId,
@@ -0,0 +1,29 @@
import { useAppStore } from '@/store'
import { decideInitialAgentTabViewMode } from '@/lib/native-chat-initial-view-mode'
import { isNativeChatTranscriptLocalReadable } from '@/lib/native-chat-transcript-readability'
import type { WorktreeCreationRequest } from '@/lib/pending-worktree-creation'
export function resolveBackendDraftStartup(
request: WorktreeCreationRequest
): WorktreeCreationRequest['startup'] {
if (!request.startup || !request.agent || !request.launchDraftPrompt) {
return request.startup
}
const state = useAppStore.getState()
const repo = state.repos.find((entry) => entry.id === request.repoId)
const connectionId = repo ? (repo.connectionId ?? null) : undefined
const viewMode =
decideInitialAgentTabViewMode({
experimentalNativeChat: state.settings?.experimentalNativeChat,
openAgentTabsInChatByDefault: state.settings?.openAgentTabsInChatByDefault,
agent: request.agent,
promptDelivery: 'draft',
launchDraftText: request.launchDraftPrompt,
...(request.agent === 'grok'
? {
nativeChatTranscriptIsLocalReadable: isNativeChatTranscriptLocalReadable(connectionId)
}
: {})
}) ?? 'terminal'
return { ...request.startup, viewMode }
}
+1
View File
@@ -2148,6 +2148,7 @@ export type WorktreeStartupLaunch = {
launchConfig?: SleepingAgentLaunchConfig
launchToken?: string
launchAgent?: TuiAgent
viewMode?: 'terminal' | 'chat'
startupCommandDelivery?: StartupCommandDelivery
telemetry?: { agent_kind: AgentKind; launch_source: LaunchSource; request_kind: RequestKind }
}