mirror of
https://github.com/openai/codex.git
synced 2026-09-15 12:08:01 +00:00
Add model grouping to the agent command center (#44957)
## What changed - Cycle task grouping through project, status, and model with `Ctrl+S`, and show the active grouping in the footer. - Group tasks by model with the most recently updated tasks first within each group. Use `Unknown` for missing or empty model names. - Display the model in task details and refresh it when thread settings change. - In model grouping, dispatch new tasks without inheriting the selected task's working directory, matching status grouping. ## Testing Add coverage for model grouping, selection preservation, navigation, and new-task dispatch across grouping modes. Update snapshots for model details and grouping hints. GitOrigin-RevId: 429cf61c1e7438ba3b2f989d3ed14e12545856f7
This commit is contained in:
39
codex-rs/tui/src/app/agents_overview_grouping.rs
Normal file
39
codex-rs/tui/src/app/agents_overview_grouping.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
//! Task grouping modes share model labels and group membership across rendering and navigation.
|
||||
|
||||
use super::AgentsOverviewView;
|
||||
use codex_app_server_protocol::Thread;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub(in super::super) enum AgentsOverviewGrouping {
|
||||
#[default]
|
||||
Project,
|
||||
Status,
|
||||
Model,
|
||||
}
|
||||
|
||||
pub(super) fn model_name(thread: &Thread) -> &str {
|
||||
thread
|
||||
.model
|
||||
.as_deref()
|
||||
.filter(|model| !model.is_empty())
|
||||
.unwrap_or("Unknown")
|
||||
}
|
||||
|
||||
impl AgentsOverviewView {
|
||||
pub(super) fn same_group(
|
||||
&self,
|
||||
grouping: AgentsOverviewGrouping,
|
||||
left: usize,
|
||||
right: usize,
|
||||
) -> bool {
|
||||
match grouping {
|
||||
AgentsOverviewGrouping::Project => {
|
||||
self.project_groups[left].key == self.project_groups[right].key
|
||||
}
|
||||
AgentsOverviewGrouping::Status => self.rows[left].group == self.rows[right].group,
|
||||
AgentsOverviewGrouping::Model => {
|
||||
model_name(&self.rows[left].thread) == model_name(&self.rows[right].thread)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ impl AgentsOverviewView {
|
||||
pub(super) fn handle_composer_key(&mut self, key: KeyEvent) {
|
||||
let mut state = self.state();
|
||||
let offline = state.connection_notice.is_some();
|
||||
let status_grouping = state.status_grouping;
|
||||
let grouping = state.grouping;
|
||||
if !offline
|
||||
&& crate::key_hint::plain(KeyCode::Right).is_press(key)
|
||||
&& state
|
||||
@@ -78,7 +78,7 @@ impl AgentsOverviewView {
|
||||
self.app_event_tx
|
||||
.send(AppEvent::DispatchAgentsOverviewTask {
|
||||
prompt,
|
||||
cwd: (!status_grouping)
|
||||
cwd: (grouping == AgentsOverviewGrouping::Project)
|
||||
.then(|| self.selected_row().map(|row| row.thread.cwd.clone()))
|
||||
.flatten(),
|
||||
});
|
||||
|
||||
@@ -95,7 +95,11 @@ impl AgentsOverviewView {
|
||||
add_hint(
|
||||
self.agents_keymap
|
||||
.primary_hint("toggle_grouping", &self.agents_keymap.toggle_grouping),
|
||||
"group",
|
||||
match self.state().grouping {
|
||||
AgentsOverviewGrouping::Project => "group: project",
|
||||
AgentsOverviewGrouping::Status => "group: status",
|
||||
AgentsOverviewGrouping::Model => "group: model",
|
||||
},
|
||||
true,
|
||||
);
|
||||
add_hint(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use super::super::agents_overview_view::AgentsOverviewGrouping;
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1403,6 +1404,64 @@ async fn worktrees_overview_grouping_requires_feature() {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn overview_model_grouping_shows_details_and_preserves_selection() {
|
||||
let mut app = make_test_app().await;
|
||||
let threads = [
|
||||
("Older task", Some("model-a"), 1),
|
||||
("Other model", Some("model-b"), 2),
|
||||
("Recent task", Some("model-a"), 3),
|
||||
("Legacy task", None, 4),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(name, model, index)| {
|
||||
let mut thread = overview_thread(
|
||||
ThreadId::from_u128(index),
|
||||
/*parent_thread_id*/ None,
|
||||
name,
|
||||
ThreadStatus::Idle,
|
||||
);
|
||||
thread.model = model.map(str::to_string);
|
||||
thread.updated_at = index as i64;
|
||||
thread
|
||||
})
|
||||
.collect();
|
||||
let selected = ThreadId::from_u128(/*value*/ 1);
|
||||
let mut view = app.agents_overview_view(threads, Some(selected));
|
||||
view.handle_key_event(KeyCode::Esc.into());
|
||||
for _ in 0..2 {
|
||||
view.handle_key_event(KeyEvent::new(KeyCode::Char('s'), KeyModifiers::CONTROL));
|
||||
}
|
||||
assert_eq!(
|
||||
app.agents_overview.view_state.lock().unwrap().grouping,
|
||||
AgentsOverviewGrouping::Model
|
||||
);
|
||||
assert_eq!(
|
||||
view.rows[view.selected_index().unwrap()].thread_id,
|
||||
selected
|
||||
);
|
||||
// Within a model, newer tasks come first; navigation then crosses model groups.
|
||||
for (key, expected) in [
|
||||
(KeyCode::Up, 3),
|
||||
(KeyCode::Down, 1),
|
||||
(KeyCode::Down, 2),
|
||||
(KeyCode::Down, 4),
|
||||
] {
|
||||
view.handle_key_event(key.into());
|
||||
assert_eq!(
|
||||
view.rows[view.selected_index().unwrap()].thread_id,
|
||||
ThreadId::from_u128(expected)
|
||||
);
|
||||
}
|
||||
app.chat_widget.show_bottom_pane_view(Box::new(view));
|
||||
insta::assert_snapshot!(
|
||||
"agents_overview_model_grouping",
|
||||
render_bottom_popup(&app.chat_widget, /*width*/ 100)
|
||||
.replace(&test_path_display("/tmp/project"), "/tmp/project")
|
||||
.replace("fwd del", "del")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn shared_overview_shows_only_root_sessions() {
|
||||
assert_eq!(
|
||||
@@ -1466,12 +1525,21 @@ async fn shared_overview_shows_only_root_sessions() {
|
||||
Arc::clone(&app.agents_overview.view_state),
|
||||
);
|
||||
let state = &app.agents_overview.view_state;
|
||||
assert!(!state.lock().unwrap().status_grouping);
|
||||
assert_eq!(
|
||||
state.lock().unwrap().grouping,
|
||||
AgentsOverviewGrouping::Project
|
||||
);
|
||||
action_view.handle_key_event(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
|
||||
action_view.handle_key_event(KeyEvent::new(KeyCode::Char('s'), KeyModifiers::CONTROL));
|
||||
assert!(state.lock().unwrap().status_grouping);
|
||||
assert_eq!(
|
||||
state.lock().unwrap().grouping,
|
||||
AgentsOverviewGrouping::Status
|
||||
);
|
||||
app.agents_overview_view(Vec::new(), /*selected_thread_id*/ None);
|
||||
assert!(state.lock().unwrap().status_grouping);
|
||||
assert_eq!(
|
||||
state.lock().unwrap().grouping,
|
||||
AgentsOverviewGrouping::Status
|
||||
);
|
||||
assert!(
|
||||
action_view.handle_paste("Use \u{1b}[31mthe\u{1b}[0m current project\u{7}".to_string())
|
||||
);
|
||||
@@ -1483,6 +1551,23 @@ async fn shared_overview_shows_only_root_sessions() {
|
||||
));
|
||||
action_view.handle_key_event(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
|
||||
action_view.handle_key_event(KeyEvent::new(KeyCode::Char('s'), KeyModifiers::CONTROL));
|
||||
assert_eq!(
|
||||
state.lock().unwrap().grouping,
|
||||
AgentsOverviewGrouping::Model
|
||||
);
|
||||
assert!(action_view.handle_paste("Use the default project".to_string()));
|
||||
action_view.handle_key_event(KeyCode::Enter.into());
|
||||
assert!(matches!(
|
||||
event_rx.try_recv(),
|
||||
Ok(AppEvent::DispatchAgentsOverviewTask { prompt, cwd: None })
|
||||
if prompt.text == "Use the default project"
|
||||
));
|
||||
action_view.handle_key_event(KeyCode::Esc.into());
|
||||
action_view.handle_key_event(KeyEvent::new(KeyCode::Char('s'), KeyModifiers::CONTROL));
|
||||
assert_eq!(
|
||||
state.lock().unwrap().grouping,
|
||||
AgentsOverviewGrouping::Project
|
||||
);
|
||||
assert!(action_view.handle_paste("Fix the flaky tests after all retries complete".to_string()));
|
||||
let area = ratatui::layout::Rect::new(
|
||||
/*x*/ 0, /*y*/ 0, /*width*/ 40, /*height*/ 12,
|
||||
|
||||
@@ -86,6 +86,7 @@ impl App {
|
||||
ServerNotification::ThreadSettingsUpdated(settings) => {
|
||||
if let Some(thread) = thread {
|
||||
thread.cwd.clone_from(&settings.thread_settings.cwd);
|
||||
thread.model = Some(settings.thread_settings.model.clone());
|
||||
thread
|
||||
.model_provider
|
||||
.clone_from(&settings.thread_settings.model_provider);
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
//! Dashboard for inspecting and managing the TUI's retained daemon tasks.
|
||||
//! The shared view state retains the new-task editor across metadata refreshes.
|
||||
|
||||
#[path = "agents_overview_grouping.rs"]
|
||||
mod grouping;
|
||||
#[path = "agents_overview_input.rs"]
|
||||
mod input;
|
||||
#[path = "agents_overview_render.rs"]
|
||||
mod render;
|
||||
|
||||
pub(super) use grouping::AgentsOverviewGrouping;
|
||||
use grouping::model_name;
|
||||
|
||||
use super::agents_overview::AGENTS_OVERVIEW_VIEW_ID;
|
||||
use super::agents_overview_details::AgentsOverviewDetails;
|
||||
use crate::app_event::AgentsOverviewAction;
|
||||
@@ -138,7 +143,7 @@ pub(super) struct AgentsOverviewViewState {
|
||||
pub(super) server_version_notice: Option<String>,
|
||||
search: String,
|
||||
searching: bool,
|
||||
pub(super) status_grouping: bool,
|
||||
pub(super) grouping: AgentsOverviewGrouping,
|
||||
pub(super) renaming: bool,
|
||||
// The picker can finish this retained view when it selects the already active session.
|
||||
pub(super) completion: Option<ViewCompletion>,
|
||||
@@ -281,13 +286,20 @@ impl AgentsOverviewView {
|
||||
(search.is_empty() || searchable.contains(&search)).then_some(index)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if !state.status_grouping {
|
||||
visible.sort_by_key(|index| {
|
||||
match state.grouping {
|
||||
AgentsOverviewGrouping::Project => visible.sort_by_key(|index| {
|
||||
(
|
||||
&self.project_groups[*index].key,
|
||||
std::cmp::Reverse(self.rows[*index].thread.updated_at),
|
||||
)
|
||||
});
|
||||
}),
|
||||
AgentsOverviewGrouping::Status => {}
|
||||
AgentsOverviewGrouping::Model => visible.sort_by_key(|index| {
|
||||
(
|
||||
model_name(&self.rows[*index].thread),
|
||||
std::cmp::Reverse(self.rows[*index].thread.updated_at),
|
||||
)
|
||||
}),
|
||||
}
|
||||
visible
|
||||
}
|
||||
@@ -367,11 +379,7 @@ impl AgentsOverviewView {
|
||||
fn render_rows(&self, area: Rect, buf: &mut Buffer) {
|
||||
let mut offset = 0;
|
||||
let mut previous_group_index: Option<usize> = None;
|
||||
let project_grouping = !self
|
||||
.state
|
||||
.lock()
|
||||
.unwrap_or_else(PoisonError::into_inner)
|
||||
.status_grouping;
|
||||
let grouping = self.state().grouping;
|
||||
let visible = self.visible_indices();
|
||||
let mut first = visible
|
||||
.iter()
|
||||
@@ -381,13 +389,7 @@ impl AgentsOverviewView {
|
||||
while first > 0 {
|
||||
let previous_index = visible[first - 1];
|
||||
let current_index = visible[first];
|
||||
let previous = &self.rows[previous_index];
|
||||
let current = &self.rows[current_index];
|
||||
let group_changed = if project_grouping {
|
||||
self.project_groups[previous_index].key != self.project_groups[current_index].key
|
||||
} else {
|
||||
previous.group != current.group
|
||||
};
|
||||
let group_changed = !self.same_group(grouping, previous_index, current_index);
|
||||
let added_height = 1 + 2 * u16::from(group_changed);
|
||||
if height + added_height > area.height {
|
||||
break;
|
||||
@@ -400,18 +402,15 @@ impl AgentsOverviewView {
|
||||
break;
|
||||
}
|
||||
let row = &self.rows[index];
|
||||
let group = if project_grouping {
|
||||
self.project_groups[index].heading.display().to_string()
|
||||
} else {
|
||||
row.group.label().to_string()
|
||||
};
|
||||
let group_changed = previous_group_index.is_none_or(|previous_index| {
|
||||
if project_grouping {
|
||||
self.project_groups[previous_index].key != self.project_groups[index].key
|
||||
} else {
|
||||
self.rows[previous_index].group != row.group
|
||||
let group = match grouping {
|
||||
AgentsOverviewGrouping::Project => {
|
||||
self.project_groups[index].heading.display().to_string()
|
||||
}
|
||||
});
|
||||
AgentsOverviewGrouping::Status => row.group.label().to_string(),
|
||||
AgentsOverviewGrouping::Model => model_name(&row.thread).to_string(),
|
||||
};
|
||||
let group_changed = previous_group_index
|
||||
.is_none_or(|previous_index| !self.same_group(grouping, previous_index, index));
|
||||
if group_changed {
|
||||
offset += u16::from(previous_group_index.is_some());
|
||||
if offset >= area.height {
|
||||
@@ -421,13 +420,8 @@ impl AgentsOverviewView {
|
||||
.rows
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(candidate_index, candidate)| {
|
||||
if project_grouping {
|
||||
self.project_groups[*candidate_index].key
|
||||
== self.project_groups[index].key
|
||||
} else {
|
||||
candidate.group == row.group
|
||||
}
|
||||
.filter(|(candidate_index, _)| {
|
||||
self.same_group(grouping, *candidate_index, index)
|
||||
})
|
||||
.count();
|
||||
Line::from(vec![group.clone().bold(), format!(" {count}").dim()])
|
||||
@@ -453,7 +447,7 @@ impl AgentsOverviewView {
|
||||
Span::styled(display_title(&row.thread), self.title_style(row.thread_id)),
|
||||
current.dim(),
|
||||
];
|
||||
if project_grouping {
|
||||
if grouping != AgentsOverviewGrouping::Status {
|
||||
spans.extend([" ".into(), status.dim()]);
|
||||
}
|
||||
Line::from(spans).render(Rect::new(area.x, area.y + offset, area.width, 1), buf);
|
||||
@@ -481,6 +475,10 @@ impl AgentsOverviewView {
|
||||
Line::default(),
|
||||
Line::from("Project".dim()),
|
||||
Line::from(row.thread.cwd.display().to_string()),
|
||||
Line::from(vec![
|
||||
"Model: ".dim(),
|
||||
model_name(&row.thread).to_string().into(),
|
||||
]),
|
||||
];
|
||||
if let Some(branch) = row
|
||||
.thread
|
||||
@@ -692,7 +690,11 @@ impl BottomPaneView for AgentsOverviewView {
|
||||
}
|
||||
if self.agents_keymap.toggle_grouping.is_pressed(key) {
|
||||
let mut state = self.state();
|
||||
state.status_grouping = !state.status_grouping;
|
||||
state.grouping = match state.grouping {
|
||||
AgentsOverviewGrouping::Project => AgentsOverviewGrouping::Status,
|
||||
AgentsOverviewGrouping::Status => AgentsOverviewGrouping::Model,
|
||||
AgentsOverviewGrouping::Model => AgentsOverviewGrouping::Project,
|
||||
};
|
||||
return;
|
||||
}
|
||||
if self.agents_keymap.new_task.is_pressed(key) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
source: tui/src/app/agents_overview_actions_tests.rs
|
||||
expression: "render_bottom_popup(&app.chat_widget, 48)"
|
||||
expression: "render_bottom_popup(&app.chat_widget,\n48).replace(&test_path_display(\"/tmp/project\"), \"/tmp/project\")"
|
||||
---
|
||||
Agent command center
|
||||
0 need input 0 working 1 ready
|
||||
@@ -17,12 +17,12 @@ expression: "render_bottom_popup(&app.chat_widget, 48)"
|
||||
|
||||
|
||||
|
||||
|
||||
New task
|
||||
|
||||
› Describe a new task
|
||||
|
||||
↑↓ navigate ctrl+o resume → open
|
||||
ctrl+n new task ctrl+f search ctrl+s group
|
||||
ctrl+r rename ctrl+x stop f5 f8 hide
|
||||
f5 f6 archive f5 f7 delete esc back
|
||||
ctrl+n new task ctrl+f search
|
||||
ctrl+s group: project ctrl+r rename
|
||||
ctrl+x stop f5 f8 hide f5 f6 archive
|
||||
f5 f7 delete esc back
|
||||
|
||||
@@ -22,6 +22,7 @@ expression: "cached.replace(&format!(\"{project} 1\"),\n&format!(\"/tmp/project
|
||||
" │ "
|
||||
" │ Project "
|
||||
" │ /tmp/project "
|
||||
" │ Model: Unknown "
|
||||
" │ "
|
||||
" │ Prompt "
|
||||
" │ Review parser and token handling. "
|
||||
@@ -36,7 +37,6 @@ expression: "cached.replace(&format!(\"{project} 1\"),\n&format!(\"/tmp/project
|
||||
" │ "
|
||||
" │ "
|
||||
" │ "
|
||||
" │ "
|
||||
" New task "
|
||||
" "
|
||||
"› Describe a new task "
|
||||
|
||||
@@ -12,11 +12,11 @@ expression: "render_bottom_popup(&app.chat_widget,\n96).replace(&format!(\"{proj
|
||||
│
|
||||
│ Last message
|
||||
│ let explanation = "A long code line
|
||||
│ should wrap inside the task details
|
||||
│ panel.";
|
||||
│ …
|
||||
│
|
||||
│ Project
|
||||
│ /tmp/project
|
||||
│ Model: Unknown
|
||||
│
|
||||
│ Prompt
|
||||
│ long prompt long prompt long prompt
|
||||
|
||||
@@ -12,11 +12,11 @@ expression: "render_bottom_popup(&app.chat_widget,\n96).replace(&format!(\"{proj
|
||||
│
|
||||
│ Last message
|
||||
│ Check Result
|
||||
│ ━━━━━━━━ ━━━━━━━━
|
||||
│ Parser Fixed
|
||||
│ …
|
||||
│
|
||||
│ Project
|
||||
│ /tmp/project
|
||||
│ Model: Unknown
|
||||
│
|
||||
│ Prompt
|
||||
│ long prompt long prompt long prompt
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
source: tui/src/app/agents_overview_tests.rs
|
||||
expression: "render_bottom_popup(&app.chat_widget,\n100).replace(&test_path_display(\"/tmp/project\"),\n\"/tmp/project\").replace(\"fwd del\", \"del\")"
|
||||
---
|
||||
Agent command center
|
||||
0 need input 0 working 4 ready
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
Unknown 1 │ Task details
|
||||
› ○ Legacy task Ready │
|
||||
│ Legacy task
|
||||
model-a 2 │ ○ Ready
|
||||
○ Recent task Ready │
|
||||
○ Older task Ready │ Project
|
||||
│ /tmp/project
|
||||
model-b 1 │ Model: Unknown
|
||||
○ Other model Ready │
|
||||
│ Prompt
|
||||
│ Legacy task
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
New task
|
||||
|
||||
› Describe a new task
|
||||
|
||||
↑↓ navigate ctrl+o resume → open ctrl+n new task ctrl+f search ctrl+s group: model ctrl+r rename
|
||||
ctrl+x stop ctrl+w hide ctrl+e archive del delete esc back
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: tui/src/app/agents_overview_tests.rs
|
||||
assertion_line: 314
|
||||
expression: "render_bottom_popup(&app.chat_widget,\n96).replace(&format!(\"{project} 2\"),\n&group).replace(&project, \"/tmp/project\").replace(\"fwd del\", \"del\")"
|
||||
---
|
||||
Agent command center
|
||||
@@ -13,6 +12,7 @@ expression: "render_bottom_popup(&app.chat_widget,\n96).replace(&format!(\"{proj
|
||||
│
|
||||
│ Project
|
||||
│ /tmp/project
|
||||
│ Model: Unknown
|
||||
│
|
||||
│ Prompt
|
||||
│ Second task
|
||||
@@ -20,10 +20,9 @@ expression: "render_bottom_popup(&app.chat_widget,\n96).replace(&format!(\"{proj
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
New task
|
||||
|
||||
› Unsent draft
|
||||
|
||||
↑↓ navigate ctrl+o resume → open ctrl+n new task ctrl+f search ctrl+s group ctrl+r rename
|
||||
ctrl+x stop ctrl+w hide ctrl+e archive del delete esc back
|
||||
↑↓ navigate ctrl+o resume → open ctrl+n new task ctrl+f search ctrl+s group: project
|
||||
ctrl+r rename ctrl+x stop ctrl+w hide ctrl+e archive del delete esc back
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
source: tui/src/app/agents_overview_tests.rs
|
||||
expression: "rendered.lines().find(|line| line.contains(\"open\")).unwrap()"
|
||||
---
|
||||
↑↓ navigate ctrl+o resume → open ctrl+n new task ctrl+f search ctrl+s group ctrl+r rename
|
||||
↑↓ navigate ctrl+o resume → open ctrl+n new task ctrl+f search ctrl+s group: project
|
||||
|
||||
@@ -12,6 +12,7 @@ expression: "render_bottom_popup(&app.chat_widget, 100)"
|
||||
│
|
||||
│ Project
|
||||
│ /tmp/project
|
||||
│ Model: Unknown
|
||||
│
|
||||
│ Prompt
|
||||
│ Selected task
|
||||
@@ -20,7 +21,6 @@ expression: "render_bottom_popup(&app.chat_widget, 100)"
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
New task
|
||||
|
||||
› Keep this task draft!
|
||||
|
||||
@@ -12,6 +12,7 @@ expression: "render_bottom_popup(&app.chat_widget, 100)"
|
||||
│
|
||||
│ Project
|
||||
│ /tmp/project
|
||||
│ Model: Unknown
|
||||
│
|
||||
│ Prompt
|
||||
│ Selected task
|
||||
@@ -20,7 +21,6 @@ expression: "render_bottom_popup(&app.chat_widget, 100)"
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
New task
|
||||
|
||||
› Keep this task draft!
|
||||
|
||||
@@ -12,6 +12,7 @@ expression: "render_bottom_popup(&app.chat_widget, 100)"
|
||||
│
|
||||
│ Project
|
||||
│ /tmp/project
|
||||
│ Model: Unknown
|
||||
│
|
||||
│ Prompt
|
||||
│ New task
|
||||
@@ -20,7 +21,6 @@ expression: "render_bottom_popup(&app.chat_widget, 100)"
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
New task
|
||||
|
||||
› Describe a new task
|
||||
|
||||
@@ -221,7 +221,7 @@ pub(super) const KEYMAP_ACTIONS: &[KeymapActionDescriptor] = &[
|
||||
action("agents", "Agents", "archive", "Archive the selected task and its child agents."),
|
||||
action("agents", "Agents", "delete", "Permanently delete the selected task and its child agents."),
|
||||
action("agents", "Agents", "hide", "Hide the selected task until explicitly resumed."),
|
||||
action("agents", "Agents", "toggle_grouping", "Group tasks by status or project."),
|
||||
action("agents", "Agents", "toggle_grouping", "Cycle task grouping by project, status, or model."),
|
||||
action("approval", "Approval", "open_fullscreen", "Open approval details fullscreen."),
|
||||
action("approval", "Approval", "open_thread", "Open the approval source thread when available."),
|
||||
action("approval", "Approval", "approve", "Approve the primary option."),
|
||||
|
||||
@@ -16,10 +16,10 @@ expression: "render_bottom_popup(&app.chat_widget,\n96).replace(&format!(\"{proj
|
||||
│
|
||||
│ Project
|
||||
│ /tmp/project
|
||||
│ Model: Unknown
|
||||
│
|
||||
│ Prompt
|
||||
│ Build the dashboard
|
||||
│
|
||||
New task
|
||||
|
||||
› First line
|
||||
|
||||
@@ -13,11 +13,11 @@ expression: "render_bottom_popup(&app.chat_widget,\n96).replace(&format!(\"{proj
|
||||
│ Agent: Check dependencies
|
||||
│ Which dependency version should I use?
|
||||
│ Open task to review.
|
||||
│ Waiting for approval.
|
||||
│ Waiting for your response.
|
||||
│ …
|
||||
│
|
||||
│ Project
|
||||
│ /tmp/project
|
||||
│ Model: Unknown
|
||||
│
|
||||
│ Prompt
|
||||
│ Repair authentication
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
source: tui/src/app/agents_overview_tests.rs
|
||||
assertion_line: 2041
|
||||
expression: "render_bottom_popup(&app.chat_widget, 48)"
|
||||
expression: rendered
|
||||
---
|
||||
Agent command center
|
||||
0 need input 0 working 0 ready
|
||||
@@ -18,12 +17,12 @@ expression: "render_bottom_popup(&app.chat_widget, 48)"
|
||||
|
||||
|
||||
|
||||
|
||||
New task
|
||||
|
||||
› Describe a new task
|
||||
|
||||
↑↓ navigate ctrl+o resume → open
|
||||
ctrl+n new task ctrl+f search ctrl+s group
|
||||
ctrl+r rename ctrl+x stop ctrl+w hide
|
||||
ctrl+e archive del delete esc back
|
||||
ctrl+n new task ctrl+f search
|
||||
ctrl+s group: project ctrl+r rename
|
||||
ctrl+x stop ctrl+w hide ctrl+e archive
|
||||
del delete esc back
|
||||
|
||||
@@ -12,11 +12,11 @@ expression: "render_bottom_popup(&app.chat_widget,\n96).replace(&format!(\"{proj
|
||||
│
|
||||
│ Last message
|
||||
│ Found the regression in the parser.
|
||||
│ Found the regression in the parser.
|
||||
│ …
|
||||
│
|
||||
│ Project
|
||||
│ /tmp/project
|
||||
│ Model: Unknown
|
||||
│
|
||||
│ Prompt
|
||||
│ Investigate parser
|
||||
|
||||
@@ -15,12 +15,12 @@ expression: "render_bottom_popup(&app.chat_widget,\n96).replace(&format!(\"{proj
|
||||
│
|
||||
│ Project
|
||||
│ /tmp/project
|
||||
│ Model: Unknown
|
||||
│
|
||||
│ Prompt
|
||||
│ No prompt available.
|
||||
│
|
||||
│
|
||||
│
|
||||
New task
|
||||
|
||||
› Keep this draft
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: tui/src/keymap_setup.rs
|
||||
assertion_line: 1039
|
||||
expression: agents
|
||||
---
|
||||
Resume | ctrl-o | Agents resume Resume Open the session resume picker. ctrl-o Default
|
||||
@@ -11,4 +10,4 @@ Stop | ctrl-x | Agents stop Stop Stop the selected running task. ctrl-x Default
|
||||
Archive | ctrl-e | Agents archive Archive Archive the selected task and its child agents. ctrl-e Default
|
||||
Delete | delete | Agents delete Delete Permanently delete the selected task and its child agents. delete Default
|
||||
Hide | ctrl-w | Agents hide Hide Hide the selected task until explicitly resumed. ctrl-w Default
|
||||
Toggle Grouping | ctrl-s | Agents toggle_grouping Toggle Grouping Group tasks by status or project. ctrl-s Default
|
||||
Toggle Grouping | ctrl-s | Agents toggle_grouping Toggle Grouping Cycle task grouping by project, status, or model. ctrl-s Default
|
||||
|
||||
Reference in New Issue
Block a user