Simplify the new-tab agent launch menu (#39279)

* feat(agent-launch): simplify the new-tab agent launch menu

Drop Permissions and Arguments from the launch-mode setup panel; they are
agent defaults edited in Agent settings. Put effort levels (unlabeled) and
Fast mode on one row, and make the Launch chat/terminal buttons fill the
row at a larger size with icons. Select-mode pickers (automations, new
workspace, source control, continuation) keep both controls.

* fix(agent-launch): let current Agent settings win over launch-menu speed edits

A Fast or speed-tier edit copies the current Permissions and Arguments into the
launch draft. With no Arguments editor in the launch menu, a later settings
change was silently ignored. Launch drafts now drop Arguments stamped by older
settings; the speed choice still applies. Also stop pointing hosts without a
model list at the removed Arguments editor.

* fix(agent-launch): show Fast as a button in the launch menu

Matches the mock: an outlined Fast chip with the lightning icon in place of the
"Fast mode" switch. The chat composer keeps its switch.
This commit is contained in:
Brennan Benson
2026-11-07 22:37:41 -08:00
committed by GitHub
parent 3b6d5d4c15
commit 104d01b686
24 changed files with 202 additions and 81 deletions
@@ -26,7 +26,15 @@ const entry: AgentPickerEntry = {
origin: 'probe',
fetchedAt: 1,
models: [
{ id: 'astra', label: 'Astra', isDefault: true, efforts: [{ value: 'low', label: 'Low' }] }
{
id: 'astra',
label: 'Astra',
isDefault: true,
efforts: [
{ value: 'low', label: 'Low' },
{ value: 'high', label: 'High' }
]
}
]
}
}
@@ -48,6 +56,6 @@ afterEach(() => {
vi.unstubAllGlobals()
})
it('keeps + ordinary and actual omnibox original presets independent after editing their models', async () => {
it('keeps + ordinary and actual omnibox original presets independent after editing the ordinary effort', async () => {
await verifyOmniboxPresetIdentity(root, launch)
})
@@ -107,11 +107,9 @@ it('keeps the terminal launch shown but waiting while the host connects, with ch
{ name: 'Launch chat', disabled: false },
{ name: 'Launch terminal', disabled: true }
])
const args = () => document.querySelector<HTMLTextAreaElement>('textarea[aria-label="Arguments"]')
fireEvent.change(args()!, { target: { value: '--search' } })
view.rerender(menu(false))
expect(reason()).toBe('ClaudeNo chat over SSH')
expect(args()?.value).toBe('--search')
expect(document.querySelector('[contenteditable]')?.textContent).toBe('first line')
expect(buttons()).toEqual([
{ name: 'Launch chat', disabled: false },
{ name: 'Launch terminal', disabled: false }
@@ -56,10 +56,25 @@ afterEach(() => {
describe('setup records exist only for authored setups', () => {
it('keeps typed prompt and options through close, reopen and a rename', () => {
const entry = {
const entry: AgentPickerEntry = {
...row(0, 'codex'),
worktreeId: 'repo::/old',
arguments: '--add-dir docs'
arguments: '--add-dir docs',
catalog: {
origin: 'probe',
fetchedAt: 1,
models: [
{
id: 'gpt',
label: 'gpt',
isDefault: true,
efforts: [
{ value: 'low', label: 'Low' },
{ value: 'high', label: 'High' }
]
}
]
}
}
const show = (value: AgentPickerEntry) =>
act(() =>
@@ -73,7 +88,10 @@ describe('setup records exist only for authored setups', () => {
document.querySelector<HTMLButtonElement>('button[aria-label="Configure codex"]')!
const prompt = () =>
document.querySelector<HTMLElement>('[contenteditable][aria-multiline="true"]')!
const close = () => fireEvent.keyDown(document.querySelector('textarea')!, { key: 'Escape' })
const effort = (label: string) =>
getByRole(document.body, 'radio', { name: label }).getAttribute('data-state')
const close = () =>
fireEvent.keyDown(document.querySelector('[cmdk-input]')!, { key: 'Escape' })
show(entry)
fireEvent.click(configure())
close()
@@ -81,15 +99,17 @@ describe('setup records exist only for authored setups', () => {
// The open panel's first write is an option change; it shows at once and retains the setup.
fireEvent.click(configure())
fireEvent.change(document.querySelector('textarea')!, { target: { value: '--add-dir src' } })
expect(document.querySelector<HTMLTextAreaElement>('textarea')?.value).toBe('--add-dir src')
// Arguments are agent settings, so the launch panel offers no editor for them.
expect(document.querySelector('textarea[aria-label="Arguments"]')).toBeNull()
fireEvent.click(getByRole(document.body, 'radio', { name: 'Low' }))
expect(effort('Low')).toBe('on')
expect(sourceCount()).toBe(1)
changePrompt(prompt(), 'fix the flaky test')
close()
expect(document.querySelector('textarea')).toBeNull()
expect(document.querySelector('[cmdk-input]')).toBeNull()
fireEvent.click(configure())
expect(prompt().textContent).toBe('fix the flaky test')
expect(document.querySelector<HTMLTextAreaElement>('textarea')?.value).toBe('--add-dir src')
expect(effort('Low')).toBe('on')
close()
act(() =>
@@ -102,7 +122,7 @@ describe('setup records exist only for authored setups', () => {
show({ ...entry, worktreeId: 'repo::/new' })
fireEvent.click(configure())
expect(prompt().textContent).toBe('fix the flaky test')
expect(document.querySelector<HTMLTextAreaElement>('textarea')?.value).toBe('--add-dir src')
expect(effort('Low')).toBe('on')
})
it('keeps a search preset in its own options slot, saving only real changes, with one shared prompt', async () => {
@@ -146,7 +166,8 @@ describe('setup records exist only for authored setups', () => {
const effort = (label: string) =>
getByRole(document.body, 'radio', { name: label }).getAttribute('data-state')
const model = () => document.querySelector('[cmdk-item][data-current="true"]')?.textContent
const close = () => fireEvent.keyDown(document.querySelector('textarea')!, { key: 'Escape' })
const close = () =>
fireEvent.keyDown(document.querySelector('[cmdk-input]')!, { key: 'Escape' })
// Opening a preset saves nothing, and the plain row still shows defaults.
fireEvent.click(searched)
@@ -1,7 +1,8 @@
import { terminalPendingLabel } from '../tab-bar/new-tab-agent-menu-model'
import { useImperativeHandle, useRef, type Ref } from 'react'
import { CornerDownLeft } from 'lucide-react'
import { CornerDownLeft, MessageSquare, SquareTerminal } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { cn } from '@/lib/utils'
import { ShortcutKeyCombo } from '@/components/ShortcutKeyCombo'
import { translate } from '@/i18n/i18n'
import { useTranslate, type Translate } from '@/i18n/use-translate'
@@ -49,18 +50,23 @@ export function AgentLaunchSurfaceButtons({
disabled,
terminalBlocked,
primary,
prominent,
onSelect
}: {
entry: AgentPickerEntry
disabled?: boolean
terminalBlocked?: boolean
primary: AgentPickerSurface
/** Launch is the popover's main action, so its buttons split the full row. */
prominent?: boolean
onSelect: (surface: AgentPickerSurface) => void
}): React.JSX.Element {
const t = useTranslate()
const platform = getShortcutPlatform()
const surfaceIcon = (surface: AgentPickerSurface) =>
surface === 'chat' ? <MessageSquare /> : <SquareTerminal />
return (
<div className="grid max-w-full auto-cols-fr grid-flow-col gap-2">
<div className={cn('grid max-w-full auto-cols-fr grid-flow-col gap-2', prominent && 'w-full')}>
{agentPickerButtonOrder(entry).map((next) => {
const isPrimary = next === primary
const label =
@@ -71,7 +77,7 @@ export function AgentLaunchSurfaceButtons({
<Button
key={next}
type="button"
size="xs"
size={prominent ? 'sm' : 'xs'}
className="min-w-0 w-full"
aria-label={label}
aria-keyshortcuts={
@@ -85,7 +91,8 @@ export function AgentLaunchSurfaceButtons({
variant={isPrimary ? 'default' : 'outline'}
onClick={() => onSelect(next)}
>
<span className="truncate">{label}</span>
{prominent ? surfaceIcon(next) : null}
<span className={cn('truncate', prominent && 'mr-auto')}>{label}</span>
<span aria-hidden="true">
<ShortcutKeyCombo
keys={formatKeybinding(isPrimary ? 'Enter' : 'Mod+Enter', platform)}
@@ -149,11 +156,12 @@ export function AgentLaunchSetupFooter({
}
/>
</div>
<div className="flex items-center justify-end gap-2 border-t border-border px-2.5 py-2">
<div className="flex items-center px-3 pb-3">
<AgentLaunchSurfaceButtons
entry={entry}
disabled={disabled}
primary={entry.defaultSurface}
prominent
terminalBlocked={terminalBlocked}
onSelect={launch}
/>
@@ -41,7 +41,9 @@ it.each(['claude', 'codex'] as const)(
id: 'selected',
label: 'Selected',
efforts: [{ value: 'high', label: 'High' }],
isDefault: true
isDefault: true,
supportsFastMode: true,
serviceTiers: [{ value: 'fast', label: 'Fast' }]
}
]
}
@@ -68,9 +70,12 @@ it.each(['claude', 'codex'] as const)(
)!
fireEvent.click(panel().querySelector('[cmdk-item]')!)
fireEvent.click(getByRole(document.body, 'radio', { name: 'High' }))
fireEvent.change(panel().querySelector('textarea')!, {
target: { value: '--add-dir "authored exact path"' }
})
// A speed edit copies the current Arguments into the draft; later settings must still win.
fireEvent.click(
agent === 'claude'
? getByRole(document.body, 'button', { name: 'Fast' })
: getByRole(document.body, 'radio', { name: 'Fast' })
)
changePrompt(
panel().querySelector<HTMLElement>('[contenteditable]')!,
'keep @/repo/exact-file.ts'
@@ -100,41 +105,38 @@ it.each(['claude', 'codex'] as const)(
)
)
fireEvent.click(configure())
expect(panel().querySelector<HTMLTextAreaElement>('textarea')?.value).toBe(
'--add-dir "authored exact path"'
)
expect(panel().querySelector('textarea[aria-label="Arguments"]')).toBeNull()
expect(panel().querySelector('[contenteditable]')?.textContent).toContain('exact-file.ts')
launch.mockReturnValueOnce(false)
fireEvent.keyDown(panel().querySelector('textarea')!, { key: 'Enter' })
fireEvent.click(getByRole(document.body, 'button', { name: 'Launch terminal' }))
await waitFor(() => expect(launch).toHaveBeenCalledOnce())
const inputs = agentPickerLaunchInputs(launch.mock.lastCall![1], refreshed)
expect(inputs).toMatchObject({
agentArgs: '--add-dir "authored exact path"',
sessionOptions: { model: 'selected', effort: 'high' }
})
expect(inputs).toMatchObject({ sessionOptions: { model: 'selected', effort: 'high' } })
expect(compilePickerTerminalLaunch(launch.mock.lastCall![1], refreshed)).toMatchObject({
ok: true,
tokens:
agent === 'claude'
? [
'claude',
'--add-dir',
'authored exact path',
'--dangerously-skip-permissions',
'--model',
'selected',
'--effort',
'high'
'high',
'--settings',
'{"fastMode":true}'
]
: [
'codex',
'-c',
'disable_paste_burst=true',
'--add-dir',
'authored exact path',
'--dangerously-bypass-approvals-and-sandbox',
'-m',
'selected',
'-c',
'model_reasoning_effort=high'
'model_reasoning_effort=high',
'-c',
'service_tier=fast'
]
})
await waitFor(() =>
@@ -144,10 +146,7 @@ it.each(['claude', 'codex'] as const)(
)
fireEvent.click(getByRole(document.body, 'button', { name: 'Launch terminal' }))
await waitFor(() => expect(launch).toHaveBeenCalledTimes(2))
expect(launch.mock.lastCall![1]).toMatchObject({
prompt: 'keep @/repo/exact-file.ts',
argumentsOwner: refreshed.ownerKey
})
expect(launch.mock.lastCall![1]).toMatchObject({ prompt: 'keep @/repo/exact-file.ts' })
await waitFor(() =>
expect(readNativeChatComposerDraft(agentLaunchPromptScopeKey(refreshed)).text).toBe('')
)
@@ -3,7 +3,8 @@ import { textActionArgumentsOwnModel } from '../../../../shared/agent-launch/pic
import type { AgentLaunchSetupPanelProps } from './AgentLaunchSetupButton'
import {
agentLaunchSetupOptionRows,
agentLaunchSetupCatalogState as catalogState
agentLaunchSetupRowGroups,
agentLaunchSetupCatalogState as catalogOf
} from './agent-launch-setup-option-rows'
import { useRef, useState } from 'react'
import { Check, ChevronDown, ChevronLeft, ChevronRight } from 'lucide-react'
@@ -83,7 +84,7 @@ export function AgentLaunchSetupPanel({
? models.find((model) => model.isDefault)?.id
: undefined)
// Settled with nothing chosen still lets the user pick; nothing is checked and the agent keeps its own default.
const { hasModels, catalogPending, catalogMessage } = catalogState(entry, models, currentModel)
const { hasModels, catalogPending, catalogMessage } = catalogOf(entry, models, currentModel, mode)
const modelInArguments = textActionArgumentsOwnModel(entry, draft)
const modelDisabled = disabled || defaultsPending || catalogPending
const options = agentPickerEntryOptions(entry, currentModel, draft.surface)
@@ -98,14 +99,14 @@ export function AgentLaunchSetupPanel({
disabled,
argumentsKnown
})
const renderOptions = (rows: SessionOptionDescriptor[]) =>
const renderOptions = (rows: SessionOptionDescriptor[], layout?: 'inline') =>
rows.length ? (
<NativeChatSessionOptionRows
options={rows}
shownReason={null}
pendingIds={new Set()}
divided={false}
layout="settings"
layout={layout ?? 'settings'}
onSet={(option, value) => {
if (entry.textGeneration && value === 'arguments') {
return
@@ -144,6 +145,9 @@ export function AgentLaunchSetupPanel({
},
argumentsView.context
)
const rowGroups = agentLaunchSetupRowGroups(optionRows, mode === 'launch')
// Launch leaves Permissions and Arguments to Agent settings; select mode edits a saved setup.
const editsDefaults = mode === 'select' && !entry.textGeneration
const note = agentLaunchSetupArgumentsNote(entry.label, options, !seed?.modelApply.cli?.length)
const legacyCount = models.filter((model) => model.isLegacy && !model.hidden).length
return (
@@ -288,10 +292,9 @@ export function AgentLaunchSetupPanel({
</CommandList>
</Command>
<div className="grid min-w-0 grid-cols-1 gap-2.5 border-t border-border px-3 pt-2.5 pb-1">
{renderOptions(
optionRows.filter((row) => row.id !== 'fastMode' && row.id !== 'serviceTier')
)}
{!entry.textGeneration ? (
{mode === 'launch' && renderOptions([...rowGroups.level, ...rowGroups.speed], 'inline')}
{renderOptions(rowGroups.rest)}
{editsDefaults ? (
<div>
<AgentPermissionMenu
agentId={entry.agent}
@@ -321,10 +324,8 @@ export function AgentLaunchSetupPanel({
/>
</div>
) : null}
{renderOptions(
optionRows.filter((row) => row.id === 'fastMode' || row.id === 'serviceTier')
)}
{!entry.textGeneration && entry.surfaces.includes('terminal') ? (
{mode === 'select' ? renderOptions(rowGroups.speed) : null}
{editsDefaults && entry.surfaces.includes('terminal') ? (
<div>
<Collapsible open={argumentsOpen} onOpenChange={setArgumentsOpen}>
<CollapsibleTrigger asChild>
@@ -18,7 +18,8 @@ import {
import {
type AgentPickerRetainedSetup as RetainedSetup,
currentAgentPickerDraft
currentAgentPickerDraft,
launchAgentPickerDraft
} from '../../../../shared/agent-launch/picker/agent-launch-picker-authority'
import { retireAgentPickerSetups } from './agent-launch-picker-retirement'
import {
@@ -228,7 +229,10 @@ export function useAgentPickerSetup({
}, [open, read, acquire, retained, mode])
let record = retained && matches(retained) ? retained : undefined
const currentDraft = (draft: AgentPickerDraft) => {
const current = currentAgentPickerDraft(entry, draft)
const current =
mode === 'launch'
? launchAgentPickerDraft(entry, draft)
: currentAgentPickerDraft(entry, draft)
return currentSurface && currentSurface !== current.surface
? { ...current, surface: currentSurface }
: current
@@ -80,34 +80,33 @@ export async function verifyOmniboxPresetIdentity(
)
)
fireEvent.click(document.querySelector('button[aria-label="Configure Codex"]')!)
await waitFor(() => expect(document.querySelector('textarea')).not.toBeNull())
await waitFor(() => expect(effort('Low')).not.toBeNull())
}
const close = () => fireEvent.keyDown(document.querySelector('textarea')!, { key: 'Escape' })
const panel = () =>
document.querySelector<HTMLElement>(
'[data-slot="dropdown-menu-sub-content"], [data-slot="popover-content"]'
)!
const close = () => fireEvent.keyDown(panel(), { key: 'Escape' })
const effort = (label: string) =>
getByRole(document.body, 'radio', { name: label }).getAttribute('data-state')
try {
await showEntries(false)
await configure()
fireEvent.change(document.querySelector('textarea')!, {
target: { value: '--ordinary-pending' }
})
fireEvent.click(getByRole(document.body, 'radio', { name: 'High' }))
close()
await showEntries(true)
fireEvent.change(document.querySelector('input')!, { target: { value: 'Astra low' } })
await configure()
expect(document.querySelector('textarea')).not.toHaveProperty('value', '--ordinary-pending')
fireEvent.change(document.querySelector('textarea')!, {
target: { value: '--model private/preset --preset-pending' }
})
fireEvent.click(getByRole(document.body, 'radio', { name: 'Low' }))
expect(effort('Low')).toBe('on')
close()
await showEntries(false)
await configure()
expect(document.querySelector('textarea')).toHaveProperty('value', '--ordinary-pending')
expect(effort('High')).toBe('on')
close()
await showEntries(true)
fireEvent.change(document.querySelector('input')!, { target: { value: 'Astra low' } })
await configure()
expect(document.querySelector('textarea')).toHaveProperty('value', '--preset-pending')
expect(document.body.textContent).toContain('private/preset')
expect(effort('Low')).toBe('on')
expect(launch).not.toHaveBeenCalled()
} finally {
act(() => {
@@ -163,10 +163,24 @@ export function agentLaunchSetupOptionRows(
})
}
/** Speed rows sit apart from the rest; launch also lifts level rows onto speed's line. */
export function agentLaunchSetupRowGroups(rows: SessionOptionDescriptor[], launch: boolean) {
const isSpeed = (row: SessionOptionDescriptor) =>
row.id === 'fastMode' || row.id === 'serviceTier'
const isLevel = (row: SessionOptionDescriptor) =>
launch && !isSpeed(row) && row.category === 'thought_level' && row.kind.type === 'select'
return {
level: rows.filter(isLevel),
speed: rows.filter(isSpeed),
rest: rows.filter((row) => !isSpeed(row) && !isLevel(row))
}
}
export function agentLaunchSetupCatalogState(
entry: AgentPickerEntry,
models: readonly { id: string; hidden?: true }[],
currentModel: string | undefined,
mode: 'launch' | 'select',
t: Translate = translate
) {
const hasModels = models.some((model) => !model.hidden)
@@ -178,11 +192,17 @@ export function agentLaunchSetupCatalogState(
? null
: entry.catalogRead === 'unsupported' ||
(!hasModels && entry.catalog.origin === 'unknown' && entry.catalogRead === undefined)
? t(
'agentPicker.catalogUnsupported',
'{{agent}} models are unavailable on this host. You can keep your current choices or use Arguments.',
{ agent: entry.label }
)
? mode === 'launch'
? t(
'agentPicker.catalogUnsupportedLaunch',
'{{agent}} models are unavailable on this host. Your current choices still apply.',
{ agent: entry.label }
)
: t(
'agentPicker.catalogUnsupported',
'{{agent}} models are unavailable on this host. You can keep your current choices or use Arguments.',
{ agent: entry.label }
)
: entry.catalogRead === 'failed'
? t(
'agentPicker.catalogFailed',
@@ -1,7 +1,9 @@
import { useId, useLayoutEffect, useRef } from 'react'
import { Zap } from 'lucide-react'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
import { Switch } from '@/components/ui/switch'
import { Toggle } from '@/components/ui/toggle'
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { sortNativeChatSessionOptions } from '../../../../../../../shared/native-chat/input/native-chat-session-option-snapshot'
@@ -33,8 +35,8 @@ type NativeChatSessionOptionRowsProps = {
onAction: (descriptor: SessionOptionDescriptor) => void
/** False when the caller already separates these rows from what sits above them. */
divided?: boolean
/** Settings grids provide their own inset and spacing between option blocks. */
layout?: 'menu' | 'settings'
/** Settings grids provide their own inset and spacing; inline puts levels, unlabeled, left of the rest on one line. */
layout?: 'menu' | 'settings' | 'inline'
}
// Retain missing rows disabled for this visit so model changes cannot slide the picker under the pointer.
@@ -83,7 +85,7 @@ function LevelSegment(props: {
function OptionRow(props: {
descriptor: SessionOptionDescriptor
layout: 'menu' | 'settings'
layout: 'menu' | 'settings' | 'inline'
/** Kept from an earlier model in this visit; the current one does not offer it. */
gone: boolean
onSet: (value: SessionOptionValue) => void
@@ -122,6 +124,20 @@ function OptionRow(props: {
</div>
)
}
if (descriptor.kind.type === 'boolean' && layout === 'inline' && descriptor.id === 'fastMode') {
return (
<Toggle
variant="chip"
size="xs"
pressed={!gone && descriptor.kind.currentValue}
disabled={off}
onPressedChange={onSet}
>
<Zap className="size-3" />
{say('fast')}
</Toggle>
)
}
if (descriptor.kind.type === 'boolean') {
return (
<label
@@ -166,7 +182,13 @@ function OptionRow(props: {
layout === 'menu' && 'pl-2'
)}
>
<span id={labelId} className="text-xs text-muted-foreground">
<span
id={labelId}
className={cn(
'text-xs text-muted-foreground',
layout === 'inline' && descriptor.category === 'thought_level' && 'sr-only'
)}
>
{label}
</span>
<ToggleGroup
@@ -186,7 +208,7 @@ function OptionRow(props: {
}
}}
// Why -ml-2: a wrapped group lines its first label up under the row's.
className="-ml-2 flex-wrap"
className={cn('flex-wrap', layout !== 'inline' && '-ml-2')}
>
{descriptor.kind.choices.map((choice) => (
<LevelSegment key={choice.value} optionId={descriptor.id} choice={choice} />
@@ -233,19 +255,28 @@ export function NativeChatSessionOptionRows({
ref={frame}
className={cn(
'h-(--option-rows-height) shrink-0 overflow-hidden transition-[height] duration-150 ease-out motion-reduce:transition-none',
layout === 'settings' && rows.length === 0 && 'hidden'
layout !== 'menu' && rows.length === 0 && 'hidden'
)}
>
<div ref={content}>
{rows.length > 0 ? (
<div className={cn(divided && 'border-t border-border', layout === 'menu' && 'p-1')}>
<div className={cn('flex flex-col', layout === 'menu' ? 'gap-0.5' : 'gap-2.5')}>
<div
className={cn(
layout === 'inline' ? 'flex flex-wrap items-center gap-3' : 'flex flex-col',
layout === 'menu' && 'gap-0.5',
layout === 'settings' && 'gap-2.5'
)}
>
{rows.map((descriptor) => {
const reason = nativeChatSessionOptionDisabledReason(descriptor.disabledReason, say)
return (
<NativeChatSessionOptionWriteGroup
key={descriptor.id}
busy={pendingIds.has(descriptor.id)}
className={cn(
layout === 'inline' && descriptor.category !== 'thought_level' && 'ml-auto'
)}
>
<div data-option-id={descriptor.id}>
<OptionRow
+3 -1
View File
@@ -14,7 +14,9 @@ const toggleVariants = cva(
'border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground',
// Why not bg-accent: on a light popover it is nearly the surface colour (STYLEGUIDE, list rows).
segmented:
'bg-transparent text-muted-foreground transition-[color,background-color,box-shadow] hover:bg-foreground/6 hover:text-foreground data-[state=on]:bg-foreground/12 data-[state=on]:text-foreground dark:hover:bg-accent/50 dark:data-[state=on]:bg-accent'
'bg-transparent text-muted-foreground transition-[color,background-color,box-shadow] hover:bg-foreground/6 hover:text-foreground data-[state=on]:bg-foreground/12 data-[state=on]:text-foreground dark:hover:bg-accent/50 dark:data-[state=on]:bg-accent',
// An outlined segmented toggle that stands alone; pressed trades its border for the fill.
chip: 'border border-input bg-transparent text-muted-foreground transition-[color,background-color,box-shadow] hover:bg-foreground/6 hover:text-foreground data-[state=on]:border-transparent data-[state=on]:bg-foreground/12 data-[state=on]:text-foreground dark:hover:bg-accent/50 dark:data-[state=on]:bg-accent'
},
size: {
default: 'h-9 min-w-9 px-2',
+1
View File
@@ -23427,6 +23427,7 @@
},
"agentPicker": {
"catalogUnsupported": "Modelle für {{agent}} sind auf diesem Host nicht verfügbar. Du kannst deine Auswahl beibehalten oder Argumente verwenden.",
"catalogUnsupportedLaunch": "Modelle für {{agent}} sind auf diesem Host nicht verfügbar. Deine aktuelle Auswahl gilt weiterhin.",
"catalogFailed": "Modelle für {{agent}} konnten nicht geladen werden. Schließe die Agentenauswahl und öffne sie erneut.",
"catalogSignIn": "Melde dich bei {{agent}} an, um die Modelle zu laden, und öffne die Agentenauswahl erneut.",
"catalogInstall": "Installiere {{agent}} auf diesem Host, um die Modelle zu laden, und öffne die Agentenauswahl erneut.",
+1
View File
@@ -23839,6 +23839,7 @@
},
"agentPicker": {
"catalogUnsupported": "{{agent}} models are unavailable on this host. You can keep your current choices or use Arguments.",
"catalogUnsupportedLaunch": "{{agent}} models are unavailable on this host. Your current choices still apply.",
"catalogFailed": "Could not load {{agent}} models. Close and reopen the agent picker to try again.",
"catalogSignIn": "Sign in to {{agent}} to load its models, then close and reopen the agent picker.",
"catalogInstall": "Install {{agent}} on this host to load its models, then close and reopen the agent picker.",
+1
View File
@@ -23289,6 +23289,7 @@
},
"agentPicker": {
"catalogUnsupported": "Los modelos de {{agent}} no están disponibles en este equipo. Puedes conservar tus opciones o usar Argumentos.",
"catalogUnsupportedLaunch": "Los modelos de {{agent}} no están disponibles en este equipo. Tus opciones actuales se siguen aplicando.",
"catalogFailed": "No se pudieron cargar los modelos de {{agent}}. Cierra y vuelve a abrir el selector de agentes para intentarlo de nuevo.",
"catalogSignIn": "Inicia sesión en {{agent}} para cargar sus modelos y vuelve a abrir el selector de agentes.",
"catalogInstall": "Instala {{agent}} en este equipo para cargar sus modelos y vuelve a abrir el selector de agentes.",
+1
View File
@@ -23289,6 +23289,7 @@
},
"agentPicker": {
"catalogUnsupported": "Les modèles de {{agent}} sont indisponibles sur cet hôte. Vous pouvez conserver vos choix ou utiliser les arguments.",
"catalogUnsupportedLaunch": "Les modèles de {{agent}} sont indisponibles sur cet hôte. Vos choix actuels s'appliquent toujours.",
"catalogFailed": "Impossible de charger les modèles de {{agent}}. Fermez puis rouvrez le sélecteur d’agents pour réessayer.",
"catalogSignIn": "Connectez-vous à {{agent}} pour charger ses modèles, puis rouvrez le sélecteur d’agents.",
"catalogInstall": "Installez {{agent}} sur cet hôte pour charger ses modèles, puis rouvrez le sélecteur d’agents.",
+1
View File
@@ -22598,6 +22598,7 @@
},
"agentPicker": {
"catalogUnsupported": "このホストでは{{agent}}のモデルを利用できません。現在の選択を保持するか、引数を使用できます。",
"catalogUnsupportedLaunch": "このホストでは{{agent}}のモデルを利用できません。現在の選択はそのまま適用されます。",
"catalogFailed": "{{agent}}のモデルを読み込めませんでした。Agent 選択を閉じて開き直してください。",
"catalogSignIn": "{{agent}}にサインインしてモデルを読み込み、Agent 選択を開き直してください。",
"catalogInstall": "このホストに{{agent}}をインストールしてモデルを読み込み、Agent 選択を開き直してください。",
+1
View File
@@ -22598,6 +22598,7 @@
},
"agentPicker": {
"catalogUnsupported": "이 호스트에서는 {{agent}} 모델을 사용할 수 없습니다. 현재 선택을 유지하거나 인수를 사용할 수 있습니다.",
"catalogUnsupportedLaunch": "이 호스트에서는 {{agent}} 모델을 사용할 수 없습니다. 현재 선택은 그대로 적용됩니다.",
"catalogFailed": "{{agent}} 모델을 불러오지 못했습니다. 에이전트 선택기를 닫았다가 다시 열어 보세요.",
"catalogSignIn": "{{agent}}에 로그인하여 모델을 불러온 후 에이전트 선택기를 다시 여세요.",
"catalogInstall": "이 호스트에 {{agent}}를 설치하여 모델을 불러온 후 에이전트 선택기를 다시 여세요.",
+1
View File
@@ -23262,6 +23262,7 @@
},
"agentPicker": {
"catalogUnsupported": "Os modelos de {{agent}} não estão disponíveis neste host. Você pode manter suas escolhas ou usar argumentos.",
"catalogUnsupportedLaunch": "Os modelos de {{agent}} não estão disponíveis neste host. Suas escolhas atuais continuam valendo.",
"catalogFailed": "Não foi possível carregar os modelos de {{agent}}. Feche e reabra o seletor de agentes para tentar novamente.",
"catalogSignIn": "Entre em {{agent}} para carregar seus modelos e reabra o seletor de agentes.",
"catalogInstall": "Instale {{agent}} neste host para carregar seus modelos e reabra o seletor de agentes.",
+1
View File
@@ -24183,6 +24183,7 @@
},
"agentPicker": {
"catalogUnsupported": "Модели {{agent}} недоступны на этом сервере. Можно сохранить текущий выбор или использовать аргументы.",
"catalogUnsupportedLaunch": "Модели {{agent}} недоступны на этом сервере. Текущий выбор по-прежнему применяется.",
"catalogFailed": "Не удалось загрузить модели {{agent}}. Закройте и снова откройте выбор агента.",
"catalogSignIn": "Войдите в {{agent}}, чтобы загрузить модели, затем снова откройте выбор агента.",
"catalogInstall": "Установите {{agent}} на этом сервере, чтобы загрузить модели, затем снова откройте выбор агента.",
+1
View File
@@ -23408,6 +23408,7 @@
},
"agentPicker": {
"catalogUnsupported": "{{agent}} modelleri bu sunucuda kullanılamıyor. Geçerli seçimlerinizi koruyabilir veya bağımsız değişkenleri kullanabilirsiniz.",
"catalogUnsupportedLaunch": "{{agent}} modelleri bu sunucuda kullanılamıyor. Geçerli seçimleriniz uygulanmaya devam eder.",
"catalogFailed": "{{agent}} modelleri yüklenemedi. Yeniden denemek için ajan seçiciyi kapatıp açın.",
"catalogSignIn": "Modelleri yüklemek için {{agent}} oturumu açın, ardından ajan seçiciyi yeniden açın.",
"catalogInstall": "Modelleri yüklemek için bu sunucuya {{agent}} yükleyin, ardından ajan seçiciyi yeniden açın.",
@@ -23093,6 +23093,7 @@
},
"agentPicker": {
"catalogUnsupported": "此主機上無法使用 {{agent}} 模型。你可以保留目前的選擇或使用引數。",
"catalogUnsupportedLaunch": "此主機上無法使用 {{agent}} 模型。目前的選擇仍會套用。",
"catalogFailed": "無法載入 {{agent}} 模型。請關閉並重新開啟代理選擇器以重試。",
"catalogSignIn": "請登入 {{agent}} 以載入模型,然後重新開啟代理選擇器。",
"catalogInstall": "請在此主機上安裝 {{agent}} 以載入模型,然後重新開啟代理選擇器。",
+1
View File
@@ -22598,6 +22598,7 @@
},
"agentPicker": {
"catalogUnsupported": "此主机上无法使用 {{agent}} 模型。你可以保留当前选择或使用参数。",
"catalogUnsupportedLaunch": "此主机上无法使用 {{agent}} 模型。当前选择仍会生效。",
"catalogFailed": "无法加载 {{agent}} 模型。请关闭并重新打开智能体选择器以重试。",
"catalogSignIn": "请登录 {{agent}} 以加载模型,然后重新打开智能体选择器。",
"catalogInstall": "请在此主机上安装 {{agent}} 以加载模型,然后重新打开智能体选择器。",
@@ -103,8 +103,10 @@ it('keeps pristine, edited and reopened historical-host picker launches free of
? createElement(
TooltipProvider,
null,
// Select mode still edits Arguments and Permissions; the launch menu leaves them to settings.
createElement(AgentLaunchSetupPanel, {
entry,
mode: 'select',
draft: setup.draft,
getDraft: setup.getDraft,
onDraftChange: setup.change,
@@ -42,3 +42,20 @@ export type AgentPickerRetainedSetup = {
draft: AgentPickerDraft
active: boolean
}
/** Launch has no Arguments editor, so Arguments a speed edit copied from older settings yield to the current ones. */
export function launchAgentPickerDraft(
entry: AgentPickerEntry,
draft: AgentPickerDraft
): AgentPickerDraft {
if (draft.argumentsOwner === undefined || draft.argumentsOwner === entry.ownerKey) {
return draft
}
const {
arguments: _arguments,
pendingArguments: _pending,
argumentsOwner: _owner,
...rest
} = draft
return rest
}