mirror of
https://github.com/stablyai/orca.git
synced 2026-10-18 16:03:17 +00:00
Default agent launches to yolo permissions mode (#5145)
* Default agent launches to yolo mode * test: update launch default validations
This commit is contained in:
@@ -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', () => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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({
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user