From 429f5e5d1bb2440f43cc676cb3ac87fe43e0f8b8 Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Sat, 30 May 2026 09:21:28 -0700 Subject: [PATCH] Fix default tab command setup policy Co-authored-by: Orca --- src/main/hooks.test.ts | 18 ++++++- src/main/hooks.ts | 12 ++++- .../components/NewWorkspaceComposerCard.tsx | 35 ++++++++++-- src/renderer/src/lib/new-workspace.test.ts | 37 +++++++++++++ src/renderer/src/lib/new-workspace.ts | 53 ++++++++++++++++--- 5 files changed, 142 insertions(+), 13 deletions(-) diff --git a/src/main/hooks.test.ts b/src/main/hooks.test.ts index 3b63b6d8a76..5e9269075a8 100644 --- a/src/main/hooks.test.ts +++ b/src/main/hooks.test.ts @@ -882,7 +882,10 @@ describe('shouldRunSetupForCreate', () => { }) describe('getDefaultTabsLaunch', () => { - const makeRepo = (setupRunPolicy?: 'ask' | 'run-by-default' | 'skip-by-default') => + const makeRepo = ( + setupRunPolicy?: 'ask' | 'run-by-default' | 'skip-by-default', + commandSourcePolicy?: 'local-only' | 'run-both' | 'shared-only' + ) => ({ id: 'test-id', path: '/test/repo', @@ -892,6 +895,7 @@ describe('getDefaultTabsLaunch', () => { hookSettings: { mode: 'auto', setupRunPolicy, + commandSourcePolicy, scripts: { setup: '', archive: '' } } }) as unknown as Repo @@ -923,4 +927,16 @@ describe('getDefaultTabsLaunch', () => { runCommands: false }) }) + + it('does not run shared default tab commands when command source is local-only', () => { + const hooks = { + scripts: {}, + defaultTabs: [{ title: 'Server', command: 'pnpm dev' }] + } + + expect(getDefaultTabsLaunch(hooks, makeRepo('run-by-default', 'local-only'))).toEqual({ + tabs: hooks.defaultTabs, + runCommands: false + }) + }) }) diff --git a/src/main/hooks.ts b/src/main/hooks.ts index 8fcead5cba2..b63c3fc934b 100644 --- a/src/main/hooks.ts +++ b/src/main/hooks.ts @@ -357,7 +357,17 @@ export function getDefaultTabsLaunch( return undefined } const hasCommands = tabs.some((tab) => Boolean(tab.command?.trim())) - const runCommands = hasCommands ? shouldRunSetupForCreate(repo, decision) : false + const sharedCommandPolicy = resolveHookCommandSourcePolicy( + repo.hookSettings?.commandSourcePolicy, + { + hasLocalScript: Boolean(repo.hookSettings?.scripts.setup?.trim()) + } + ) + // Why: default tab commands come from committed `orca.yaml`; a repo set to + // local-only may still use shared titles/colors, but must not execute them. + const canRunSharedCommands = sharedCommandPolicy !== 'local-only' + const runCommands = + hasCommands && canRunSharedCommands ? shouldRunSetupForCreate(repo, decision) : false return { tabs, runCommands } } diff --git a/src/renderer/src/components/NewWorkspaceComposerCard.tsx b/src/renderer/src/components/NewWorkspaceComposerCard.tsx index b7546889ba8..55a25518502 100644 --- a/src/renderer/src/components/NewWorkspaceComposerCard.tsx +++ b/src/renderer/src/components/NewWorkspaceComposerCard.tsx @@ -267,6 +267,31 @@ export default function NewWorkspaceComposerCard({ selectedRepoSshStatus === 'disconnected' || selectedRepoSshStatus === null ? 'Connect' : 'Reconnect' + const setupConfigLabel = + setupConfig?.kind === 'default-tabs' + ? 'Default tab commands' + : setupConfig?.kind === 'setup-and-default-tabs' + ? 'Setup and default tab commands' + : 'Setup script' + const setupRunLabel = + setupConfig?.kind === 'default-tabs' + ? 'Run default tab commands' + : setupConfig?.kind === 'setup-and-default-tabs' + ? 'Run setup and default tab commands' + : 'Run setup command' + const setupAskLabel = + setupConfig?.kind === 'default-tabs' + ? 'Run default tab commands now?' + : setupConfig?.kind === 'setup-and-default-tabs' + ? 'Run setup and default tab commands now?' + : 'Run setup now?' + const setupRunButtonLabel = + setupConfig?.kind === 'default-tabs' + ? 'Run commands now' + : setupConfig?.kind === 'setup-and-default-tabs' + ? 'Run commands now' + : 'Run setup now' + const setupSkipButtonLabel = setupConfig?.kind === 'setup' ? 'Skip for now' : 'Skip commands' const handleSetDefaultAgent = React.useCallback( (next: TuiAgent | 'blank' | null) => { @@ -558,7 +583,7 @@ export default function NewWorkspaceComposerCard({
{setupConfig.source === 'yaml' @@ -600,7 +625,7 @@ export default function NewWorkspaceComposerCard({ } className="sr-only" /> - Run setup command + {setupRunLabel} ) } @@ -609,7 +634,7 @@ export default function NewWorkspaceComposerCard({ {requiresExplicitSetupChoice ? (
- Run setup now? + {setupAskLabel}
{!setupDecision ? ( diff --git a/src/renderer/src/lib/new-workspace.test.ts b/src/renderer/src/lib/new-workspace.test.ts index 59812d340e9..1ec36fd483a 100644 --- a/src/renderer/src/lib/new-workspace.test.ts +++ b/src/renderer/src/lib/new-workspace.test.ts @@ -40,6 +40,7 @@ vi.mock('@/lib/telemetry', () => ({ import { ensureAgentStartupInTerminal, + getSetupConfig, getWorkspaceSeedName, isGitLabIssueUrl } from './new-workspace' @@ -252,3 +253,39 @@ describe('ensureAgentStartupInTerminal prompt delivery', () => { expect(mockTrack).not.toHaveBeenCalledWith('agent_prompt_sent', expect.anything()) }) }) + +describe('getSetupConfig', () => { + it('treats default tab commands as setup-decision commands', () => { + expect( + getSetupConfig(undefined, { + scripts: {}, + defaultTabs: [ + { title: 'Server', command: 'pnpm dev' }, + { title: 'Notes' }, + { command: 'codex' } + ] + }) + ).toEqual({ + source: 'yaml', + kind: 'default-tabs', + command: '# defaultTabs[1] Server\npnpm dev\n\n# defaultTabs[3]\ncodex' + }) + }) + + it('ignores shared default tab commands when command source is local-only', () => { + expect( + getSetupConfig( + { + hookSettings: { + commandSourcePolicy: 'local-only', + scripts: {} + } + }, + { + scripts: {}, + defaultTabs: [{ title: 'Server', command: 'pnpm dev' }] + } + ) + ).toBeNull() + }) +}) diff --git a/src/renderer/src/lib/new-workspace.ts b/src/renderer/src/lib/new-workspace.ts index 4ad081f46b9..ef048ef200d 100644 --- a/src/renderer/src/lib/new-workspace.ts +++ b/src/renderer/src/lib/new-workspace.ts @@ -78,7 +78,38 @@ export function isGitLabIssueUrl(url: string): boolean { // is the minimum viable instruction that always produces a coherent agent task. export const DEFAULT_ISSUE_COMMAND_TEMPLATE = 'Complete {{artifact_url}}' -export type SetupConfig = { source: 'yaml' | 'local' | 'both'; command: string } +export type SetupConfig = { + source: 'yaml' | 'local' | 'both' + command: string + kind: 'setup' | 'default-tabs' | 'setup-and-default-tabs' +} + +function getDefaultTabCommandPreview(yamlHooks: OrcaHooks | null): string { + return (yamlHooks?.defaultTabs ?? []) + .map((tab, index) => { + const command = tab.command?.trim() + if (!command) { + return null + } + const label = tab.title ? ` ${tab.title}` : '' + return `# defaultTabs[${index + 1}]${label}\n${command}` + }) + .filter((entry): entry is string => entry !== null) + .join('\n\n') +} + +function getSetupConfigKind( + hasSetup: boolean, + hasDefaultTabCommands: boolean +): SetupConfig['kind'] { + if (hasSetup && hasDefaultTabCommands) { + return 'setup-and-default-tabs' + } + if (hasDefaultTabCommands) { + return 'default-tabs' + } + return 'setup' +} /** * Substitute the issue-command template variables. Prefers `{{artifact_url}}` @@ -149,21 +180,31 @@ export function getSetupConfig( yamlHooks: OrcaHooks | null ): SetupConfig | null { const yamlSetup = yamlHooks?.scripts?.setup?.trim() + const yamlDefaultTabCommands = getDefaultTabCommandPreview(yamlHooks) const localSetup = repo?.hookSettings?.scripts?.setup?.trim() const sourcePolicy = resolveHookCommandSourcePolicy(repo?.hookSettings?.commandSourcePolicy, { hasLocalScript: Boolean(localSetup) }) if (sourcePolicy === 'local-only') { - return localSetup ? { source: 'local', command: localSetup } : null + return localSetup ? { source: 'local', command: localSetup, kind: 'setup' } : null } - if (sourcePolicy === 'run-both' && yamlSetup && localSetup) { - return { source: 'both', command: `${yamlSetup}\n${localSetup}` } + const yamlCommand = [yamlSetup, yamlDefaultTabCommands].filter(Boolean).join('\n\n') + if (sourcePolicy === 'run-both' && yamlCommand && localSetup) { + return { + source: 'both', + command: `${yamlCommand}\n\n${localSetup}`, + kind: getSetupConfigKind(true, Boolean(yamlDefaultTabCommands)) + } } - if (yamlSetup) { - return { source: 'yaml', command: yamlSetup } + if (yamlCommand) { + return { + source: 'yaml', + command: yamlCommand, + kind: getSetupConfigKind(Boolean(yamlSetup), Boolean(yamlDefaultTabCommands)) + } } return null }