Add custom TUI agent presets (#2284)#2521
Open
noname-oni wants to merge 6 commits into
Open
Conversation
…#2284) Introduces CustomTuiAgent and TuiAgentId alongside the built-in TuiAgent union. Widens GlobalSettings.defaultTuiAgent and agentCmdOverrides to the new id union, and adds customTuiAgents: CustomTuiAgent[] (default []). The new shared/effective-tui-agent module merges built-in and custom agents into a single EffectiveTuiAgent view so launch and detection callers can iterate one list. Includes firstExecutableToken (default detect command / expected process for customs) and generateCustomTuiAgentId (auto-generated, never user-editable). Persistence load-time normalization drops invalid custom entries, locks promptInjectionMode to 'stdin-after-start' (v1), and cascades default / override cleanup if a referenced custom id disappears. Commit-message AI narrows the widened defaultTuiAgent back to built-in only (plan §9). Co-Authored-By: Claude Opus 4.7 <[email protected]>
buildAgentStartupPlan and buildAgentDraftLaunchPlan now accept TuiAgentId and an optional customTuiAgents list, resolving effective config via getEffectiveTuiAgent instead of indexing TUI_AGENT_CONFIG directly. All in-process callers (launch-agent-in-new-tab, launch-agent-background- session, launch-work-item-direct, worktree-activation, useComposerState, onboarding-folder-agent-startup, FloatingTerminalWindowControls) pass the persisted customTuiAgents through. agent-paste-draft.ts reads custom agents from the store so the readiness strategy resolves for both built-in and custom ids. Background session launches narrow preflightTrust to built-ins (plan §9). Telemetry's tuiAgentToAgentKind widens to TuiAgentId and buckets customs into 'other' to keep the closed schema closed. Co-Authored-By: Claude Opus 4.7 <[email protected]>
Replaces the static KNOWN_AGENT_COMMANDS list with getKnownAgentCommands that combines built-ins with the persisted customTuiAgents (falling back to firstExecutableToken(command) when no detect command is set). Detection handlers now read customTuiAgents from the Store at call time, so a freshly-added preset is detectable without an app restart. detectRemoteAgents bundles custom entries into the relay's commands payload; the relay handler is protocol-agnostic so no relay change is needed. Adds a code comment in agent-process-recognition.ts documenting that custom agents are intentionally not process-recognized in v1 (static module-load map; tradeoff: process-keyed status shows "Unknown" only). Co-Authored-By: Claude Opus 4.7 <[email protected]>
AgentCombobox, AgentCatalogEntry, AgentIcon, and the detected-agents store slice now accept TuiAgentId so custom presets can flow through manual launch surfaces (QuickLaunch, New Workspace composer, default- agent picker, AgentsPane). New buildAgentCatalog(customAgents) merges built-ins with the user's custom presets at picker call sites. Surfaces that remain built-in only (commit-message AI, automations, source-control "send to agent", PR-fields generation, onboarding, long-form composer's required agent) narrow `custom:*` ids back to null at their boundary with a code comment explaining why (plan §9). QuickLaunch's orderAgents places custom presets after built-ins, sorted by label. Co-Authored-By: Claude Opus 4.7 <[email protected]>
Custom presets often wrap absolute paths, shell aliases, or company CLIs that don't show up in PATH detection. Previously the NewWorkspace card, QuickLaunch dropdown, and FloatingTerminal default-agent shortcut all filtered the picker by detected ids — so a configured custom preset would silently disappear from those surfaces. - NewWorkspaceComposerCard and QuickLaunchButton now include any custom agent with a non-empty command, regardless of detection state. - QuickLaunchButton.orderAgents sorts custom presets after built-ins by user-facing label; exported for test coverage. - FloatingTerminalWindowControls and NewWorkspaceComposerModal narrow a custom defaultTuiAgent back to null when its id is no longer present, preventing a stale-default crash after the user deletes the preset. - AgentCombobox threads the merged catalog into AgentIcon so custom rows resolve their inherited icon, and wires aria-controls between the trigger button and the popover list for keyboard navigation. Tests added: AgentCombobox renders a custom selection label; orderAgents preserves built-in order and appends sorted custom ids.
…tablyai#2284) Four rough edges that surfaced once the feature was used end-to-end: - Form placeholders no longer privilege Codex as the example. Name, launch command, and detect command now read "Your agent", "agent --profile work", and "agent" respectively. - Renamed "Detection binary" to "Detect command" and rewrote the helper text to name concrete examples (codex, claude) and clarify its role versus the launch command, which was previously ambiguous. - Custom presets inherit the icon of the wrapped built-in when the launch (or detect) command's first executable token matches a known cmd. codex --profile work renders the Codex SVG; grok --foo renders the Grok favicon; unknown wrappers fall back to initials. Implemented via resolveCustomAgentIconSource() plus an optional iconSourceId on AgentCatalogEntry; AgentIcon routes through iconSourceId before its favicon-or-initials fallback. - Edit expands inside the same card under a border-t divider with a rotating chevron, instead of spawning a sibling editor card below the targeted row. Matches the pattern used by the Installed-agent row's command-override panel. Tests added: catalog merge inherits iconSourceId from the matching built-in; resolveCustomAgentIconSource() prefers detectCmd over the launch command's first token; unfamiliar commands return no source.
Contributor
|
This is a good idea, we'll definitely take a look! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2284.
Summary
Adds custom TUI agent presets as a first-class concept alongside Orca's 27 built-in agents. Users can now create, edit, and delete their own agent presets — wrappers, company CLIs, alternate profiles — without sacrificing a built-in slot to override its command.
Before this PR, Settings → Agents only allowed overriding the launch command of a built-in agent. A user with
codexplus acodex --profile workwrapper had to choose one or the other. After this PR, both coexist as distinct entries in every picker.What's included
Data model
CustomTuiAgenttype with id, label, command, optional detect command, and prompt-injection mode.TuiAgentId = TuiAgent | CustomTuiAgentId— a namespaced union so built-in code stays exhaustively typed while custom presets flow through the same call sites.custom:<slug>-<6char>format, auto-generated from the label on save, never user-editable.GlobalSettingsgainscustomTuiAgents: CustomTuiAgent[];defaultTuiAgentandagentCmdOverrideswiden toTuiAgentId.Persistence + normalization
persistence.tswith belt-and-braces normalization on load: drops entries missing thecustom:prefix, label, or command; de-duplicates by id; coercespromptInjectionModeto the v1 default; resetsdefaultTuiAgenttonullif it references a missing id; dropsagentCmdOverrideskeys for missing custom ids.normalizeTerminalQuickCommands).Effective-agent registry
src/shared/effective-tui-agent.tsmerges built-ins (TUI_AGENT_CONFIG) with custom presets into a singleEffectiveTuiAgentshape. Launch, detection, and pickers iterate one list.firstExecutableToken()derives sensible defaults:detectCmdandexpectedProcessfall back to the first token of the launch command.isCustomTuiAgentId()discriminates the namespace at type-narrowing boundaries.Launch path
buildAgentStartupPlanandbuildAgentDraftLaunchPlanacceptTuiAgentIdpluscustomTuiAgents; resolve the effective config before building commands.agentCmdOverrideshonored for both built-ins and customs.promptInjectionMode: 'stdin-after-start'— the safest default that doesn't require knowing a specific CLI's flag schema. Other modes (argv,flag-prompt, etc.) remain built-in-only.buildAgentDraftLaunchPlanreturnsnullfor customs (no flag/env hints), so callers fall through to paste-after-ready naturally.Detection
src/main/ipc/preflight.tsbuildsKNOWN_AGENT_COMMANDSdynamically per request: built-in detect commands plus each custom agent's detect command (or first-token fallback).detectInstalledAgents,refreshShellPathAndDetectAgents, anddetectRemoteAgentsunchanged.Pickers and UI surfaces
Custom agents appear in:
Custom presets are kept selectable in those surfaces even when they don't pass PATH detection — wrappers often use absolute paths, shell aliases, or company CLIs that aren't on PATH. Surfaces that read a custom
defaultTuiAgentalso defensively narrow it back tonullwhen the id no longer exists, so deleting the current default doesn't leave a broken launch shortcut behind.Intentionally not included for v1 (kept built-in-only):
Settings UI — Custom agents section
border-tdivider with a rotating chevron — consistent with how Installed-agent rows reveal their command-override panel.defaultTuiAgentand removes anyagentCmdOverrideskeyed by the deleted id.codex --profile work→ Codex icon,grok --foo→ Grok favicon). Unknown wrappers fall back to initials.Your agent/agent --profile work/agent) — no built-in is privileged as the example.codexandclaudeas examples and clarifies its role versus the launch command.Tests
src/main/persistence.test.ts— settings round-trip and normalization edge cases.src/renderer/src/lib/tui-agent-startup-custom.test.ts— launch plan resolves custom config, respects overrides, returns null for unknown ids.src/shared/effective-tui-agent.test.ts— id generation, token parsing, effective-agent shapes.src/renderer/src/lib/agent-catalog.test.tsx— catalog merge, icon-source inheritance.src/renderer/src/components/settings/AgentsPane.test.tsx— render, add, update, delete cascade.src/renderer/src/components/agent/AgentCombobox.test.tsx— custom selection label renders.src/renderer/src/components/tab-bar/QuickLaunchButton.test.ts—orderAgentskeeps built-ins first and sorts customs by label.src/main/ipc/preflight.test.ts— custom agents included in detection.pnpm typecheck✅pnpm test✅ (825 test files / 8584 tests pass)Architecture choice and future direction
We picked a separate dynamic-agent model (built-ins typed by
TuiAgent, customs in acustom:id namespace) over three alternatives evaluated during planning:TuiAgent = string— loses exhaustive checks across icon, telemetry, prompt-injection, trust-preflight, and feature-gating code.The natural long-term direction is a fully data-driven agent registry where built-ins and customs share one persisted shape. That's worth doing if/when import/export, per-repo presets, or shareable preset bundles land, since those features need both kinds of agents to round-trip through the same schema. The
EffectiveTuiAgenthelpers added in this PR are the seam to evolve there — renderer and main already iterate one merged list through them.We didn't do it now because:
Out of scope (explicit deferrals)
argv,flag-prompt, etc.).Test plan
codex --profile work. Confirm:Your agent/agent --profile work/agent).codexorclaude.grok --foo— row shows the Grok favicon.mywrapper— row falls back to initials.stdin-after-start).