mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 01:57:57 +00:00
* Source control actions start chats and terminals on their saved model A "start an agent" action's model lived only in free-text CLI arguments, which a native chat never reads, so AI Fix opened a chat on the default model. Each action now saves a structured model and effort that both routes apply: chat create options, or the agent's own flags in a terminal (winning over the same flag in CLI arguments). Desktop, dialog, fix-checks and mobile launches carry it; Settings rows get Model/Effort selects and the CLI arguments field says it is terminal-only. Tracked in STA-11102. * Name the model in the repository override note * Type the dialog's model hand-off; keep Agent in English in Japanese copy * fix: preserve action chat model choices through launch * Preserve action selection ownership across chat launches * Keep terminal host contract fixtures aligned with preflight * fix: wait for action Settings agent inventory * fix: consolidate direct agent routing imports * fix: use standard action option labels * fix: align action launch contracts with shared types * fix: sync action option localization metadata * test: provide action Settings inventory in mounted fixtures * Keep action options row height stable while loading * Let each Source Control action choose its chat permission mode An action's saved chat choice may now carry a permission mode, with or without a model; "Same as new chats" saves nothing. A model is replaced only when the action names one, so a permission-only choice keeps the chat's remembered model and options. * fix(native-chat): leave older hosts their own permission mode and keep action options readable - An action's saved permission mode is sent only to hosts that advertise agentSession.create.options-mode.v1. Older hosts get no mode, in create options or a later setOption, so the chat starts on that host's new-chat default, as a phone launch to such a host already does. Hosts from 1.4.225 to 1.4.227 refused a mode their tables lacked (OpenCode, Grok, OMP), leaving the action unable to start. - Settings action rows give Model and Permissions full rows in the options column so saved values are readable; the loading placeholder reserves the same rows. - Remove two unused locale keys; fix two stale comments. - Tests: the older-host create carries no permission mode; a permission mode picked alone keeps the remembered model on an acquired create. * fix(settings): reserve one options row while loading an action with nothing saved Terminal and permissions-only rows no longer shrink when loading ends; a chat agent with a model list may grow by its Model row once known. * fix(native-chat): keep the create options mode a checked literal type * fix(settings): share verified action option fields * fix(settings): keep action selectors on standard typography * Keep the launch files under their length limits after the main merge The structured route imports its result types where they are defined, and the two older-host action-launch tests share one setup helper. * One action settings section for chat and terminal launches A Source Control action's Model, model options and Permissions now apply whether it opens a chat or a terminal; terminals receive them as the agent's own flags, and a repository's own CLI arguments keep the flags it sets. Extra CLI arguments stay terminal-only. * fix(source-control-ai): one terminal projection for saved action choices Ask in a terminal now states its permission instead of leaving it to the user's own config: Claude and Grok pass --permission-mode default, Codex passes -a on-request -s read-only -c approvals_reviewer="user" (the interactive CLI has no untrusted policy, so a read-only sandbox is what asks before edits). Agents whose CLI has no flag for a mode show the existing "Applies to chats only" hint. The strict launch-preferences projection now requires the launch agent at every caller, composes model ids through the resolver's own step, carries a mode only where the agent's terminal has a flag for it, and the local string wire keeps booleans as 'true'/'false', so Cursor Thinking/Fast reach the terminal and an OpenCode permission-only choice is not refused. The argument parser knows flag arity, so removing a boolean permission flag keeps other options' values, following positionals and '--'. Pre-admission and executor share one terminal admission check on the options the terminal will receive. Customize with Save launches the recipe as it will resolve once saved; legacy partial repository overrides are edited as the complete recipe they resolve to. Empty Extra CLI arguments start collapsed. * fix(source-control-ai): an edited repository action saves complete Every Settings edit of a repository action (agent, model, permission, prompt or arguments) now starts from the complete recipe the action resolves to, whether its stored override is owned or a legacy partial. The resolved agent selection is kept (never an agent only detected to run a default), an explicit null args clear stays null, and empty args stay empty instead of being dropped. Clearing the last preference therefore means the action agent's own defaults rather than re-inheriting Global. The action text draft is lossless: unset or cleared prompt and args stay so until the user types, and the inputs show them as empty, so saving a prompt no longer writes empty args that suppress the agent's default flags. Untouched sibling actions are not rewritten by the edit. * fix(settings): style the Extra CLI arguments toggle through Button's own variant and size CI's changed-code quality check refuses restyle classes on Button and CollapsibleContent. Use the ghost variant at size xs with a margin, and put the content spacing on a plain inner wrapper. * fix: keep saved action and restart launch recipes consistent * fix: describe inherited arguments and chat-only permissions accurately * test: check repository action settings fixture type * test: map saved action options into terminal compiler inputs * fix: retain saved action controls during agent detection * fix(i18n): retire obsolete Portuguese action labels * test: align folder launch fallback mock with outcome export * fix: carry resolved connector launch intent and mode together * test: pin shared action launch branches to recorded agent output * test: preserve saved source control choices through detection and migration * test: align launch fixtures with integrated result contract * test: retain recipe and restart contracts after main integration * Unify Source Control action permission settings * Keep native Ask copy with its owning change * Preserve action drafts and old-host permission intent * Consume rejected Source Control action saves * Follow the moved Settings persistence fixture * Keep permission integration contracts in the cross-version test project * Include the action permission inheritance label in English catalogs * Align launch contracts with shared permission recipes * Keep Source Control action controls within their Settings columns * Use primitive variants for compact action settings controls * Show action arguments only for terminal defaults and widen recipe rows * Apply terminal-only action arguments visibility to repository overrides * Align action permission control with model controls * Use shared settings label for action permissions * Hide action model controls until argument migration ships
244 lines
8.0 KiB
TypeScript
244 lines
8.0 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
resolveAgentStartupPlanInputs,
|
|
type AgentStartupSettings
|
|
} from './agent-startup-plan-inputs'
|
|
import { buildAgentStartupPlan } from './tui-agent-startup'
|
|
|
|
const EMPTY_SETTINGS: AgentStartupSettings = {}
|
|
|
|
describe('resolveAgentStartupPlanInputs', () => {
|
|
it('reads the four launch settings and defaults an absent command override map', () => {
|
|
const inputs = resolveAgentStartupPlanInputs({
|
|
agent: 'claude',
|
|
settings: {
|
|
agentCmdOverrides: { claude: 'my-claude' },
|
|
agentDefaultArgs: { claude: '--verbose' },
|
|
agentDefaultEnv: { claude: { FOO: 'bar' } }
|
|
},
|
|
platform: 'darwin',
|
|
isRemote: false,
|
|
shell: undefined
|
|
})
|
|
|
|
expect(inputs.cmdOverrides).toEqual({ claude: 'my-claude' })
|
|
expect(inputs.agentArgs).toBe('--verbose')
|
|
expect(inputs.agentEnv).toEqual({ FOO: 'bar' })
|
|
expect(
|
|
resolveAgentStartupPlanInputs({
|
|
agent: 'claude',
|
|
settings: EMPTY_SETTINGS,
|
|
platform: 'darwin',
|
|
isRemote: false,
|
|
shell: undefined
|
|
}).cmdOverrides
|
|
).toEqual({})
|
|
})
|
|
|
|
it('treats an explicit null agentArgs as "no arguments" rather than "use the default"', () => {
|
|
const settings: AgentStartupSettings = { agentDefaultArgs: { claude: '--verbose' } }
|
|
const base = {
|
|
agent: 'claude' as const,
|
|
settings,
|
|
platform: 'darwin' as const,
|
|
isRemote: false,
|
|
shell: undefined
|
|
}
|
|
|
|
expect(resolveAgentStartupPlanInputs(base).agentArgs).toBe('--verbose')
|
|
expect(resolveAgentStartupPlanInputs({ ...base, agentArgs: null }).agentArgs).toBeNull()
|
|
expect(resolveAgentStartupPlanInputs({ ...base, agentArgs: '--model opus' }).agentArgs).toBe(
|
|
'--model opus'
|
|
)
|
|
})
|
|
|
|
it('classifies the Windows shell only for a local win32 launch', () => {
|
|
const settings: AgentStartupSettings = { terminalWindowsShell: 'git-bash' }
|
|
|
|
expect(
|
|
resolveAgentStartupPlanInputs({
|
|
agent: 'claude',
|
|
settings,
|
|
platform: 'win32',
|
|
isRemote: false,
|
|
shell: undefined
|
|
}).shell
|
|
).toBe('posix')
|
|
// Remote targets need their own shell signal before quoting can be overridden.
|
|
expect(
|
|
resolveAgentStartupPlanInputs({
|
|
agent: 'claude',
|
|
settings,
|
|
platform: 'win32',
|
|
isRemote: true,
|
|
shell: undefined
|
|
}).shell
|
|
).toBeUndefined()
|
|
expect(
|
|
resolveAgentStartupPlanInputs({
|
|
agent: 'claude',
|
|
settings,
|
|
platform: 'darwin',
|
|
isRemote: false,
|
|
shell: undefined
|
|
}).shell
|
|
).toBeUndefined()
|
|
})
|
|
|
|
it("lets the host's resolved shell override the configured one", () => {
|
|
const inputs = resolveAgentStartupPlanInputs({
|
|
agent: 'claude',
|
|
settings: { terminalWindowsShell: 'git-bash' },
|
|
platform: 'win32',
|
|
isRemote: false,
|
|
shell: 'cmd'
|
|
})
|
|
|
|
expect(inputs.shell).toBe('cmd')
|
|
})
|
|
|
|
it('ties sessionOptionsOverrideAgentArgs to whether options were actually picked', () => {
|
|
const base = {
|
|
agent: 'codex' as const,
|
|
settings: EMPTY_SETTINGS,
|
|
platform: 'darwin' as const,
|
|
isRemote: false,
|
|
shell: undefined
|
|
}
|
|
|
|
const without = resolveAgentStartupPlanInputs(base)
|
|
expect(without.sessionOptionsOverrideAgentArgs).toBe(false)
|
|
expect(without).not.toHaveProperty('sessionOptions')
|
|
|
|
const withOptions = resolveAgentStartupPlanInputs({
|
|
...base,
|
|
sessionOptions: { model: 'gpt-5' }
|
|
})
|
|
expect(withOptions.sessionOptionsOverrideAgentArgs).toBe(true)
|
|
expect(withOptions.sessionOptions).toEqual({ model: 'gpt-5' })
|
|
})
|
|
})
|
|
|
|
describe('a picked session option outranks configured launch arguments', () => {
|
|
// Compose the surfaces: prove one model flag reaches argv and the chat can name the selected model, beyond asserting the policy flag.
|
|
function commandFor(settings: AgentStartupSettings) {
|
|
return buildAgentStartupPlan({
|
|
...resolveAgentStartupPlanInputs({
|
|
agent: 'codex',
|
|
settings,
|
|
platform: 'darwin',
|
|
isRemote: false,
|
|
shell: undefined,
|
|
sessionOptions: { model: 'gpt-5' }
|
|
}),
|
|
prompt: '',
|
|
allowEmptyPromptLaunch: true
|
|
})
|
|
}
|
|
|
|
it('strips the configured model flag instead of emitting both spellings', () => {
|
|
const plan = commandFor({ agentDefaultArgs: { codex: '--model gpt-5-codex' } })
|
|
|
|
expect(plan?.launchCommand).toBe("codex '-m' 'gpt-5'")
|
|
// Without the override the configured flag trailed the picked one and won on argv order.
|
|
expect(plan?.launchCommand).not.toContain('gpt-5-codex')
|
|
})
|
|
|
|
it('reports the picked option as launch-backed so the chat surface can render it', () => {
|
|
expect(
|
|
commandFor({ agentDefaultArgs: { codex: '--model gpt-5-codex' } })?.sessionOptions
|
|
).toEqual({ model: 'gpt-5' })
|
|
})
|
|
|
|
it('leaves non-conflicting configured arguments alone', () => {
|
|
expect(commandFor({ agentDefaultArgs: { codex: '--search' } })?.launchCommand).toContain(
|
|
"'--search'"
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('an Orca-driven launch skips the startup update prompt (#15814, #18153)', () => {
|
|
function planFor(
|
|
agent: 'codex' | 'claude',
|
|
suppressStartupUpdatePrompt: boolean,
|
|
sessionOptions?: { model: string }
|
|
) {
|
|
return buildAgentStartupPlan({
|
|
...resolveAgentStartupPlanInputs({
|
|
agent,
|
|
settings: { agentDefaultArgs: { codex: '--search' } },
|
|
platform: 'darwin',
|
|
isRemote: false,
|
|
shell: undefined,
|
|
suppressStartupUpdatePrompt,
|
|
...(sessionOptions ? { sessionOptions } : {})
|
|
}),
|
|
prompt: '',
|
|
allowEmptyPromptLaunch: true
|
|
})
|
|
}
|
|
|
|
it('adds Codex config only to the typed command, never to the resumable one', () => {
|
|
for (const sessionOptions of [undefined, { model: 'gpt-5' }]) {
|
|
const ordinary = planFor('codex', false, sessionOptions)
|
|
const suppressed = planFor('codex', true, sessionOptions)
|
|
expect(ordinary?.launchCommand).not.toContain('check_for_update_on_startup')
|
|
expect(suppressed?.launchCommand).toBe(
|
|
ordinary?.launchCommand.replace(/^codex/, "codex '-c' 'check_for_update_on_startup=false'")
|
|
)
|
|
expect(suppressed?.launchConfig).toEqual(ordinary?.launchConfig)
|
|
}
|
|
})
|
|
|
|
it('leaves agents without a startup updater unchanged', () => {
|
|
expect(planFor('claude', true)?.launchCommand).toBe(planFor('claude', false)?.launchCommand)
|
|
})
|
|
})
|
|
|
|
it('projects own permission without dropping unrelated arguments, model or environment on terminal fallback', () => {
|
|
const inputs = resolveAgentStartupPlanInputs({
|
|
agent: 'codex',
|
|
settings: {
|
|
agentPermissionDefault: 'full-access',
|
|
agentDefaultArgs: { codex: '--search' },
|
|
agentDefaultEnv: { codex: { KEEP: 'value' } }
|
|
},
|
|
platform: 'linux',
|
|
isRemote: false,
|
|
shell: 'posix',
|
|
sessionOptions: { permissionMode: 'own', model: 'gpt-5.5' }
|
|
})
|
|
const plan = buildAgentStartupPlan({ ...inputs, prompt: '', allowEmptyPromptLaunch: true })
|
|
expect(plan?.launchCommand).toContain("'--search'")
|
|
expect(plan?.launchCommand).toContain("'-m' 'gpt-5.5'")
|
|
expect(plan?.launchCommand).not.toContain('bypass')
|
|
expect(plan?.env).toEqual({ KEEP: 'value' })
|
|
expect(plan?.sessionOptions?.permissionMode).toBe('own')
|
|
})
|
|
|
|
it.each([undefined, ''])(
|
|
'keeps a terminal-only choice from args %s and projects its flag once for a legacy launch',
|
|
(agentArgs) => {
|
|
const inputs = resolveAgentStartupPlanInputs({
|
|
agent: 'gemini',
|
|
settings: {},
|
|
agentArgs,
|
|
platform: 'win32',
|
|
isRemote: true,
|
|
shell: 'powershell',
|
|
sessionOptions: { permissionMode: 'full-access' }
|
|
})
|
|
const plan = buildAgentStartupPlan({ ...inputs, prompt: '', allowEmptyPromptLaunch: true })
|
|
expect(plan?.launchConfig.agentArgs.match(/--yolo/g)).toHaveLength(1)
|
|
expect(plan?.sessionOptions?.permissionMode).toBe('full-access')
|
|
const legacy = buildAgentStartupPlan({
|
|
...inputs,
|
|
agentArgs: plan?.launchConfig.agentArgs,
|
|
sessionOptions: undefined,
|
|
prompt: '',
|
|
allowEmptyPromptLaunch: true
|
|
})
|
|
expect(legacy?.launchCommand.match(/--yolo/g)).toHaveLength(1)
|
|
}
|
|
)
|