Skip to content

feat(ui): full ACP agent configuration in Reboot settings page #38

@jmagar

Description

@jmagar

Summary

The Reboot settings page currently exposes only model selection, permission mode, and an auto-approve toggle. The ACP layer supports far more configuration — thinking budgets, extended thinking, tool allowlists, system prompt overrides, beta flags, per-agent options — none of which is surfaced in the UI. Make the settings page the comprehensive control panel for all ACP agent config.

Current State

What's configurable today (apps/web/app/settings/):

  • Model (via AcpConfigOption from backend, falls back to hardcoded CLAUDE_MODEL_OPTIONS)
  • Permission mode: plan | accept-edits | bypass-permissions
  • Auto-approve permissions toggle (localStorage only, not synced to Rust)

What's hardcoded / not surfaced:

  • Context budget: hardcoded 800k characters
  • Chat timeout: hardcoded 300 seconds
  • Thinking budget (for extended thinking)
  • Extended thinking enabled/disabled flag
  • Tool allowlist / blocklist
  • System prompt overrides per agent
  • Beta features (--betas / AXON_ALLOWED_CLAUDE_BETAS)
  • Per-agent flags (Codex --model, Gemini --model, Claude --thinking-budget)
  • MCP server configuration (section exists but incomplete)
  • Temperature, max_tokens (for OpenAI-compatible endpoints — see issue feat(llm): support OpenAI-compatible endpoints alongside ACP agents #37)

Data flow (well-architected, just not fully used):

Rust ACP Runtime → AcpBridgeEvent::ConfigOptionsUpdate { config_options: Vec<AcpConfigOption> }
  → WebSocket → useWsMessages → setAcpConfigOptions
  → WsMessagesContext.acpConfigOptions
  → SettingsPage → getAcpModelConfigOption() → renders one dropdown

The pipeline supports dynamic config options from the backend — it's just not being used for anything beyond model selection.

What Needs To Be Built

1. Per-agent settings panels

The settings page should switch context based on the active agent (pulseAgent: 'claude' | 'codex' | 'gemini' + OpenAI endpoints from #37):

Claude panel:

  • Model (already works — extend to show all claude-* models)
  • Permission mode (already works)
  • Extended thinking: enabled/disabled toggle
  • Thinking budget: slider or number input (tokens)
  • Allowed betas: multi-select from AXON_ALLOWED_CLAUDE_BETAS + free-entry
  • System prompt override: textarea (prepended to Claude's context)
  • Max turns per session: number input
  • Tool allowlist: multi-select of available tools
  • Auto-approve permissions: toggle (already exists)

Codex panel:

  • Model (dynamic from ~/.codex/models_cache.json via read_codex_cached_model_options)
  • Permission mode
  • System prompt override
  • Working directory override

Gemini panel:

  • Model (dynamic from read_gemini_cached_model_options)
  • System prompt override
  • Tool allowlist

OpenAI-compatible panel (once #37 lands):

  • Endpoint selector (from [[llm.endpoints]] config)
  • Model (fetched from endpoint's GET /models)
  • Temperature slider
  • Max tokens input
  • System prompt override

2. Dynamic AcpConfigOption rendering

Instead of getAcpModelConfigOption() extracting only the model option, render all AcpConfigOption entries the backend sends — each one becomes a settings control:

// current: only model extracted
const modelOption = getAcpModelConfigOption(acpConfigOptions)

// target: render all dynamic options
acpConfigOptions.map(opt => <AcpConfigControl key={opt.id} option={opt} />)

AcpConfigControl renders the appropriate input based on opt.category:

  • "model" → dropdown
  • "mode" → radio group
  • "thought_level" → slider or segmented control
  • "boolean" → toggle
  • "text" → input

3. Persist settings to backend (not just localStorage)

Currently autoApprovePermissions is localStorage-only — not synced to Rust. Settings that affect ACP behavior need to reach the backend:

  • New WS message type: settings_update { key, value } → Rust persists in DB or passes to next ACP session
  • Or: include all settings in the PulseChatRequest flags object (already has session_id, agent, model, permissionLevel)
  • Settings that only affect UI (theme, layout prefs) → stay in localStorage

4. AcpPromptTurnRequest extensions

Current:

pub struct AcpPromptTurnRequest {
    pub session_id: Option<String>,
    pub prompt: Vec<String>,
    pub model: Option<String>,
    pub mcp_servers: Vec<AcpMcpServerConfig>,
}

Add:

pub struct AcpPromptTurnRequest {
    pub session_id: Option<String>,
    pub prompt: Vec<String>,
    pub model: Option<String>,
    pub mcp_servers: Vec<AcpMcpServerConfig>,
    pub thinking_budget: Option<u32>,          // NEW
    pub extended_thinking: Option<bool>,       // NEW
    pub system_prompt_override: Option<String>, // NEW
    pub allowed_betas: Vec<String>,            // NEW
    pub tool_allowlist: Option<Vec<String>>,   // NEW
    pub max_turns: Option<u32>,                // NEW
}

Wire each new field through apply_config_and_model() in crates/services/acp/session.rs.

5. Settings persistence layer

New table axon_agent_settings:

CREATE TABLE axon_agent_settings (
    agent TEXT NOT NULL,            -- 'claude' | 'codex' | 'gemini' | endpoint name
    key TEXT NOT NULL,
    value JSONB NOT NULL,
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    PRIMARY KEY (agent, key)
);

Expose via:

  • GET /api/settings/acp/:agent → return all settings for agent
  • PUT /api/settings/acp/:agent → upsert settings
  • Load on session init, pass through AcpPromptTurnRequest

6. Settings page UX

  • Agent tabs at top: Claude | Codex | Gemini | (OpenAI endpoints)
  • Each tab renders that agent's panel
  • "Reset to defaults" per-agent (already has global reset — add per-agent)
  • Settings auto-save on change (no Save button)
  • Show which settings require a new session to take effect vs apply immediately

Files

File Action
apps/web/app/settings/settings-sections.tsx Render all acpConfigOptions; add per-agent panels
apps/web/app/settings/settings-data.ts Add config definitions for new options
apps/web/hooks/use-pulse-settings.ts Extend to include all agent settings; sync to backend
apps/web/lib/pulse/types.ts Extend PulseChatRequest with new fields
crates/services/types/acp.rs Extend AcpPromptTurnRequest with new fields
crates/services/acp/session.rs Wire new fields through apply_config_and_model()
crates/web/execute/sync_mode/pulse_chat.rs Pass extended settings from WS flags to service layer
crates/jobs/common/ (or new file) axon_agent_settings table schema + CRUD
crates/web.rs GET/PUT /api/settings/acp/:agent routes

Acceptance Criteria

  • Settings page has per-agent tabs: Claude, Codex, Gemini
  • All AcpConfigOption entries from backend rendered as controls (not just model)
  • Claude: extended thinking toggle + thinking budget input wired end-to-end
  • Claude: allowed betas multi-select wired end-to-end
  • Claude: system prompt override textarea wired end-to-end
  • Claude: tool allowlist multi-select wired end-to-end
  • Codex/Gemini: model selector from dynamic backend list
  • Settings persisted to axon_agent_settings DB table (not just localStorage)
  • Settings loaded and applied on ACP session init
  • GET/PUT /api/settings/acp/:agent REST endpoints
  • New session picks up latest settings without page reload
  • cargo clippy clean, tsc --noEmit clean

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions