use super::{MissionControlApp, actions::ActionContext, display_reducer};
use crate::tui::{input, state};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
pub(super) fn skill_candidates(
candidates: &[state::AutocompleteCandidate],
) -> Vec<state::AutocompleteCandidate> {
candidates
.iter()
.filter(|candidate| candidate.kind == state::AutocompleteKind::SkillTag)
.cloned()
.collect()
}
impl MissionControlApp {
pub(super) fn edit_subagent_prompt(
&mut self,
key: KeyEvent,
viewport: input::PromptViewport,
ui_state: &mut state::MissionControlState,
context: &mut ActionContext<'_>,
) {
let Some(control) = ui_state
.selected_subagent_control()
.filter(|control| control.is_active())
else {
return;
};
let candidates = skill_candidates(context.autocomplete_candidates);
let copy = ui_state
.with_selected_subagent_prompt(|state| {
if key.code == KeyCode::Enter
&& key.modifiers == KeyModifiers::NONE
&& !state.autocomplete_visible()
{
submit_steering(state, &control.steering);
return None;
}
edit_with_shared_prompt_keys(key, viewport, state, &candidates)
})
.flatten();
if let Some(text) = copy {
Self::apply_copy_request(
&text,
ui_state,
context.clipboard,
std::time::Instant::now(),
);
}
}
}
fn edit_with_shared_prompt_keys(
key: KeyEvent,
viewport: input::PromptViewport,
state: &mut state::MissionControlState,
candidates: &[state::AutocompleteCandidate],
) -> Option<String> {
let viewer = state.modals.subagent_viewer.take();
let command = input::classify_key_with_all_viewports(
key,
state,
input::KeyViewports {
prompt: Some(viewport),
..Default::default()
},
candidates,
);
state.modals.subagent_viewer = viewer;
let (action, display) = command.into_parts();
if let Some(display) = display {
apply_editor_display(display, state, candidates);
}
match action {
input::InputAction::CopySelectedPrompt(text) => Some(text),
_ => None,
}
}
fn apply_editor_display(
command: input::DisplayCommand,
state: &mut state::MissionControlState,
candidates: &[state::AutocompleteCandidate],
) {
match command {
input::DisplayCommand::Batch(commands) => {
for command in commands {
apply_editor_display(command, state, candidates);
}
}
input::DisplayCommand::Prompt(input::PromptCommand::TakeForSubmit) => {}
command @ (input::DisplayCommand::Prompt(_)
| input::DisplayCommand::RefreshAutocomplete) => {
display_reducer::apply_display_command(command, state, candidates);
}
_ => {}
}
}
fn submit_steering(
state: &mut state::MissionControlState,
steering: &crate::agent::steering::AgentSteering,
) {
use crate::agent::steering::SteeringRejected;
let prompt = state.prompt_plain_text();
if prompt.trim_start().starts_with('/') {
state.status = "Slash commands are not available for subagents".into();
return;
}
if prompt.trim().is_empty() {
return;
}
match steering.try_enqueue_nonblocking(prompt) {
Ok(_) => {
state.clear_prompt_input();
state.hide_autocomplete();
state.status = "Subagent steering queued".into();
}
Err(SteeringRejected::Full { capacity }) => {
state.status = format!("Steering queue full ({capacity} max)");
}
Err(SteeringRejected::Closed) => {
state.status = "Subagent no longer accepts steering".into();
}
Err(SteeringRejected::Busy) => {
state.status = "Steering is being saved; try again".into();
}
Err(SteeringRejected::Empty) => {}
}
}
pub(super) fn cancel_or_recall_selected(state: &mut state::MissionControlState) {
let Some(control) = state.selected_subagent_control() else {
return;
};
if !recall_selected_steering(state, &control.steering) && control.is_active() {
control.cancel();
state.status = "Subagent cancellation requested".into();
}
state.set_subagent_prompt_notice(state.status.clone());
}
fn recall_selected_steering(
state: &mut state::MissionControlState,
steering: &crate::agent::steering::AgentSteering,
) -> bool {
let mut recall_rejected = false;
let recalled = steering.restore_pending_messages(|messages| {
let restored = state
.with_selected_subagent_prompt(|state| {
let draft = state.prompt_plain_text();
let text = if draft.is_empty() {
messages.to_owned()
} else {
format!("{messages}\n{draft}")
};
let restored = matches!(
state.set_prompt_text(&text, text.len()),
state::PromptEditResult::Changed | state::PromptEditResult::Unchanged
);
if restored {
state.hide_autocomplete();
}
restored
})
.unwrap_or(false);
recall_rejected = !restored;
restored
});
state.status = match recalled {
Ok(_) if recall_rejected => "Subagent draft is too large to recall steering".into(),
Ok(true) => "Queued steering restored to subagent prompt".into(),
Ok(false) => return false,
Err(()) => "Steering is being injected; try Alt-C again".into(),
};
true
}