mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 17:17:38 +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
266 lines
11 KiB
TypeScript
266 lines
11 KiB
TypeScript
import { SourceControlActionArguments } from './SourceControlActionArguments'
|
|
import { Terminal } from 'lucide-react'
|
|
import type { GlobalSettings } from '../../../../shared/global-settings-types'
|
|
import type { TuiAgent } from '../../../../shared/tui-agent'
|
|
import type { CustomAgentId } from '../../../../shared/commit-message-agent-spec'
|
|
import { CUSTOM_AGENT_ID, isCustomAgentId } from '../../../../shared/commit-message-agent-spec'
|
|
import {
|
|
SOURCE_CONTROL_ACTION_LABELS,
|
|
type AiActionId,
|
|
type SourceControlActionLaunchPreferences
|
|
} from '../../../../shared/source-control-ai-actions'
|
|
import { AgentIcon } from '@/lib/agent-catalog'
|
|
import { SourceControlActionVariableChips } from '../source-control/SourceControlActionVariableChips'
|
|
import { Button } from '../ui/button'
|
|
import { Input } from '../ui/input'
|
|
import { Label } from '../ui/label'
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'
|
|
import type { ActionRecipeDraftValue } from './source-control-ai-action-recipe-draft'
|
|
import {
|
|
getActionDescriptions,
|
|
AI_TEXT_ACTION_ID_SET,
|
|
getAgentCatalogForAction,
|
|
getSourceControlActionAgentSupportText,
|
|
getSourceControlActionAgentWarningText,
|
|
getSourceControlAgentArgsPlaceholder
|
|
} from './source-control-action-recipe-options'
|
|
import { translate } from '@/i18n/i18n'
|
|
import { SourceControlActionOptionField } from './SourceControlActionOptionField'
|
|
import { useSourceControlActionAgent } from './use-source-control-action-agent'
|
|
import { shouldShowSourceControlActionArguments } from './source-control-action-arguments-visibility'
|
|
import { chatProviderAgents, useHostStructuredAgentsVersion } from '@/lib/chat-provider-agents'
|
|
import { LOCAL_EXECUTION_HOST_ID } from '../../../../shared/execution-host'
|
|
import { useAppStore } from '@/store'
|
|
|
|
const DEFAULT_AGENT_VALUE = '__default_agent__'
|
|
|
|
type SourceControlActionRecipeRowProps<ActionId extends AiActionId> = {
|
|
actionId: ActionId
|
|
selectedAgent: TuiAgent | CustomAgentId | null
|
|
draftValue: ActionRecipeDraftValue
|
|
baseValue: ActionRecipeDraftValue
|
|
defaultTuiAgent: GlobalSettings['defaultTuiAgent']
|
|
isSavingTemplate: boolean
|
|
hasUnsavedChanges?: boolean
|
|
repoOverrideNote?: React.ReactNode
|
|
onAgentChange: (actionId: ActionId, value: string) => void
|
|
/** Launch actions only, for chats and terminals alike; text actions run headless through CLI arguments. */
|
|
launchPreferences?: SourceControlActionLaunchPreferences
|
|
onLaunchPreferencesChange?: (
|
|
actionId: ActionId,
|
|
value: SourceControlActionLaunchPreferences | undefined
|
|
) => void
|
|
onTemplateChange: (actionId: ActionId, value: string) => void
|
|
onAgentArgsChange: (actionId: ActionId, value: string, commit?: boolean) => void
|
|
onAppendVariable: (actionId: ActionId, variable: string) => void
|
|
onDiscard: (actionId: ActionId) => void
|
|
onSave: (actionId: ActionId) => void
|
|
}
|
|
|
|
function resolveAgentArgsPlaceholderAgent(
|
|
selectedAgent: TuiAgent | CustomAgentId | null | undefined,
|
|
defaultTuiAgent: GlobalSettings['defaultTuiAgent']
|
|
): TuiAgent | null {
|
|
if (selectedAgent && !isCustomAgentId(selectedAgent)) {
|
|
return selectedAgent
|
|
}
|
|
return defaultTuiAgent && defaultTuiAgent !== 'blank' ? defaultTuiAgent : null
|
|
}
|
|
|
|
function getActionLabel(actionId: AiActionId): string {
|
|
return actionId === 'conversationName'
|
|
? translate('settings.chat.names.recipe', 'Chat name')
|
|
: SOURCE_CONTROL_ACTION_LABELS[actionId]
|
|
}
|
|
|
|
export function SourceControlActionRecipeRow<ActionId extends AiActionId>({
|
|
actionId,
|
|
selectedAgent,
|
|
draftValue,
|
|
baseValue,
|
|
defaultTuiAgent,
|
|
isSavingTemplate,
|
|
hasUnsavedChanges,
|
|
repoOverrideNote,
|
|
onAgentChange,
|
|
launchPreferences,
|
|
onLaunchPreferencesChange,
|
|
onTemplateChange,
|
|
onAgentArgsChange,
|
|
onAppendVariable,
|
|
onDiscard,
|
|
onSave
|
|
}: SourceControlActionRecipeRowProps<ActionId>): React.JSX.Element {
|
|
const actual = useSourceControlActionAgent(selectedAgent, defaultTuiAgent)
|
|
const settings = useAppStore((state) => state.settings)
|
|
useHostStructuredAgentsVersion()
|
|
const showArguments = shouldShowSourceControlActionArguments({
|
|
agent: actual.status === 'ready' ? actual.agent : null,
|
|
settings,
|
|
chatAgents: chatProviderAgents(LOCAL_EXECUTION_HOST_ID, undefined)
|
|
})
|
|
const expectedAgent = resolveAgentArgsPlaceholderAgent(selectedAgent, defaultTuiAgent)
|
|
const pickerAgent = selectedAgent || actual.status === 'pending' ? expectedAgent : actual.agent
|
|
const isLaunchAction = !AI_TEXT_ACTION_ID_SET.has(actionId)
|
|
const templateDirty =
|
|
hasUnsavedChanges ?? JSON.stringify(draftValue) !== JSON.stringify(baseValue)
|
|
const agentArgsPlaceholder = getSourceControlAgentArgsPlaceholder(
|
|
resolveAgentArgsPlaceholderAgent(selectedAgent, defaultTuiAgent)
|
|
)
|
|
const agentOptions = getAgentCatalogForAction(actionId, selectedAgent)
|
|
const agentWarningText = getSourceControlActionAgentWarningText(actionId, selectedAgent)
|
|
const agentSupportText = getSourceControlActionAgentSupportText(actionId)
|
|
|
|
return (
|
|
<div className="rounded-md border border-border px-3 py-3">
|
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
|
<div className="min-w-0 space-y-0.5">
|
|
<p className="text-xs font-medium text-foreground">{getActionLabel(actionId)}</p>
|
|
<p className="text-[11px] text-muted-foreground">{getActionDescriptions()[actionId]}</p>
|
|
</div>
|
|
<div className="w-full shrink-0 space-y-1 sm:w-[220px]">
|
|
<Select
|
|
value={selectedAgent ?? DEFAULT_AGENT_VALUE}
|
|
onValueChange={(value) => onAgentChange(actionId, value)}
|
|
>
|
|
<SelectTrigger size="sm" density="compact" className="h-8 w-full">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value={DEFAULT_AGENT_VALUE}>
|
|
<span className="flex items-center gap-2">
|
|
<Terminal className="size-3.5 text-muted-foreground" />
|
|
{translate(
|
|
'auto.components.settings.SourceControlAiActionRecipeDefaults.ee0e5c2a48',
|
|
'Use default agent'
|
|
)}
|
|
</span>
|
|
</SelectItem>
|
|
{AI_TEXT_ACTION_ID_SET.has(actionId) ? (
|
|
<SelectItem value={CUSTOM_AGENT_ID}>
|
|
<span className="flex items-center gap-2">
|
|
<Terminal className="size-3.5 text-muted-foreground" />
|
|
{translate(
|
|
'auto.components.settings.SourceControlAiActionRecipeDefaults.0740d30915',
|
|
'Custom command'
|
|
)}
|
|
</span>
|
|
</SelectItem>
|
|
) : null}
|
|
{agentOptions.map((agent) => (
|
|
<SelectItem key={agent.id} value={agent.id}>
|
|
<span className="flex items-center gap-2">
|
|
<AgentIcon agent={agent.id} size={14} />
|
|
{agent.label}
|
|
</span>
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
{agentWarningText ? (
|
|
<p className="text-[11px] text-destructive">{agentWarningText}</p>
|
|
) : agentSupportText ? (
|
|
<p className="text-[11px] text-muted-foreground">{agentSupportText}</p>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
<div className="mt-3 grid min-w-0 grid-cols-1 items-start gap-3">
|
|
{isLaunchAction ? (
|
|
<div className="flex min-w-0 flex-wrap items-start gap-3">
|
|
<SourceControlActionArguments
|
|
showArguments={showArguments}
|
|
agent={pickerAgent}
|
|
value={draftValue.agentArgs}
|
|
preferences={launchPreferences}
|
|
disabled={actual.status === 'pending' || isSavingTemplate}
|
|
onArgsChange={(value, commit) => onAgentArgsChange(actionId, value, commit)}
|
|
onPreferencesChange={(value) => onLaunchPreferencesChange?.(actionId, value)}
|
|
/>
|
|
</div>
|
|
) : (
|
|
<SourceControlActionOptionField
|
|
label={translate('auto.components.settings.AgentsPane.cfb3f35775', 'Arguments')}
|
|
>
|
|
<Input
|
|
value={draftValue.agentArgs}
|
|
spellCheck={false}
|
|
placeholder={agentArgsPlaceholder}
|
|
onChange={(event) => onAgentArgsChange(actionId, event.target.value)}
|
|
variant="code"
|
|
className="h-8"
|
|
/>
|
|
</SourceControlActionOptionField>
|
|
)}
|
|
<div className="min-w-0 space-y-2">
|
|
<Label variant="settings">
|
|
{translate(
|
|
'auto.components.settings.SourceControlAiActionRecipeDefaults.fb09da4345',
|
|
'Command template'
|
|
)}
|
|
</Label>
|
|
<textarea
|
|
value={draftValue.commandInputTemplate}
|
|
rows={3}
|
|
spellCheck={false}
|
|
onChange={(event) => onTemplateChange(actionId, event.target.value)}
|
|
className="w-full resize-y rounded-md border border-border bg-background px-2.5 py-2 font-mono text-xs text-foreground outline-none placeholder:text-muted-foreground/70 focus-visible:ring-1 focus-visible:ring-ring"
|
|
/>
|
|
<SourceControlActionVariableChips
|
|
actionId={actionId}
|
|
onInsert={(variable) => onAppendVariable(actionId, variable)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="mt-3 space-y-2">
|
|
{repoOverrideNote}
|
|
<div className="flex items-center justify-between gap-3">
|
|
<p className="text-[11px] text-muted-foreground">
|
|
{templateDirty
|
|
? translate(
|
|
'auto.components.settings.SourceControlAiActionRecipeDefaults.817128d94e',
|
|
'Unsaved changes'
|
|
)
|
|
: translate(
|
|
'auto.components.settings.SourceControlAiActionRecipeDefaults.9d3cc627f8',
|
|
'Saved'
|
|
)}
|
|
</p>
|
|
<div className="flex items-center gap-2">
|
|
{templateDirty ? (
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
size="xs"
|
|
onClick={() => onDiscard(actionId)}
|
|
disabled={isSavingTemplate}
|
|
>
|
|
{translate(
|
|
'auto.components.settings.SourceControlAiActionRecipeDefaults.b3914ecbbc',
|
|
'Discard'
|
|
)}
|
|
</Button>
|
|
) : null}
|
|
<Button
|
|
type="button"
|
|
variant="secondary"
|
|
size="xs"
|
|
onClick={() => onSave(actionId)}
|
|
disabled={!templateDirty || isSavingTemplate}
|
|
>
|
|
{isSavingTemplate
|
|
? translate(
|
|
'auto.components.settings.SourceControlAiActionRecipeDefaults.4f549a5fa8',
|
|
'Saving...'
|
|
)
|
|
: translate(
|
|
'auto.components.settings.SourceControlAiActionRecipeDefaults.d18d665e12',
|
|
'Save'
|
|
)}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|