Open the agents overview from an empty composer (#41917)

## What changed

- Open the agents overview with Left from an empty, focused composer in local-daemon sessions.
- Keep the shortcut inactive while editing text, using transient input surfaces, or when Left has been explicitly remapped.
- Show the contextual shortcut in the footer and shortcuts overlay.
- Leave the configurable `open_agents` action unbound by default instead of assigning `Alt+A`.

## Testing

- Add coverage for session eligibility, input-state guards, editor and Vim remaps, event dispatch, and footer rendering.

GitOrigin-RevId: ef17db3d9d09c3a08c3f7353c5b8a02f2b61c696
This commit is contained in:
Eric Traut
2026-08-31 21:53:02 +00:00
committed by copyberry
parent 907c34e867
commit 0a2e2bf957
23 changed files with 335 additions and 71 deletions
@@ -449,6 +449,10 @@ impl App {
chat_widget.last_terminal_title = previous_terminal_title;
}
chat_widget.remote_connection = self.chat_widget.remote_connection.clone();
chat_widget.set_agents_navigation_enabled(matches!(
self.app_server_target,
AppServerTarget::LocalDaemon { .. }
));
chat_widget.inherit_backend_banner_state(&mut self.chat_widget);
for (thread_id, entry) in self.agent_navigation.ordered_threads() {
chat_widget.set_collab_agent_metadata(
+4
View File
@@ -458,6 +458,10 @@ impl App {
};
chat_widget.note_rendered_width(tui.terminal.last_known_screen_size.width);
chat_widget.remote_connection = remote_connection;
chat_widget.set_agents_navigation_enabled(matches!(
app_server_target,
AppServerTarget::LocalDaemon { .. }
));
let thread_and_widget_ms = thread_and_widget_started_at.elapsed().as_millis();
chat_widget
.maybe_prompt_windows_sandbox_enable(should_prompt_windows_sandbox_nux_at_startup);
+2
View File
@@ -2,6 +2,8 @@
#[path = "tests/advanced_reasoning_tests.rs"]
mod advanced_reasoning_tests;
#[path = "tests/agents_navigation_tests.rs"]
mod agents_navigation_tests;
#[path = "tests/backend_banner_fallback_tests.rs"]
mod backend_banner_fallback_tests;
#[path = "tests/backend_banner_recovery_tests.rs"]
@@ -0,0 +1,46 @@
use super::*;
#[tokio::test]
async fn agents_navigation_requires_local_daemon() -> Result<()> {
let (mut app, mut events, _op_rx) = make_test_app_with_channels().await;
let mut tui = crate::tui::test_support::make_test_tui()?;
let mut app_server = start_config_write_test_app_server(&app).await?;
let endpoint = crate::RemoteAppServerEndpoint::UnixSocket {
socket_path: AbsolutePathBuf::relative_to_current_dir("codex.sock")?,
};
for target in [
AppServerTarget::Embedded,
AppServerTarget::Remote {
endpoint: endpoint.clone(),
},
AppServerTarget::LocalDaemon { endpoint },
] {
let enabled = matches!(target, AppServerTarget::LocalDaemon { .. });
app.app_server_target = target;
let init = app.chatwidget_init_for_forked_or_resumed_thread(
&mut tui,
app.config.clone(),
/*initial_user_message*/ None,
);
app.replace_chat_widget(ChatWidget::new_with_app_event(init));
while events.try_recv().is_ok() {}
app.handle_tui_event(
&mut tui,
&mut app_server,
TuiEvent::Key(KeyCode::Left.into()),
)
.await?;
if enabled {
let event = events.try_recv()?;
assert_matches!(event, AppEvent::OpenAgentsOverview);
app.handle_event(&mut tui, &mut app_server, event).await?;
assert!(!app.chat_widget.no_modal_or_popup_active());
} else {
assert!(events.try_recv().is_err());
assert!(app.chat_widget.no_modal_or_popup_active());
assert!(!render_bottom_popup(&app.chat_widget, /*width*/ 96).contains("for agents"));
}
}
app_server.shutdown().await?;
Ok(())
}
+24 -6
View File
@@ -17,6 +17,8 @@
//!
//! # Key Event Routing
//!
//! Plain Left opens agents when the local-daemon composer is empty and available for input.
//! Explicit editor remaps take precedence.
//! Most key handling goes through [`ChatComposer::handle_key_event`], which dispatches to a
//! popup-specific handler if a popup is visible and otherwise to
//! [`ChatComposer::handle_key_event_without_popup`]. After every handled key, we call
@@ -290,6 +292,7 @@ use codex_protocol::user_input::ByteRange;
use codex_protocol::user_input::MAX_USER_INPUT_TEXT_CHARS;
use codex_protocol::user_input::TextElement;
mod agents_navigation;
mod attachment_state;
mod completion_target;
mod draft_state;
@@ -494,6 +497,7 @@ pub(crate) struct ChatComposer {
popups: PopupState,
app_event_tx: AppEventSender,
history: ChatComposerHistory,
agents_navigation_enabled: bool,
footer: FooterState,
has_focus: bool,
frame_requester: Option<FrameRequester>,
@@ -634,6 +638,7 @@ impl ChatComposer {
popups: PopupState::default(),
app_event_tx,
history: ChatComposerHistory::new(),
agents_navigation_enabled: false,
footer: FooterState {
quit_shortcut_expires_at: None,
quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')),
@@ -2108,7 +2113,7 @@ impl ChatComposer {
/// Handle key events when file search popup is visible.
fn handle_key_event_with_file_popup(&mut self, key_event: KeyEvent) -> (InputResult, bool) {
if self.handle_shortcut_overlay_key(&key_event) {
if self.handle_empty_prompt_shortcut(&key_event) {
return (InputResult::None, true);
}
if key_event.code == KeyCode::Esc {
@@ -2194,7 +2199,7 @@ impl ChatComposer {
/// Handle key events when the legacy skill mention popup is visible.
fn handle_key_event_with_skill_popup(&mut self, key_event: KeyEvent) -> (InputResult, bool) {
if self.handle_shortcut_overlay_key(&key_event) {
if self.handle_empty_prompt_shortcut(&key_event) {
return (InputResult::None, true);
}
self.footer.mode = reset_mode_after_activity(self.footer.mode);
@@ -2276,7 +2281,7 @@ impl ChatComposer {
&mut self,
key_event: KeyEvent,
) -> (InputResult, bool) {
if self.handle_shortcut_overlay_key(&key_event) {
if self.handle_empty_prompt_shortcut(&key_event) {
return (InputResult::None, true);
}
self.footer.mode = reset_mode_after_activity(self.footer.mode);
@@ -3461,7 +3466,7 @@ impl ChatComposer {
if self.attachments.selected_remote_image_index.is_some() {
self.attachments.clear_remote_image_selection();
}
if self.handle_shortcut_overlay_key(&key_event) {
if self.handle_empty_prompt_shortcut(&key_event) {
return (InputResult::None, true);
}
if self.draft.is_bash_mode && key_event.code == KeyCode::Esc {
@@ -3808,17 +3813,23 @@ impl ChatComposer {
.remove_deleted_local_placeholders(&removed_payloads, &mut self.draft.textarea);
}
/// Handle the dedicated shortcut-overlay toggle key(s).
/// Handle empty-prompt agents navigation and the shortcut-overlay toggle.
///
/// This only toggles when the composer is empty and no paste burst is in
/// progress, so typing/pasting `?` still inserts text instead of opening
/// help. The bound key list intentionally supports terminal-variant
/// modifier reporting (for example `?` vs `shift-?`).
fn handle_shortcut_overlay_key(&mut self, key_event: &KeyEvent) -> bool {
fn handle_empty_prompt_shortcut(&mut self, key_event: &KeyEvent) -> bool {
if key_event.kind != KeyEventKind::Press {
return false;
}
if key_hint::plain(KeyCode::Left).is_press(*key_event) && self.agents_navigation_available()
{
self.app_event_tx.send(AppEvent::OpenAgentsOverview);
return true;
}
let toggles = self.toggle_shortcuts_keys.is_pressed(*key_event)
&& self.is_empty()
&& !self.is_in_paste_burst();
@@ -3862,6 +3873,9 @@ impl ChatComposer {
status_line_value: self.footer.status_line_value.clone(),
status_line_enabled: self.footer.status_line_enabled,
key_hints: FooterKeyHints {
agents: self
.agents_navigation_available()
.then_some(key_hint::plain(KeyCode::Left).into()),
toggle_shortcuts: self.footer.toggle_shortcuts_key,
queue: self.footer.queue_key,
insert_newline: self.footer.insert_newline_key,
@@ -4994,6 +5008,10 @@ impl ChatComposer {
}
}
#[cfg(test)]
#[path = "chat_composer/agents_navigation_tests.rs"]
mod agents_navigation_tests;
#[cfg(test)]
#[path = "chat_composer_effort_tests.rs"]
mod effort_tests;
@@ -0,0 +1,29 @@
//! Availability of the empty-prompt agents shortcut and its footer hint.
//!
//! Only local-daemon sessions enable this shortcut. Draft contents, transient input surfaces,
//! and explicit editor remaps take precedence over navigation.
use super::*;
impl ChatComposer {
pub(crate) fn set_agents_navigation_enabled(&mut self, enabled: bool) {
self.agents_navigation_enabled = enabled;
}
pub(super) fn agents_navigation_available(&self) -> bool {
let move_left = if self.draft.textarea.is_vim_normal_mode() {
&self.vim_normal_keymap.move_left
} else {
&self.editor_keymap.move_left
};
self.agents_navigation_enabled
&& move_left.is_pressed(KeyCode::Left.into())
&& self.has_focus
&& self.draft.input_enabled
&& self.slash_commands_enabled()
&& self.is_empty()
&& !self.is_in_paste_burst()
&& !self.popup_active()
&& !self.draft.textarea.is_vim_operator_pending()
}
}
@@ -0,0 +1,108 @@
use super::tests::new_test_composer;
use super::tests::snapshot_composer_state_with_width;
use super::*;
use assert_matches::assert_matches;
use pretty_assertions::assert_eq;
#[test]
fn left_navigates_only_from_an_empty_prompt() {
let (mut composer, mut events) = new_test_composer();
composer.set_agents_navigation_enabled(/*enabled*/ true);
composer.insert_str("ab");
composer.handle_key_event(KeyCode::Left.into());
composer.insert_str("!");
assert_eq!(composer.current_text(), "a!b");
assert!(events.try_recv().is_err());
composer.clear_for_ctrl_c();
for kind in [KeyEventKind::Repeat, KeyEventKind::Release] {
composer.handle_key_event(KeyEvent::new_with_kind(
KeyCode::Left,
KeyModifiers::NONE,
kind,
));
}
assert!(events.try_recv().is_err());
composer.handle_key_event(KeyCode::Left.into());
assert_matches!(events.try_recv(), Ok(AppEvent::OpenAgentsOverview));
}
#[test]
fn left_respects_attachments_pastes_and_other_input_surfaces() {
let setups: [fn(&mut ChatComposer); 4] = [
|composer| composer.config = ChatComposerConfig::plain_text(),
|composer| composer.set_remote_image_urls(vec!["https://example.com/image.png".into()]),
|composer| {
composer.handle_key_event(KeyCode::Char('x').into());
},
|composer| {
composer.handle_key_event(KeyEvent::new(KeyCode::Char('r'), KeyModifiers::CONTROL));
},
];
for setup in setups {
let (mut composer, mut events) = new_test_composer();
composer.set_agents_navigation_enabled(/*enabled*/ true);
setup(&mut composer);
composer.handle_key_event(KeyCode::Left.into());
assert!(
!std::iter::from_fn(|| events.try_recv().ok())
.any(|event| matches!(event, AppEvent::OpenAgentsOverview))
);
}
}
#[test]
fn agents_navigation_hint_snapshots() {
for name in [
"agents_navigation_enabled",
"agents_navigation_help",
"agents_navigation_status",
] {
snapshot_composer_state_with_width(
name,
/*width*/ 80,
/*enhanced_keys_supported*/ false,
|composer| {
composer.set_agents_navigation_enabled(/*enabled*/ true);
match name {
"agents_navigation_help" => {
composer.handle_key_event(KeyCode::Char('?').into());
}
"agents_navigation_status" => {
composer.set_status_line_enabled(/*enabled*/ true);
composer.set_status_line(Some(Line::from("model · project")));
}
_ => {}
}
},
);
}
}
#[test]
fn left_respects_editor_and_vim_history_remaps() {
use codex_config::types::KeybindingSpec;
use codex_config::types::KeybindingsSpec;
use codex_config::types::TuiKeymap;
for vim in [false, true] {
let (mut composer, mut events) = new_test_composer();
composer.set_agents_navigation_enabled(/*enabled*/ true);
composer.insert_str("previous");
composer.handle_key_event(KeyCode::Enter.into());
let mut config = TuiKeymap::default();
if vim {
config.vim_normal.move_left = Some(KeybindingsSpec::One(KeybindingSpec("h".into())));
config.vim_normal.move_up = Some(KeybindingsSpec::One(KeybindingSpec("left".into())));
} else {
config.editor.move_left = Some(KeybindingsSpec::One(KeybindingSpec("ctrl-b".into())));
config.editor.move_up = Some(KeybindingsSpec::One(KeybindingSpec("left".into())));
}
composer.set_keymap_bindings(&RuntimeKeymap::from_config(&config).unwrap());
composer.set_vim_enabled(vim);
assert!(composer.footer_props().key_hints.agents.is_none());
composer.handle_key_event(KeyCode::Left.into());
assert_eq!(composer.current_text(), "previous");
assert!(events.try_recv().is_err());
}
}
@@ -211,7 +211,7 @@ impl ChatComposer {
&mut self,
key_event: KeyEvent,
) -> (InputResult, bool) {
if self.handle_shortcut_overlay_key(&key_event) {
if self.handle_empty_prompt_shortcut(&key_event) {
return (InputResult::None, true);
}
if key_event.code == KeyCode::Esc {
+35 -1
View File
@@ -107,6 +107,7 @@ const FOOTER_CONTEXT_GAP_COLS: u16 = 1;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct FooterKeyHints {
pub(crate) agents: Option<ShortcutHint>,
pub(crate) toggle_shortcuts: Option<ShortcutHint>,
pub(crate) queue: Option<ShortcutHint>,
pub(crate) insert_newline: Option<ShortcutHint>,
@@ -122,6 +123,7 @@ impl FooterKeyHints {
#[cfg(test)]
pub(crate) fn default_bindings() -> Self {
Self {
agents: None,
toggle_shortcuts: Some(key_hint::plain(KeyCode::Char('?')).into()),
queue: Some(key_hint::plain(KeyCode::Tab).into()),
insert_newline: Some(key_hint::ctrl(KeyCode::Char('j')).into()),
@@ -313,6 +315,13 @@ fn left_side_line(
match state.hint {
SummaryHintKind::None => {}
SummaryHintKind::Shortcuts => {
if let Some(key) = key_hints.agents {
line.push_span(key);
line.push_span(" for agents".dim());
if key_hints.toggle_shortcuts.is_some() {
line.push_span(" · ".dim());
}
}
if let Some(key) = key_hints.toggle_shortcuts {
line.push_span(key);
line.push_span(" for shortcuts".dim());
@@ -384,6 +393,19 @@ pub(crate) fn single_line_footer_layout(
}
};
let state_width = |state: LeftSideState| -> u16 { state_line(state).width() as u16 };
if show_shortcuts_hint && key_hints.agents.is_some() {
let compact_line = left_side_line(
collaboration_mode_indicator,
default_state,
FooterKeyHints {
toggle_shortcuts: None,
..key_hints
},
);
if can_show_left_with_context(area, compact_line.width() as u16, context_width) {
return (SummaryLeft::Custom(compact_line), true);
}
}
// When the mode cycle hint is applicable (idle, non-queue mode), only show
// the right-side context indicator if the "(shift+tab to cycle)" variant
// can also fit.
@@ -788,6 +810,13 @@ pub(crate) fn passive_footer_status_line(props: &FooterProps) -> Option<Line<'st
}
}
if props.mode == FooterMode::ComposerEmpty
&& let Some(key) = props.key_hints.agents
&& let Some(line) = line.as_mut()
{
line.extend(vec![" · ".dim(), key.into(), " for agents".dim()]);
}
line
}
@@ -939,8 +968,13 @@ fn shortcut_overlay_lines(state: ShortcutsState) -> Vec<Line<'static>> {
ordered.push(change_mode);
}
ordered.push(show_transcript);
let mut lines = build_columns(ordered);
if let Some(key) = state.key_hints.agents {
lines.push(Line::from(vec![
key.into(),
" for agents (empty prompt)".into(),
]));
}
lines.push(Line::from(""));
lines.push(Line::from(vec![
"customize shortcuts with ".into(),
+5
View File
@@ -380,6 +380,11 @@ impl BottomPane {
self.request_redraw();
}
pub(crate) fn set_agents_navigation_enabled(&mut self, enabled: bool) {
self.composer.set_agents_navigation_enabled(enabled);
self.request_redraw();
}
pub(crate) fn set_task_mentions_enabled(&mut self, enabled: bool) {
self.composer.set_task_mentions_enabled(enabled);
self.request_redraw();
@@ -0,0 +1,13 @@
---
source: tui/src/bottom_pane/chat_composer.rs
expression: terminal.backend()
---
" "
"› Ask Codex to do anything "
" "
" "
" "
" "
" "
" "
" ← for agents · ? for shortcuts 100% context left "
@@ -0,0 +1,22 @@
---
source: tui/src/bottom_pane/chat_composer.rs
expression: terminal.backend()
---
" "
"› Ask Codex to do anything "
" "
" "
" "
" "
" "
" "
" / for commands ! for shell commands "
" ctrl + j for newline tab to submit message "
" @ for file paths ctrl + v to paste images "
" ctrl + g to edit in external editor esc esc to edit previous message "
" ctrl + r search history ctrl + c to exit "
" ⌥ + , reasoning down ⌥ + . reasoning up "
" ctrl + t to view transcript "
" ← for agents (empty prompt) "
" "
" customize shortcuts with /keymap "
@@ -0,0 +1,13 @@
---
source: tui/src/bottom_pane/chat_composer.rs
expression: terminal.backend()
---
" "
"› Ask Codex to do anything "
" "
" "
" "
" "
" "
" "
" model · project · ← for agents "
@@ -4,6 +4,10 @@ use super::*;
use crate::bottom_pane::BottomPaneView;
impl ChatWidget {
pub(crate) fn set_agents_navigation_enabled(&mut self, enabled: bool) {
self.bottom_pane.set_agents_navigation_enabled(enabled);
}
pub(crate) fn keymap_contexts(&self) -> crate::keymap::KeymapContextSet {
self.bottom_pane.keymap_contexts()
}
+6 -45
View File
@@ -599,25 +599,12 @@ impl RuntimeKeymap {
|| configured_context_alias_is_used(&keymap.list, alias)
|| configured_context_alias_is_used(&keymap.approval, alias)
});
let open_agents_default_is_shadowed = keymap.global.open_agents.is_none()
&& (configured_main_surface_alias_is_used(keymap, "alt-a")
|| configured_context_alias_is_used(&keymap.list, "alt-a")
|| configured_context_alias_is_used(&keymap.approval, "alt-a")
|| chords.bindings.iter().any(|binding| {
binding.action.context.overlaps(KeymapContext::Global)
&& binding.chord.prefix.parts() == key_hint::alt(KeyCode::Char('a')).parts()
}));
let app = AppKeymap {
open_agents: if open_agents_default_is_shadowed {
Vec::new()
} else {
resolve_bindings(
keymap.global.open_agents.as_ref(),
&defaults.app.open_agents,
"tui.keymap.global.open_agents",
)?
},
open_agents: resolve_bindings(
keymap.global.open_agents.as_ref(),
&defaults.app.open_agents,
"tui.keymap.global.open_agents",
)?,
open_transcript: resolve_bindings(
keymap.global.open_transcript.as_ref(),
&defaults.app.open_transcript,
@@ -1397,7 +1384,7 @@ impl RuntimeKeymap {
fn built_in_defaults() -> Self {
Self {
app: AppKeymap {
open_agents: default_bindings![alt(KeyCode::Char('a'))],
open_agents: default_bindings![],
open_transcript: default_bindings![ctrl(KeyCode::Char('t'))],
open_external_editor: default_bindings![ctrl(KeyCode::Char('g'))],
copy: default_bindings![ctrl(KeyCode::Char('o'))],
@@ -3314,32 +3301,6 @@ mod tests {
);
}
#[test]
fn agents_overview_default_yields_to_existing_custom_shortcuts() {
let mut keymap = TuiKeymap::default();
keymap.global.open_transcript = Some(one("alt-a"));
let runtime = RuntimeKeymap::from_config(&keymap).expect("existing keymap remains valid");
assert_eq!(
runtime.app.open_transcript,
vec![key_hint::alt(KeyCode::Char('a'))]
);
assert!(runtime.app.open_agents.is_empty());
keymap.global.open_transcript = Some(one("alt-a ctrl-t"));
let runtime = RuntimeKeymap::from_config(&keymap).expect("existing chord remains valid");
assert!(runtime.app.open_agents.is_empty());
keymap.global.open_transcript = None;
keymap.pager.scroll_up = Some(one("alt-a page-up"));
let runtime = RuntimeKeymap::from_config(&keymap).expect("pager chord remains valid");
assert_eq!(
runtime.app.open_agents,
vec![key_hint::alt(KeyCode::Char('a'))]
);
}
#[test]
fn toggle_side_conversation_can_be_remapped_and_rejects_conflicts() {
let mut keymap = TuiKeymap::default();
+1
View File
@@ -1071,6 +1071,7 @@ mod tests {
))
.collect::<Vec<_>>(),
vec![
("Open Agents", Some("unbound"), false),
("Toggle Vim Mode", Some("unbound"), false),
("Previous Permission Mode", Some("unbound"), false),
("Next Permission Mode", Some("unbound"), false),
@@ -2,7 +2,7 @@
source: tui/src/keymap_setup.rs
expression: snapshot
---
Open Agents | alt-a | Global open_agents Open Agents Open the shared agent-session overview. alt-a Default
Open Agents | unbound | Global open_agents Open Agents Open the shared agent-session overview. unbound Default
Open Transcript | ctrl-t | Global open_transcript Open Transcript Open the transcript overlay. ctrl-t Default
Open External Editor | ctrl-g | Global open_external_editor Open External Editor Open the current draft in an external editor. ctrl-g Default
Copy | ctrl-o | Global copy Copy Copy the last agent response to the clipboard. ctrl-o Default
@@ -5,12 +5,12 @@ expression: "render_picker(params, 120)"
Keymap
All configurable shortcuts.
135 actions, 1 customized, 4 unbound.
135 actions, 1 customized, 5 unbound.
[All] Common Customized (1) Unbound (4) App Composer Editor Vim Navigation Agents Approval Debug
[All] Common Customized (1) Unbound (5) App Composer Editor Vim Navigation Agents Approval Debug
Type to search shortcuts
› Global Open Agents alt-a
› Global - Open Agents unbound
Global Open Transcript ctrl-t
Global Open External Editor ctrl-g
Global Copy ctrl-o
@@ -5,12 +5,12 @@ expression: "render_picker(params, 120)"
Keymap
All configurable shortcuts.
136 actions, 0 customized, 5 unbound.
136 actions, 0 customized, 6 unbound.
[All] Common Customized (0) Unbound (5) App Composer Editor Vim Navigation Agents Approval Debug
[All] Common Customized (0) Unbound (6) App Composer Editor Vim Navigation Agents Approval Debug
Type to search shortcuts
› Global Open Agents alt-a
› Global - Open Agents unbound
Global Open Transcript ctrl-t
Global Open External Editor ctrl-g
Global Copy ctrl-o
@@ -5,7 +5,7 @@ expression: snapshot
tab: All (135 selectable)
tab: Common (20 selectable)
tab: Customized (0) (0 selectable)
tab: Unbound (4) (4 selectable)
tab: Unbound (5) (5 selectable)
tab: App (14 selectable)
tab: Composer (5 selectable)
tab: Editor (17 selectable)
@@ -14,7 +14,7 @@ tab: Navigation (20 selectable)
tab: Agents (5 selectable)
tab: Approval (8 selectable)
tab: Debug (1 selectable)
Open Agents | alt-a | Global open_agents Open Agents Open the shared agent-session overview. alt-a Default
Open Agents | unbound | Global open_agents Open Agents Open the shared agent-session overview. unbound Default
Open Transcript | ctrl-t | Global open_transcript Open Transcript Open the transcript overlay. ctrl-t Default
Open External Editor | ctrl-g | Global open_external_editor Open External Editor Open the current draft in an external editor. ctrl-g Default
Copy | ctrl-o | Global copy Copy Copy the last agent response to the clipboard. ctrl-o Default
@@ -5,12 +5,12 @@ expression: "render_picker(params, 120)"
Keymap
All configurable shortcuts.
135 actions, 1 customized, 4 unbound.
135 actions, 1 customized, 5 unbound.
[All] Common Customized (1) Unbound (4) App Composer Editor Vim Navigation Agents Approval Debug
[All] Common Customized (1) Unbound (5) App Composer Editor Vim Navigation Agents Approval Debug
Type to search shortcuts
› Global Open Agents alt-a
› Global - Open Agents unbound
Global * Open Transcript ctrl-t, ctrl-x ctrl-t
Global Open External Editor ctrl-g
Global Copy ctrl-o
@@ -5,13 +5,13 @@ expression: "render_picker(params, 78)"
Keymap
All configurable shortcuts.
135 actions, 0 customized, 4 unbound.
135 actions, 0 customized, 5 unbound.
[All] Common Customized (0) Unbound (4) App Composer Editor Vim
[All] Common Customized (0) Unbound (5) App Composer Editor Vim
Navigation Agents Approval Debug
Type to search shortcuts
› Global Open Agents alt-a
› Global - Open Agents unbound
Global Open Transcript ctrl-t
Global Open External Editor ctrl-g
Global Copy ctrl-o
@@ -5,12 +5,12 @@ expression: "render_picker(params, 120)"
Keymap
All configurable shortcuts.
135 actions, 0 customized, 4 unbound.
135 actions, 0 customized, 5 unbound.
[All] Common Customized (0) Unbound (4) App Composer Editor Vim Navigation Agents Approval Debug
[All] Common Customized (0) Unbound (5) App Composer Editor Vim Navigation Agents Approval Debug
Type to search shortcuts
› Global Open Agents alt-a
› Global - Open Agents unbound
Global Open Transcript ctrl-t
Global Open External Editor ctrl-g
Global Copy ctrl-o