Default agent launches to yolo permissions mode (#5145)

* Default agent launches to yolo mode

* test: update launch default validations
This commit is contained in:
Neil
2026-06-11 12:51:55 -07:00
committed by Jinwoo-H
parent 470d46ecea
commit e4050eccbd
50 changed files with 869 additions and 193 deletions
+17
View File
@@ -56,6 +56,23 @@ describe('getDefaultSettings', () => {
it('keeps compact worktree cards disabled by default', () => {
expect(getDefaultSettings('/tmp').compactWorktreeCards).toBe(false)
})
it('defaults agent launch args to yolo mode where the CLI supports it', () => {
const settings = getDefaultSettings('/tmp')
expect(settings.agentDefaultArgs).toMatchObject({
claude: '--dangerously-skip-permissions',
codex: '--dangerously-bypass-approvals-and-sandbox',
gemini: '--yolo',
cursor: '--yolo',
copilot: '--yolo',
grok: '--permission-mode bypassPermissions'
})
expect(settings.agentDefaultEnv).toMatchObject({
goose: { GOOSE_MODE: 'auto' }
})
expect(settings.agentYoloDefaultsMigrated).toBe(true)
})
})
describe('getDefaultPrimarySelectionMiddleClickPaste', () => {
+4
View File
@@ -22,6 +22,7 @@ import { DEFAULT_APP_ICON_ID } from './app-icon'
import { DEFAULT_OPEN_IN_APPLICATIONS } from './open-in-applications'
import { DEFAULT_BROWSER_PAGE_ZOOM_LEVEL } from './browser-page-zoom'
import { DEFAULT_DISABLED_TUI_AGENTS } from './tui-agent-selection'
import { DEFAULT_TUI_AGENT_ARGS, DEFAULT_TUI_AGENT_ENV } from './tui-agent-launch-defaults'
import { UI_LANGUAGE_SYSTEM } from './ui-language'
export { DEFAULT_STATUS_BAR_ITEMS } from './status-bar-defaults'
@@ -289,6 +290,9 @@ export function getDefaultSettings(homedir: string): GlobalSettings {
opencodeWorkspaceId: '',
geminiCliOAuthEnabled: false,
agentCmdOverrides: {},
agentDefaultArgs: { ...DEFAULT_TUI_AGENT_ARGS },
agentDefaultEnv: { ...DEFAULT_TUI_AGENT_ENV },
agentYoloDefaultsMigrated: true,
agentStatusHooksEnabled: true,
tabAutoGenerateTitle: false,
keepComputerAwakeWhileAgentsRun: false,
-21
View File
@@ -51,12 +51,6 @@ export type TuiAgentConfig = {
draftPasteReadySignal?: DraftPasteReadySignal
}
// Why: the new-workspace handoff depends on three pieces of per-agent
// knowledge staying in sync: how Orca detects the agent on PATH, which binary
// it actually launches, and whether the initial prompt should be passed as an
// argv flag/argument or typed into the interactive session after startup.
// Centralizing that metadata prevents the picker, launcher, and preflight
// checks from quietly drifting apart as new agents are added.
export const TUI_AGENT_CONFIG: Record<TuiAgent, TuiAgentConfig> = {
claude: {
detectCmd: 'claude',
@@ -91,11 +85,6 @@ export const TUI_AGENT_CONFIG: Record<TuiAgent, TuiAgentConfig> = {
launchCmd: 'codex',
expectedProcess: 'codex',
promptInjectionMode: 'argv',
// Why: Codex's positional prompt auto-submits the first turn, so Orca
// must still paste a draft. The Codex TUI enables bracketed paste before
// the first render, then chat_composer.rs emits `›` when the composer row
// is visible. Waiting for that prompt skips the generic quiet timer while
// avoiding startup/onboarding screens that ignore paste.
preflightTrust: 'codex',
draftPasteReadySignal: 'codex-composer-prompt'
},
@@ -126,16 +115,6 @@ export const TUI_AGENT_CONFIG: Record<TuiAgent, TuiAgentConfig> = {
draftPromptEnvVar: 'ORCA_PI_PREFILL'
},
omp: {
// Why: OMP (omp.sh) is a Pi fork with its own binary (`omp`), brand,
// default config dir (~/.omp/agent), and overlay tree. It re-uses
// Pi's argv prompt-injection contract because the OMP binary inherits
// Pi's command-line parser, but every Orca-owned env var (overlay
// shadow, prefill) is scoped to OMP - see ORCA_OMP_* in
// src/main/pi/titlebar-extension-service.ts. The one var that MUST
// stay shared is `PI_CODING_AGENT_DIR`: OMP's CHANGELOG documents
// the deliberate rename of `OMP_CODING_AGENT_DIR` -> `PI_CODING_AGENT_DIR`
// (packages/ai/CHANGELOG.md), so the binary itself reads the PI-prefixed
// name and we have to set that to point at the OMP overlay dir.
detectCmd: 'omp',
launchCmd: 'omp',
expectedProcess: 'omp',
+103
View File
@@ -0,0 +1,103 @@
import { isTuiAgent } from './tui-agent-config'
import type { TuiAgent } from './types'
export const DEFAULT_TUI_AGENT_ARGS: Partial<Record<TuiAgent, string>> = {
claude: '--dangerously-skip-permissions',
'claude-agent-teams': '--dangerously-skip-permissions',
openclaude: '--dangerously-skip-permissions',
codex: '--dangerously-bypass-approvals-and-sandbox',
opencode: '--dangerously-skip-permissions',
gemini: '--yolo',
antigravity: '--dangerously-skip-permissions',
aider: '--yes-always',
amp: '--dangerously-allow-all',
kilo: '--dangerously-skip-permissions',
kiro: '--trust-all-tools',
crush: '--yolo',
autohand: '--unrestricted',
cline: '--auto-approve true',
'command-code': '--yolo',
continue: '--allow "*"',
cursor: '--yolo',
kimi: '--yolo',
'mistral-vibe': '--agent auto-approve',
'qwen-code': '--approval-mode yolo',
rovo: '--yolo',
hermes: '--yolo',
copilot: '--yolo',
grok: '--permission-mode bypassPermissions'
}
export const DEFAULT_TUI_AGENT_ENV: Partial<Record<TuiAgent, Record<string, string>>> = {
goose: { GOOSE_MODE: 'auto' }
}
export function normalizeTuiAgentArgsRecord(value: unknown): Partial<Record<TuiAgent, string>> {
const normalized: Partial<Record<TuiAgent, string>> = {}
if (!value || typeof value !== 'object') {
return normalized
}
for (const [agent, args] of Object.entries(value)) {
if (!isTuiAgent(agent) || typeof args !== 'string') {
continue
}
normalized[agent] = args.trim()
}
return normalized
}
export function normalizeTuiAgentEnvRecord(
value: unknown
): Partial<Record<TuiAgent, Record<string, string>>> {
const normalized: Partial<Record<TuiAgent, Record<string, string>>> = {}
if (!value || typeof value !== 'object') {
return normalized
}
for (const [agent, env] of Object.entries(value)) {
if (!isTuiAgent(agent) || !env || typeof env !== 'object') {
continue
}
const nextEnv: Record<string, string> = {}
for (const [name, raw] of Object.entries(env)) {
const key = name.trim()
if (!key || typeof raw !== 'string') {
continue
}
nextEnv[key] = raw
}
normalized[agent] = nextEnv
}
return normalized
}
export function getTuiAgentDefaultArgs(agent: TuiAgent): string {
return DEFAULT_TUI_AGENT_ARGS[agent] ?? ''
}
export function getTuiAgentDefaultEnv(agent: TuiAgent): Record<string, string> {
return { ...DEFAULT_TUI_AGENT_ENV[agent] }
}
export function resolveTuiAgentLaunchArgs(
agent: TuiAgent,
configuredArgs: Partial<Record<TuiAgent, string>> | null | undefined
): string {
if (
configuredArgs &&
Object.prototype.hasOwnProperty.call(configuredArgs, agent) &&
typeof configuredArgs[agent] === 'string'
) {
return configuredArgs[agent] ?? ''
}
return getTuiAgentDefaultArgs(agent)
}
export function resolveTuiAgentLaunchEnv(
agent: TuiAgent,
configuredEnv: Partial<Record<TuiAgent, Record<string, string>>> | null | undefined
): Record<string, string> {
if (configuredEnv && Object.prototype.hasOwnProperty.call(configuredEnv, agent)) {
return { ...configuredEnv[agent] }
}
return getTuiAgentDefaultEnv(agent)
}
+14
View File
@@ -172,6 +172,20 @@ describe('tui agent startup plans', () => {
expect(plan?.launchCommand).toBe("claude '--model' 'sonnet' '--name' 'Bob''s' 'fix it'")
})
it('carries agent launch environment defaults into startup plans', () => {
const plan = buildAgentStartupPlan({
agent: 'goose',
prompt: '',
cmdOverrides: {},
agentEnv: { GOOSE_MODE: 'auto' },
platform: 'linux',
allowEmptyPromptLaunch: true
})
expect(plan?.launchCommand).toBe('goose')
expect(plan?.env).toEqual({ GOOSE_MODE: 'auto' })
})
it('clears draft environment variables with the target shell syntax', () => {
expect(
buildAgentDraftLaunchPlan({
+23 -10
View File
@@ -106,6 +106,7 @@ export function buildAgentStartupPlan(args: {
shell?: AgentStartupShell
allowEmptyPromptLaunch?: boolean
agentArgs?: string | null
agentEnv?: Record<string, string> | null
}): AgentStartupPlan | null {
const { agent, prompt, cmdOverrides, platform, allowEmptyPromptLaunch = false } = args
const shell = resolveStartupShell(platform, args.shell)
@@ -129,7 +130,8 @@ export function buildAgentStartupPlan(args: {
agent,
launchCommand: baseCommand.command,
expectedProcess: config.expectedProcess,
followupPrompt: null
followupPrompt: null,
...(args.agentEnv ? { env: { ...args.agentEnv } } : {})
}
}
@@ -140,7 +142,8 @@ export function buildAgentStartupPlan(args: {
agent,
launchCommand: `${baseCommand.command} ${quotedPrompt}`,
expectedProcess: config.expectedProcess,
followupPrompt: null
followupPrompt: null,
...(args.agentEnv ? { env: { ...args.agentEnv } } : {})
}
}
@@ -149,7 +152,8 @@ export function buildAgentStartupPlan(args: {
agent,
launchCommand: `${baseCommand.command} --prompt ${quotedPrompt}`,
expectedProcess: config.expectedProcess,
followupPrompt: null
followupPrompt: null,
...(args.agentEnv ? { env: { ...args.agentEnv } } : {})
}
}
@@ -158,7 +162,8 @@ export function buildAgentStartupPlan(args: {
agent,
launchCommand: `${baseCommand.command} --prompt-interactive ${quotedPrompt}`,
expectedProcess: config.expectedProcess,
followupPrompt: null
followupPrompt: null,
...(args.agentEnv ? { env: { ...args.agentEnv } } : {})
}
}
@@ -167,7 +172,8 @@ export function buildAgentStartupPlan(args: {
agent,
launchCommand: `${baseCommand.command} -i ${quotedPrompt}`,
expectedProcess: config.expectedProcess,
followupPrompt: null
followupPrompt: null,
...(args.agentEnv ? { env: { ...args.agentEnv } } : {})
}
}
@@ -175,7 +181,8 @@ export function buildAgentStartupPlan(args: {
agent,
launchCommand: baseCommand.command,
expectedProcess: config.expectedProcess,
followupPrompt: trimmedPrompt
followupPrompt: trimmedPrompt,
...(args.agentEnv ? { env: { ...args.agentEnv } } : {})
}
}
@@ -185,6 +192,8 @@ export function buildAgentResumeStartupPlan(args: {
cmdOverrides: Partial<Record<TuiAgent, string>>
platform: NodeJS.Platform
shell?: AgentStartupShell
agentArgs?: string | null
agentEnv?: Record<string, string> | null
}): AgentStartupPlan | null {
const argv = getAgentResumeArgv(args.agent, args.providerSession)
if (!argv) {
@@ -195,7 +204,8 @@ export function buildAgentResumeStartupPlan(args: {
const baseCommand = resolveBaseCommand({
agent: args.agent,
cmdOverrides: args.cmdOverrides,
shell
shell,
agentArgs: args.agentArgs
})
if (!baseCommand.ok) {
return null
@@ -209,7 +219,8 @@ export function buildAgentResumeStartupPlan(args: {
agent: args.agent,
launchCommand,
expectedProcess: config.expectedProcess,
followupPrompt: null
followupPrompt: null,
...(args.agentEnv ? { env: { ...args.agentEnv } } : {})
}
}
@@ -227,6 +238,7 @@ export function buildAgentDraftLaunchPlan(args: {
platform: NodeJS.Platform
shell?: AgentStartupShell
agentArgs?: string | null
agentEnv?: Record<string, string> | null
}): AgentDraftLaunchPlan | null {
const { agent, draft, cmdOverrides, platform } = args
const shell = resolveStartupShell(platform, args.shell)
@@ -249,7 +261,8 @@ export function buildAgentDraftLaunchPlan(args: {
return {
agent,
launchCommand: `${baseCommand.command} ${config.draftPromptFlag} ${quoted}`,
expectedProcess: config.expectedProcess
expectedProcess: config.expectedProcess,
...(args.agentEnv ? { env: { ...args.agentEnv } } : {})
}
}
if (config.draftPromptEnvVar) {
@@ -258,7 +271,7 @@ export function buildAgentDraftLaunchPlan(args: {
agent,
launchCommand: `${baseCommand.command}${commandSeparator(shell)}${clearVar}`,
expectedProcess: config.expectedProcess,
env: { [config.draftPromptEnvVar]: trimmed }
env: { ...args.agentEnv, [config.draftPromptEnvVar]: trimmed }
}
}
return null
+6
View File
@@ -2418,6 +2418,12 @@ export type GlobalSettings = {
geminiCliOAuthEnabled: boolean
/** Per-agent CLI command overrides. A missing key means use the catalog default binary name. */
agentCmdOverrides: Partial<Record<TuiAgent, string>>
/** Per-agent default CLI arguments appended after the binary/path and before prompts. */
agentDefaultArgs?: Partial<Record<TuiAgent, string>>
/** Per-agent launch environment defaults used when yolo mode is exposed as env. */
agentDefaultEnv?: Partial<Record<TuiAgent, Record<string, string>>>
/** One-shot guard for adding yolo-mode default args to untouched agent launch profiles. */
agentYoloDefaultsMigrated?: boolean
/** Why: disabling must persist so startup does not reinstall global agent
* hook entries right after the user removes them from Settings or CLI. */
agentStatusHooksEnabled: boolean