In-app agent chat: your own coding agents over ACP, plus OpenAI-compatible endpoints (#1102)

* Agent chat prototype: drive an ACP agent the user already has from a side panel

A desktop-only chat panel (off by default) that starts a coding agent over the
Agent Client Protocol and gives it SolidCraft's MCP server, bridged to the open
window through the token-protected control channel with the default folder
limits. It finds installed agents (Claude Code and Codex adapters, Gemini CLI,
Copilot, Qwen, OpenCode, goose) with version and login state, streams text,
tool calls and plans, shows the agent's permission requests, and when an agent
isn't signed in shows its own login methods without handling any credential.

Design notes and findings: docs/design/in-app-agent-chat.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: say only what the end-to-end run showed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Agent chat: gate, one undo per message, docking, process groups, discovery polish

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Agent chat: Settings → AI, several agents with a switcher, OpenAI-compatible endpoints, permission modes, toolbar button

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Agent chat: translations, tool cards, Add agent dialog fixes, test agent saves on its own

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Agent chat: third-party licences for the keychain crates; clippy

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Agent chat: badge text from the theme tokens

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Agent chat: Auto-allow model edits is the default permission mode

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bherbruckandClaude Opus 5.5 authored and GitHub committed 2026-10-15 14:22:38 -04:00
1 parent 02fe495d38
commit 2d069ca186
36 files changed
+8078 -303

No files matched your search

+49 -1
View File
@@ -206,7 +206,8 @@ pub fn nav_preset(id: &str) -> &'static NavPreset {
NAV_PRESETS.iter().find(|p| p.id == id).unwrap_or(&NAV_PRESETS[0])
}
/// The dialog's pages, in order.
pub const PAGES: [&str; 7] = ["General", "Navigation", "Keyboard Shortcuts", "Design", "Graphics", "Sketch", "Selection"];
pub const PAGES: [&str; 8] = ["General", "Navigation", "Keyboard Shortcuts", "Design", "Graphics", "Sketch", "Selection", "AI"];
pub const PAGE_AI: usize = 7;
pub const PAGE_SHORTCUTS: usize = 2;
pub const PAGE_DESIGN: usize = 3;
pub const PAGE_GRAPHICS: usize = 4;
@@ -325,6 +326,27 @@ pub struct Prefs {
pub save_mesh_send: bool,
pub save_mesh_slicer: String,
pub custom_slicers: Vec<String>,
/// AI features (docs/agent-chat.md): the master switch. Off: no AI button, shortcut, menu
/// entry or panel, and nothing is looked for or started.
pub ai_features: bool,
/// The AI button at the toolbar row's right end (the shortcut and View menu entry stay).
pub ai_toolbar_button: bool,
/// Agents the user added (the installed ones are found by themselves). API keys are never
/// here: they live in the system keychain.
pub agents: Vec<crate::agent_chat::agents::AgentConfig>,
/// The agent a new conversation starts with (an agent's id; empty: the first one available).
pub agent_default: String,
/// How much the agent may do without asking (`ask`, `edits`, `all`), and per agent.
pub ai_permissions: crate::agent_chat::agents::Mode,
pub agent_permissions: std::collections::BTreeMap<String, crate::agent_chat::agents::Mode>,
/// "Always allow" answers: per agent, the tools it may use without asking.
pub agent_always_allow: std::collections::BTreeMap<String, Vec<String>>,
/// The side the panel docks on (`left` or `right`), its width, and whether it is folded to a strip.
pub agent_chat_side: String,
pub agent_chat_width: f32,
pub agent_chat_minimized: bool,
/// Changes the agent may make for one message.
pub agent_chat_turn_limit: u32,
}
/// What a middle-button double-click in the view does.
@@ -394,6 +416,17 @@ impl Default for Prefs {
save_mesh_send: false,
save_mesh_slicer: String::new(),
custom_slicers: Vec::new(),
ai_features: true,
ai_toolbar_button: true,
agents: Vec::new(),
agent_default: String::new(),
ai_permissions: crate::agent_chat::agents::Mode::Edits,
agent_permissions: Default::default(),
agent_always_allow: Default::default(),
agent_chat_side: "right".into(),
agent_chat_width: 380.0,
agent_chat_minimized: false,
agent_chat_turn_limit: crate::agent_chat::gate::DEFAULT_TURN_LIMIT,
}
}
}
@@ -492,6 +525,17 @@ fn defaults(app: &mut SolveApp, page: usize) {
app.autosave_minutes = 5.0;
set_autosave(app);
}
PAGE_AI => {
// The agents the user added and their "always allow" answers stay.
p.ai_features = d.ai_features;
p.ai_toolbar_button = d.ai_toolbar_button;
p.ai_permissions = d.ai_permissions;
p.agent_permissions = d.agent_permissions;
p.agent_chat_side = d.agent_chat_side;
p.agent_chat_width = d.agent_chat_width;
p.agent_chat_minimized = d.agent_chat_minimized;
p.agent_chat_turn_limit = d.agent_chat_turn_limit;
}
1 => {
p.nav = d.nav;
p.navigator_style = d.navigator_style;
@@ -925,6 +969,10 @@ fn page(app: &mut SolveApp, ui: &mut egui::Ui, t: &Tokens) {
crate::keymap::page(app, ui);
return;
}
if app.prefs_window.section == PAGE_AI {
crate::agent_chat::settings::page(app, ui);
return;
}
egui::Grid::new(("sc_prefs_grid", app.prefs_window.section)).num_columns(2).spacing([16.0, 8.0]).show(ui, |ui| match app.prefs_window.section {
0 => {
ui.label(app.tr("ui.language"));