mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
feat(tui/agents): simplify to single orchestrator input, restore labels, and prefill fields\n\n- Remove per-agent instructions field; keep a single parent (orchestrator) text field that resizes with content.\n- Prefill Name for built-ins and existing commands; retain selected agents when editing.\n- Restore and preserve titles/labels by reserving dynamic blank rows under the label and rendering the field there (no overlap).\n- Adjust navigation: 0 Name, 1 Mode, 2 Agents, 3 Orchestrator, 4 Save, 5 Cancel.\n- Update desired height to include orchestrator field rows; drop agent rows.\n- Persist only orchestrator_instructions; always clear agent_instructions.
This commit is contained in:
@@ -84,10 +84,9 @@ pub struct SubagentEditorView {
|
||||
selected_agent_indices: Vec<usize>,
|
||||
agent_cursor: usize,
|
||||
orch_field: FormTextField,
|
||||
agent_field: FormTextField,
|
||||
available_agents: Vec<String>,
|
||||
is_new: bool,
|
||||
field: usize, // 0 name, 1 mode, 2 agents, 3 orch, 4 agent, 5 save, 6 cancel
|
||||
field: usize, // 0 name, 1 mode, 2 agents, 3 orch, 4 save, 5 cancel
|
||||
is_complete: bool,
|
||||
app_event_tx: AppEventSender,
|
||||
}
|
||||
@@ -100,19 +99,19 @@ impl SubagentEditorView {
|
||||
selected_agent_indices: Vec::new(),
|
||||
agent_cursor: 0,
|
||||
orch_field: FormTextField::new_multi_line(),
|
||||
agent_field: FormTextField::new_multi_line(),
|
||||
available_agents: root.available_agents.clone(),
|
||||
is_new: name.is_empty(),
|
||||
field: 0,
|
||||
is_complete: false,
|
||||
app_event_tx: root.app_event_tx.clone(),
|
||||
};
|
||||
// Always seed the name field with the provided name
|
||||
if !name.is_empty() { me.name_field.set_text(name); }
|
||||
// Seed from existing config if present
|
||||
if let Some(cfg) = root.existing.iter().find(|c| c.name.eq_ignore_ascii_case(name)) {
|
||||
me.name_field.set_text(&cfg.name);
|
||||
me.read_only = cfg.read_only;
|
||||
me.orch_field.set_text(&cfg.orchestrator_instructions.clone().unwrap_or_default());
|
||||
me.agent_field.set_text(&cfg.agent_instructions.clone().unwrap_or_default());
|
||||
let set: std::collections::HashSet<String> = cfg.agents.iter().cloned().collect();
|
||||
for (idx, a) in me.available_agents.iter().enumerate() {
|
||||
if set.contains(a) { me.selected_agent_indices.push(idx); }
|
||||
@@ -123,19 +122,16 @@ impl SubagentEditorView {
|
||||
"plan" => {
|
||||
me.read_only = true;
|
||||
me.orch_field.set_text("Plan a multi-agent approach. Research the repo structure, enumerate tasks, dependencies, risks, and milestones. Use multiple agents in read-only mode and synthesize a single, actionable plan.");
|
||||
me.agent_field.set_text("Study the codebase, cite files you read, list assumptions. Propose concrete steps and call out risks or unknowns.");
|
||||
}
|
||||
"solve" => {
|
||||
me.read_only = true;
|
||||
me.orch_field.set_text("Coordinate multiple agents to propose competing solutions. Keep all agents read-only. Compare proposals, pick one, and outline verification steps.");
|
||||
me.agent_field.set_text("Propose a fix with exact steps to validate. Be explicit about tests to run and edge cases to check.");
|
||||
}
|
||||
"code" => {
|
||||
me.read_only = false;
|
||||
me.orch_field.set_text("Coordinate write-mode agents to implement the task in isolated worktrees. Surface worktree_path and branch_name after completion.");
|
||||
me.agent_field.set_text("Make minimal, focused changes with clear rationale. Include tests or validation steps where possible.");
|
||||
}
|
||||
_ => { me.name_field.set_text(name); }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
me
|
||||
@@ -176,10 +172,7 @@ impl SubagentEditorView {
|
||||
let t = self.orch_field.text().trim().to_string();
|
||||
if t.is_empty() { None } else { Some(t) }
|
||||
},
|
||||
agent_instructions: {
|
||||
let t = self.agent_field.text().trim().to_string();
|
||||
if t.is_empty() { None } else { Some(t) }
|
||||
},
|
||||
agent_instructions: None,
|
||||
};
|
||||
// Persist to disk asynchronously to avoid blocking the TUI runtime
|
||||
if let Ok(home) = codex_core::config::find_codex_home() {
|
||||
@@ -197,17 +190,17 @@ impl<'a> BottomPaneView<'a> for SubagentEditorView {
|
||||
fn handle_key_event(&mut self, _pane: &mut BottomPane<'a>, key_event: KeyEvent) {
|
||||
match key_event {
|
||||
KeyEvent { code: KeyCode::Esc, .. } => { self.is_complete = true; }
|
||||
KeyEvent { code: KeyCode::Tab, .. } => { self.field = (self.field + 1).min(6); }
|
||||
KeyEvent { code: KeyCode::Tab, .. } => { self.field = (self.field + 1).min(5); }
|
||||
KeyEvent { code: KeyCode::BackTab, .. } => { if self.field > 0 { self.field -= 1; } }
|
||||
KeyEvent { code: KeyCode::Up, modifiers, .. } => {
|
||||
if (self.field == 3 || self.field == 4) && modifiers.contains(KeyModifiers::SHIFT) {
|
||||
let _ = match self.field { 3 => self.orch_field.handle_key(key_event), 4 => self.agent_field.handle_key(key_event), _ => false };
|
||||
if self.field == 3 && modifiers.contains(KeyModifiers::SHIFT) {
|
||||
let _ = self.orch_field.handle_key(key_event);
|
||||
} else if self.field > 0 { self.field -= 1; }
|
||||
}
|
||||
KeyEvent { code: KeyCode::Down, modifiers, .. } => {
|
||||
if (self.field == 3 || self.field == 4) && modifiers.contains(KeyModifiers::SHIFT) {
|
||||
let _ = match self.field { 3 => self.orch_field.handle_key(key_event), 4 => self.agent_field.handle_key(key_event), _ => false };
|
||||
} else { self.field = (self.field + 1).min(6); }
|
||||
if self.field == 3 && modifiers.contains(KeyModifiers::SHIFT) {
|
||||
let _ = self.orch_field.handle_key(key_event);
|
||||
} else { self.field = (self.field + 1).min(5); }
|
||||
}
|
||||
KeyEvent { code: KeyCode::Left, .. } if self.field == 1 => { self.read_only = !self.read_only; }
|
||||
KeyEvent { code: KeyCode::Right, .. } if self.field == 1 => { self.read_only = !self.read_only; }
|
||||
@@ -225,9 +218,8 @@ impl<'a> BottomPaneView<'a> for SubagentEditorView {
|
||||
// Delegate input to focused text fields (handles Shift‑chars, Enter/newline, undo, etc.)
|
||||
ev @ KeyEvent { .. } if self.field == 0 => { let _ = self.name_field.handle_key(ev); }
|
||||
ev @ KeyEvent { .. } if self.field == 3 => { let _ = self.orch_field.handle_key(ev); }
|
||||
ev @ KeyEvent { .. } if self.field == 4 => { let _ = self.agent_field.handle_key(ev); }
|
||||
KeyEvent { code: KeyCode::Enter, .. } if self.field == 5 => { self.save(); }
|
||||
KeyEvent { code: KeyCode::Enter, .. } if self.field == 6 => { self.is_complete = true; }
|
||||
KeyEvent { code: KeyCode::Enter, .. } if self.field == 4 => { self.save(); }
|
||||
KeyEvent { code: KeyCode::Enter, .. } if self.field == 5 => { self.is_complete = true; }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -235,12 +227,7 @@ impl<'a> BottomPaneView<'a> for SubagentEditorView {
|
||||
fn is_complete(&self) -> bool { self.is_complete }
|
||||
|
||||
fn handle_paste(&mut self, text: String) -> super::bottom_pane_view::ConditionalUpdate {
|
||||
match self.field {
|
||||
0 => self.name_field.handle_paste(text),
|
||||
3 => self.orch_field.handle_paste(text),
|
||||
4 => self.agent_field.handle_paste(text),
|
||||
_ => {}
|
||||
}
|
||||
match self.field { 0 => self.name_field.handle_paste(text), 3 => self.orch_field.handle_paste(text), _ => {} }
|
||||
super::bottom_pane_view::ConditionalUpdate::NeedsRedraw
|
||||
}
|
||||
|
||||
@@ -249,16 +236,12 @@ impl<'a> BottomPaneView<'a> for SubagentEditorView {
|
||||
let inner_w = width.saturating_sub(2);
|
||||
let content_w = inner_w.saturating_sub(1).max(10) as usize;
|
||||
// Static rows (content lines):
|
||||
// Name(1), Mode(1), Agents label(1), Agents line(1),
|
||||
// Orchestrator label(1), Agent label(1), Buttons(1)
|
||||
let static_rows: u16 = 7;
|
||||
// Content rows for the two instruction bodies (wrapped)
|
||||
// Name(1), Mode(1), Agents label(1), Agents line(1), Orchestrator label(1), Buttons(1)
|
||||
let static_rows: u16 = 6;
|
||||
// Content rows for the instruction body (wrapped)
|
||||
let orch_rows = self.orch_field.desired_height(content_w as u16);
|
||||
let agent_rows = self.agent_field.desired_height(content_w as u16);
|
||||
// Sum and add borders (2)
|
||||
let content_rows = static_rows
|
||||
.saturating_add(orch_rows)
|
||||
.saturating_add(agent_rows);
|
||||
let content_rows = static_rows.saturating_add(orch_rows);
|
||||
(content_rows + 2).clamp(8, 50)
|
||||
}
|
||||
|
||||
@@ -294,16 +277,16 @@ impl<'a> BottomPaneView<'a> for SubagentEditorView {
|
||||
lines.push(Line::from(Span::styled("Agents:", label(2))));
|
||||
lines.push(Line::from(spans));
|
||||
|
||||
// Orchestrator label (content drawn below)
|
||||
// Orchestrator label (content will be drawn in reserved blank rows below)
|
||||
lines.push(Line::from(Span::styled("Instructions to Code (orchestrator):", label(3))));
|
||||
lines.push(Line::from(""));
|
||||
// Reserve as many blank lines as needed for the field height
|
||||
let inner = Block::default().inner(area);
|
||||
let content_rect = Rect { x: inner.x.saturating_add(1), y: inner.y, width: inner.width.saturating_sub(1), height: inner.height };
|
||||
let orch_h = self.orch_field.desired_height(content_rect.width);
|
||||
for _ in 0..orch_h.max(1) { lines.push(Line::from("")); }
|
||||
|
||||
// Agent label (content drawn below)
|
||||
lines.push(Line::from(Span::styled("Instructions to each agent:", label(4))));
|
||||
lines.push(Line::from(""));
|
||||
|
||||
let save_style = sel(5).fg(crate::colors::success());
|
||||
let cancel_style = sel(6).fg(crate::colors::error());
|
||||
let save_style = sel(4).fg(crate::colors::success());
|
||||
let cancel_style = sel(5).fg(crate::colors::error());
|
||||
lines.push(Line::from(vec![
|
||||
Span::styled("[ Save ] ", save_style),
|
||||
Span::styled("[ Cancel ]", cancel_style),
|
||||
@@ -329,16 +312,10 @@ impl<'a> BottomPaneView<'a> for SubagentEditorView {
|
||||
// Row 1: Mode; Row 2: Agents label; Row 3: Agents list
|
||||
y = y.saturating_add(3);
|
||||
|
||||
// Row 4: Orchestrator label; draw field starting next row
|
||||
// Row 4: Orchestrator label; draw field starting next row (reserved area)
|
||||
y = y.saturating_add(1);
|
||||
let orch_h = self.orch_field.desired_height(content_w);
|
||||
self.orch_field.render(Rect { x: content_rect.x, y, width: content_w, height: orch_h }, buf, self.field == 3);
|
||||
y = y.saturating_add(orch_h);
|
||||
|
||||
// Next row is Agent label; draw field starting the row after
|
||||
y = y.saturating_add(1);
|
||||
let agent_h = self.agent_field.desired_height(content_w);
|
||||
self.agent_field.render(Rect { x: content_rect.x, y, width: content_w, height: agent_h }, buf, self.field == 4);
|
||||
let orch_h2 = self.orch_field.desired_height(content_w);
|
||||
self.orch_field.render(Rect { x: content_rect.x, y, width: content_w, height: orch_h2 }, buf, self.field == 3);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user