Files
orca/src/shared/structured-native-chat-launch-route.ts
T
Brennan Benson f06f737802 Unify Source Control action permissions and card layout (#30449)
* 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
2026-10-26 16:01:56 -07:00

127 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/** Share settings defaults and per-launch feasibility between renderer tab launches and orchestration workers to prevent drift. */
import { isAgentSessionHandleProvider } from './agent-session-provider-handle'
import { parseExecutionHostId } from './execution-host'
import type { GlobalSettings } from './global-settings-types'
import type { NativeChatProviderSettings } from './native-chat/native-chat-providers'
import type { ProjectExecutionRuntimeResolution } from './project-execution-runtime'
import {
STRUCTURED_AGENT_SESSION_CLIENT_LAUNCH_MODE_CAPABILITY,
STRUCTURED_AGENT_SESSION_REGISTERED_AGENTS_RUNTIME_CAPABILITY,
STRUCTURED_AGENT_SESSION_RUNTIME_CAPABILITY
} from './protocol-version'
import type { TuiAgent } from './tui-agent'
import type { WorkspaceLaunchKind } from './workspace-launch-kind'
export type NativeChatDefaultSettings = Pick<GlobalSettings, 'experimentalNativeChat'> &
Partial<NativeChatProviderSettings>
/** Why a launch that the user's default asked to be structured cannot be. */
export type StructuredNativeChatBlocker =
| 'reused-terminal'
| 'agent-without-structured-session'
/** Only PTYs apply a start directory outside the workspace; configured command/arguments do not choose the surface. */
| 'custom-start-directory'
| 'remote-execution-host'
| 'project-runtime'
| 'runtime-capability'
/** Unestablished host support is unknown, distinct from an explicit capability refusal. */
| 'runtime-capability-unknown'
/** This client does not tell a paired host it can read and launch structured sessions. */
| 'client-capability'
export type StructuredNativeChatSupport =
| { supported: true }
| { supported: false; blocker: StructuredNativeChatBlocker }
export type StructuredNativeChatSupportInput = {
agent: TuiAgent
executionHostId: string
/** Owning host’s capabilities, including paired servers; null means unestablished. */
hostCapabilities: readonly string[] | null
/** What this client advertises to a paired host; read only for `runtime:` hosts. */
clientCapabilities?: readonly string[]
/** Host-derived. Absent means the kind was never established, which is not evidence of any kind. */
workspaceKind?: WorkspaceLaunchKind
projectExecutionKind?: 'host' | 'ssh'
projectRuntime?: ProjectExecutionRuntimeResolution | null
startsOutsideWorkspaceRoot?: boolean
/** An existing PTY agent keeps its execution transport. */
reusesTerminal?: boolean
/** Without host inventory, offer only Claude/Codex, which every structured host supports. */
hostStructuredAgents?: readonly string[]
}
/** The single Chat UI switch selects structured chat for supported new launches. */
export function isNativeChatEnabled(
settings: Partial<NativeChatDefaultSettings> | null | undefined
): boolean {
return settings?.experimentalNativeChat === true
}
/** Unchosen agents retain the chat default, independently of installation inventory. */
export function isNativeChatDefaultFor(
settings: Partial<NativeChatDefaultSettings> | null | undefined,
agent: TuiAgent
): boolean {
return isNativeChatEnabled(settings) && (settings?.nativeChatProviders?.[agent] ?? true)
}
function clientChoosesStructuredLaunches(capabilities: readonly string[] | undefined): boolean {
return (
capabilities?.includes(STRUCTURED_AGENT_SESSION_RUNTIME_CAPABILITY) === true &&
capabilities.includes(STRUCTURED_AGENT_SESSION_CLIENT_LAUNCH_MODE_CAPABILITY)
)
}
export function resolveStructuredNativeChatSupport(
input: StructuredNativeChatSupportInput
): StructuredNativeChatSupport {
// Paired runtimes execute structured sessions themselves; relay SSH hosts cannot.
const host = parseExecutionHostId(input.executionHostId)
if (
input.projectExecutionKind === 'ssh' ||
(host?.kind !== 'local' && host?.kind !== 'runtime')
) {
return { supported: false, blocker: 'remote-execution-host' }
}
if (input.reusesTerminal === true) {
return { supported: false, blocker: 'reused-terminal' }
}
const builtInAgent = isAgentSessionHandleProvider(input.agent)
if (!builtInAgent && !input.hostStructuredAgents?.includes(input.agent)) {
return { supported: false, blocker: 'agent-without-structured-session' }
}
if (input.startsOutsideWorkspaceRoot === true) {
return { supported: false, blocker: 'custom-start-directory' }
}
const projectRuntime = input.projectRuntime
if (projectRuntime?.status === 'repair-required' || projectRuntime?.runtime.kind === 'wsl') {
return { supported: false, blocker: 'project-runtime' }
}
if (input.hostCapabilities === null) {
return { supported: false, blocker: 'runtime-capability-unknown' }
}
if (
!input.hostCapabilities.includes(STRUCTURED_AGENT_SESSION_RUNTIME_CAPABILITY) ||
// A host that does not advertise its registered agents accepts only Claude and Codex.
(!builtInAgent &&
!input.hostCapabilities.includes(
STRUCTURED_AGENT_SESSION_REGISTERED_AGENTS_RUNTIME_CAPABILITY
))
) {
return { supported: false, blocker: 'runtime-capability' }
}
if (host.kind === 'runtime') {
// Older paired hosts gate on their own chat setting, so retain terminal launches without client-launch-mode support.
if (!input.hostCapabilities.includes(STRUCTURED_AGENT_SESSION_CLIENT_LAUNCH_MODE_CAPABILITY)) {
return { supported: false, blocker: 'runtime-capability' }
}
// Hosts refuse unadvertised client support; retain terminals for those clients.
if (!clientChoosesStructuredLaunches(input.clientCapabilities)) {
return { supported: false, blocker: 'client-capability' }
}
}
return { supported: true }
}