Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Add custom TUI agent presets (#2284)#2521

Open
noname-oni wants to merge 6 commits into
stablyai:mainfrom
noname-oni:issue-2284-custom-agent-presets
Open

Add custom TUI agent presets (#2284)#2521
noname-oni wants to merge 6 commits into
stablyai:mainfrom
noname-oni:issue-2284-custom-agent-presets

Conversation

@noname-oni
Copy link
Copy Markdown

@noname-oni noname-oni commented May 21, 2026

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 codex plus a codex --profile work wrapper had to choose one or the other. After this PR, both coexist as distinct entries in every picker.

image

What's included

Data model

  • New CustomTuiAgent type 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 ids use a reserved custom:<slug>-<6char> format, auto-generated from the label on save, never user-editable.
  • GlobalSettings gains customTuiAgents: CustomTuiAgent[]; defaultTuiAgent and agentCmdOverrides widen to TuiAgentId.

Persistence + normalization

  • Round-trips through persistence.ts with belt-and-braces normalization on load: drops entries missing the custom: prefix, label, or command; de-duplicates by id; coerces promptInjectionMode to the v1 default; resets defaultTuiAgent to null if it references a missing id; drops agentCmdOverrides keys for missing custom ids.
  • Existing settings migrate cleanly (no version bump needed — same pattern as normalizeTerminalQuickCommands).

Effective-agent registry

  • New src/shared/effective-tui-agent.ts merges built-ins (TUI_AGENT_CONFIG) with custom presets into a single EffectiveTuiAgent shape. Launch, detection, and pickers iterate one list.
  • firstExecutableToken() derives sensible defaults: detectCmd and expectedProcess fall back to the first token of the launch command.
  • isCustomTuiAgentId() discriminates the namespace at type-narrowing boundaries.

Launch path

  • buildAgentStartupPlan and buildAgentDraftLaunchPlan accept TuiAgentId plus customTuiAgents; resolve the effective config before building commands.
  • agentCmdOverrides honored for both built-ins and customs.
  • Custom agents lock to 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.
  • buildAgentDraftLaunchPlan returns null for customs (no flag/env hints), so callers fall through to paste-after-ready naturally.

Detection

  • src/main/ipc/preflight.ts builds KNOWN_AGENT_COMMANDS dynamically per request: built-in detect commands plus each custom agent's detect command (or first-token fallback).
  • Custom detect ids flow through detectInstalledAgents, refreshShellPathAndDetectAgents, and detectRemoteAgents unchanged.
  • SSH path works the same way — the relay payload is protocol-agnostic.

Pickers and UI surfaces

Custom agents appear in:

  • Default agent picker (Settings → Agents)
  • New Workspace composer (card + modal)
  • Quick Launch dropdown
  • Floating Terminal window controls
  • AgentCombobox (manual agent picker)
  • Onboarding agent step

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 defaultTuiAgent also defensively narrow it back to null when 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):

  • Automations editor and list
  • Commit-message AI selection
  • Trust preflight
  • Process recognition (process-keyed UI shows "Unknown" for customs; launch/detect/paste-draft are unaffected)

Settings UI — Custom agents section

  • New "Custom agents" section in Settings → Agents, between Installed and Available-to-install.
  • Add / edit / delete via a small form (name, launch command, optional detect command).
  • Edit expands inline inside the same row under a border-t divider with a rotating chevron — consistent with how Installed-agent rows reveal their command-override panel.
  • Delete cascades: clears defaultTuiAgent and removes any agentCmdOverrides keyed by the deleted id.
  • Custom agents render the icon of the wrapped built-in when the command's first token matches a known CLI (codex --profile work → Codex icon, grok --foo → Grok favicon). Unknown wrappers fall back to initials.
  • Form copy is neutral (Your agent / agent --profile work / agent) — no built-in is privileged as the example.
  • The Detect-command helper text concretely names codex and claude as 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.tsorderAgents keeps 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 a custom: id namespace) over three alternatives evaluated during planning:

  • Single "custom" built-in slot — solves nothing; same single-slot problem the issue is about.
  • TuiAgent = string — loses exhaustive checks across icon, telemetry, prompt-injection, trust-preflight, and feature-gating code.
  • Data-driven registry for everything — the right long-term shape, but too large for the first PR.

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 EffectiveTuiAgent helpers 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:

  • It's a much larger refactor and migration.
  • Several capabilities are legitimately built-in-only (trust preflight, commit-message AI, process recognition) and need explicit capability gates either way.
  • The current shape ships the user-visible feature cleanly while keeping built-in code paths exhaustively typed.

Out of scope (explicit deferrals)

  • Import/export of preset bundles.
  • Per-repo recommended presets.
  • User-set custom icons (favicon URL, custom SVG).
  • Custom prompt-injection modes (argv, flag-prompt, etc.).
  • Custom trust-preflight behavior.
  • Including custom agents in automations.
  • Commit-message AI selection of custom agents.

Test plan

  • Settings → Agents → Custom agents → "Add custom" → create a preset with name "Codex Work" and command codex --profile work. Confirm:
    • Placeholders are neutral (Your agent / agent --profile work / agent).
    • Detect-command helper text mentions codex or claude.
    • Saved row shows the Codex icon (not initials).
  • Add a second preset with command grok --foo — row shows the Grok favicon.
  • Add a third preset with command mywrapper — row falls back to initials.
  • Click pencil on a custom row — editor expands inside the same card (no sibling card below). Click again to collapse.
  • Set a custom preset as the default agent — confirm it appears as a pill in the default picker.
  • Open New Workspace composer / Quick Launch / Floating Terminal — custom presets appear in each picker even when not PATH-detected.
  • Delete the default custom preset — confirm default resets to Auto and command overrides for that id are removed.
  • Restart the app — custom presets and default selection persist.
  • Launch a custom preset from a new workspace with a prompt — TUI starts, prompt types after ready (stdin-after-start).
  • SSH workspace: custom detect command is included in the remote preflight result.
  • Automations editor / Commit-message AI settings → custom presets are intentionally not offered.

noname-oni and others added 6 commits May 21, 2026 02:41
…#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.
@nwparker
Copy link
Copy Markdown
Contributor

This is a good idea, we'll definitely take a look!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Allow users to add custom agent presets, not only override commands for built-in agents

2 participants