mirror of
https://github.com/stablyai/orca.git
synced 2026-10-29 08:07:18 +00:00
Fix default tab command setup policy
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
committed by
Neil
co-authored by
Orca
parent
b4128ba998
commit
429f5e5d1b
+17
-1
@@ -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
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
+11
-1
@@ -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 }
|
||||
}
|
||||
|
||||
|
||||
@@ -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({
|
||||
<div className="space-y-2">
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<label className="text-xs font-medium text-muted-foreground">
|
||||
Setup script
|
||||
{setupConfigLabel}
|
||||
</label>
|
||||
<span className="rounded-full border border-border/70 bg-muted/45 px-2 py-0.5 text-[10px] font-medium uppercase tracking-[0.14em] text-foreground/70">
|
||||
{setupConfig.source === 'yaml'
|
||||
@@ -600,7 +625,7 @@ export default function NewWorkspaceComposerCard({
|
||||
}
|
||||
className="sr-only"
|
||||
/>
|
||||
<span>Run setup command</span>
|
||||
<span>{setupRunLabel}</span>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
@@ -609,7 +634,7 @@ export default function NewWorkspaceComposerCard({
|
||||
{requiresExplicitSetupChoice ? (
|
||||
<div className="space-y-2">
|
||||
<div className="text-[11px] font-medium text-muted-foreground">
|
||||
Run setup now?
|
||||
{setupAskLabel}
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Button
|
||||
@@ -618,7 +643,7 @@ export default function NewWorkspaceComposerCard({
|
||||
variant={setupDecision === 'run' ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
>
|
||||
Run setup now
|
||||
{setupRunButtonLabel}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
@@ -626,7 +651,7 @@ export default function NewWorkspaceComposerCard({
|
||||
variant={setupDecision === 'skip' ? 'secondary' : 'outline'}
|
||||
size="sm"
|
||||
>
|
||||
Skip for now
|
||||
{setupSkipButtonLabel}
|
||||
</Button>
|
||||
</div>
|
||||
{!setupDecision ? (
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user