fix(agents): enable external copilot command

This commit is contained in:
James Peter
2025-11-13 13:43:42 +10:00
parent 4dbae974d6
commit 26bb270b9c
8 changed files with 163 additions and 10 deletions
@@ -10,7 +10,7 @@ use crate::app_event_sender::AppEventSender;
#[cfg(target_os = "macos")]
use crate::agent_install_helpers::macos_brew_formula_for_command;
use super::bottom_pane_view::BottomPaneView;
use super::bottom_pane_view::{BottomPaneView, ConditionalUpdate};
use super::form_text_field::{FormTextField, InputFilter};
use super::BottomPane;
@@ -118,6 +118,24 @@ impl AgentEditorView {
true
}
fn paste_into_field(field: &mut FormTextField, text: &str) -> bool {
let before = field.text().len();
field.handle_paste(text.to_string());
field.text().len() != before
}
fn paste_into_current_field(&mut self, text: &str) -> bool {
match self.field {
FIELD_NAME => Self::paste_into_field(&mut self.name_field, text),
FIELD_COMMAND => Self::paste_into_field(&mut self.command_field, text),
FIELD_READ_ONLY => Self::paste_into_field(&mut self.params_ro, text),
FIELD_WRITE => Self::paste_into_field(&mut self.params_wr, text),
FIELD_DESCRIPTION => Self::paste_into_field(&mut self.description_field, text),
FIELD_INSTRUCTIONS => Self::paste_into_field(&mut self.instr, text),
_ => false,
}
}
fn handle_key_internal(&mut self, key_event: KeyEvent) -> bool {
let last_field_idx = FIELD_CANCEL;
match key_event {
@@ -555,6 +573,14 @@ impl<'a> BottomPaneView<'a> for AgentEditorView {
let _ = self.handle_key_internal(key_event);
}
fn handle_paste(&mut self, text: String) -> ConditionalUpdate {
if self.paste_into_current_field(&text) {
ConditionalUpdate::NeedsRedraw
} else {
ConditionalUpdate::NoRedraw
}
}
fn is_complete(&self) -> bool { self.complete }
fn desired_height(&self, width: u16) -> u16 {
+1
View File
@@ -8,6 +8,7 @@ use crate::chatwidget::BackgroundOrderTicket;
use crate::user_approval_widget::{ApprovalRequest, UserApprovalWidget};
use crate::thread_spawner;
pub(crate) use bottom_pane_view::BottomPaneView;
pub(crate) use bottom_pane_view::ConditionalUpdate;
use crate::util::buffer::fill_rect;
use code_core::protocol::TokenUsage;
use code_file_search::FileMatch;
+50 -1
View File
@@ -55,6 +55,7 @@ use code_login::AuthMode;
use code_protocol::mcp_protocol::AuthMode as McpAuthMode;
use code_protocol::protocol::SessionSource;
use code_protocol::num_format::format_with_separators;
use code_core::split_command_and_args;
mod diff_handlers;
@@ -7001,6 +7002,9 @@ impl ChatWidget<'_> {
// dispatch_command() removed — command routing is handled at the App layer via AppEvent::DispatchCommand
pub(crate) fn handle_paste(&mut self, text: String) {
if settings_handlers::handle_settings_paste(self, text.clone()) {
return;
}
// Check if the pasted text is a file path to an image
let trimmed = text.trim();
@@ -17616,6 +17620,7 @@ Have we met every part of this goal and is there no further work to do?"#
let key = pending.key();
let attempt = pending.clone();
self.pending_agent_updates.insert(key, pending);
self.refresh_settings_overview_rows();
let tx = self.app_event_tx.clone();
tokio::spawn(async move {
let cfg = attempt.cfg.clone();
@@ -18705,6 +18710,15 @@ Have we met every part of this goal and is there no further work to do?"#
}
}
fn command_for_check(command: &str) -> String {
let (base, _) = split_command_and_args(command);
if base.trim().is_empty() {
command.trim().to_string()
} else {
base
}
}
fn is_builtin_agent(name: &str, command: &str) -> bool {
name.eq_ignore_ascii_case("code")
|| name.eq_ignore_ascii_case("codex")
@@ -18725,6 +18739,14 @@ Have we met every part of this goal and is there no further work to do?"#
extras.push(agent.name.to_ascii_lowercase());
}
}
let mut pending_agents: HashMap<String, AgentConfig> = HashMap::new();
for pending in self.pending_agent_updates.values() {
let lower = pending.cfg.name.to_ascii_lowercase();
pending_agents.insert(lower.clone(), pending.cfg.clone());
if !ordered.iter().any(|name| name.eq_ignore_ascii_case(&lower)) {
extras.push(lower);
}
}
extras.sort();
for extra in extras {
if !ordered.iter().any(|name| name.eq_ignore_ascii_case(&extra)) {
@@ -18733,19 +18755,46 @@ Have we met every part of this goal and is there no further work to do?"#
}
for name in ordered.iter() {
let name_lower = name.to_ascii_lowercase();
if let Some(cfg) = self
.config
.agents
.iter()
.find(|a| a.name.eq_ignore_ascii_case(name))
{
let builtin = is_builtin_agent(&cfg.name, &cfg.command);
let spec_cli = agent_model_spec(&cfg.name)
.or_else(|| agent_model_spec(&cfg.command))
.map(|spec| spec.cli);
let command_to_check = command_for_check(&cfg.command);
let installed = if builtin {
true
} else if command_exists(&command_to_check) {
true
} else if let Some(cli) = spec_cli {
command_exists(cli)
} else {
false
};
agent_rows.push(AgentOverviewRow {
name: cfg.name.clone(),
enabled: cfg.enabled,
installed,
description: Self::agent_description_for(
&cfg.name,
Some(&cfg.command),
cfg.description.as_deref(),
),
});
} else if let Some(cfg) = pending_agents.get(&name_lower) {
let builtin = is_builtin_agent(&cfg.name, &cfg.command);
let spec_cli = agent_model_spec(&cfg.name)
.or_else(|| agent_model_spec(&cfg.command))
.map(|spec| spec.cli);
let command_to_check = command_for_check(&cfg.command);
let installed = if builtin {
true
} else if command_exists(&cfg.command) {
} else if command_exists(&command_to_check) {
true
} else if let Some(cli) = spec_cli {
command_exists(cli)
@@ -1,6 +1,30 @@
use super::ChatWidget;
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind};
pub(super) fn handle_settings_paste(chat: &mut ChatWidget<'_>, text: String) -> bool {
if chat.settings.overlay.is_none() {
return false;
}
let overlay = match chat.settings.overlay.as_mut() {
Some(overlay) => overlay,
None => return false,
};
if overlay.is_menu_active() || overlay.is_help_visible() {
return false;
}
if let Some(content) = overlay.active_content_mut() {
if content.handle_paste(text) {
chat.request_redraw();
return true;
}
}
false
}
/// Handle key presses for the full-screen settings overlay. Returns true when the
/// key has been consumed (overlay stays modal while active).
pub(super) fn handle_settings_key(chat: &mut ChatWidget<'_>, key_event: KeyEvent) -> bool {
@@ -13,6 +13,7 @@ use crate::bottom_pane::{
agents_settings_view::SubagentEditorView,
AutoDriveSettingsView,
BottomPaneView,
ConditionalUpdate,
settings_panel::{render_panel, PanelFrameStyle},
GithubSettingsView,
McpSettingsView,
@@ -36,6 +37,9 @@ pub(crate) trait SettingsContent {
fn handle_key(&mut self, key: KeyEvent) -> bool;
fn is_complete(&self) -> bool;
fn on_close(&mut self) {}
fn handle_paste(&mut self, _text: String) -> bool {
false
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -784,6 +788,15 @@ impl SettingsContent for AgentsSettingsContent {
fn is_complete(&self) -> bool {
false
}
fn handle_paste(&mut self, text: String) -> bool {
match &mut self.pane {
AgentsPane::Agent(view) => {
matches!(view.handle_paste(text), ConditionalUpdate::NeedsRedraw)
}
_ => false,
}
}
}
pub(crate) struct LimitsSettingsContent {