Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app/assets/bundled/svg/kiro.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
59 changes: 58 additions & 1 deletion app/src/integration_testing/input/step.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ use crate::terminal::view::TerminalAction;

/// Opens the CLI-agent Rich Input for the terminal view at `tab_index`.
pub fn open_cli_agent_rich_input(tab_index: usize) -> TestStep {
open_cli_agent_rich_input_for_agent(tab_index, CLIAgent::Claude)
}

/// Opens the CLI-agent Rich Input for `agent` in the terminal view at `tab_index`.
pub fn open_cli_agent_rich_input_for_agent(tab_index: usize, agent: CLIAgent) -> TestStep {
new_step_with_default_assertions("Open CLI Agent Rich Input").with_action(
move |app, window_id, _step_data| {
let terminal_view = single_terminal_view_for_tab(app, window_id, tab_index);
Expand All @@ -31,7 +36,7 @@ pub fn open_cli_agent_rich_input(tab_index: usize) -> TestStep {
sessions.set_session(
view_id,
CLIAgentSession {
agent: CLIAgent::Claude,
agent,
status: CLIAgentSessionStatus::InProgress,
session_context: CLIAgentSessionContext::default(),
input_state: CLIAgentInputState::Closed,
Expand Down Expand Up @@ -65,6 +70,58 @@ pub fn open_cli_agent_rich_input(tab_index: usize) -> TestStep {
)
}

/// Asserts that the CLI-agent Rich Input is open for `tab_index`.
pub fn cli_agent_rich_input_is_open(tab_index: usize) -> warpui::integration::AssertionCallback {
Box::new(move |app, window_id| {
let terminal_view = single_terminal_view_for_tab(app, window_id, tab_index);
terminal_view.read(app, |view, ctx| {
let is_open = CLIAgentSessionsModel::as_ref(ctx)
.session(view.view_id())
.is_some_and(|session| {
matches!(session.input_state, CLIAgentInputState::Open { .. })
});
warpui::async_assert!(
is_open,
"Expected CLI-agent Rich Input to be open for tab {tab_index}"
)
})
})
}

/// Asserts that the active CLI agent for `tab_index` matches `expected_agent`.
pub fn active_cli_agent_is(
tab_index: usize,
expected_agent: CLIAgent,
) -> warpui::integration::AssertionCallback {
Box::new(move |app, window_id| {
let terminal_view = single_terminal_view_for_tab(app, window_id, tab_index);
terminal_view.read(app, |view, ctx| {
let active_agent = CLIAgentSessionsModel::as_ref(ctx)
.session(view.view_id())
.map(|session| session.agent);
warpui::async_assert!(
active_agent == Some(expected_agent),
"Expected active CLI agent {:?}; got {:?}",
expected_agent,
active_agent
)
})
})
}

/// Asserts that the Rich Input editor for `tab_index` has placeholder text configured.
pub fn rich_input_placeholder_exists(tab_index: usize) -> warpui::integration::AssertionCallback {
Box::new(move |app, window_id| {
let input_view = single_input_view_for_tab(app, window_id, tab_index);
input_view.read(app, |view, ctx| {
warpui::async_assert!(
view.editor().as_ref(ctx).placeholder_text_exists(),
"Expected Rich Input placeholder text to exist for tab {tab_index}"
)
})
})
}

/// Asserts that the Rich Input buffer text for `tab_index` is empty.
pub fn rich_input_buffer_text_is_empty(tab_index: usize) -> warpui::integration::AssertionCallback {
Box::new(move |app, window_id| {
Expand Down
1 change: 1 addition & 0 deletions app/src/server/telemetry/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,7 @@ pub enum CLIAgentType {
Claude,
Gemini,
Codex,
Kiro,
Amp,
Droid,
OpenCode,
Expand Down
20 changes: 18 additions & 2 deletions app/src/terminal/cli_agent.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! CLI agent detection and configuration.
//!
//! This module provides types for detecting and working with CLI-based AI agents
//! like Claude Code, Gemini CLI, Codex, Amp, and Droid.
//! like Claude Code, Gemini CLI, Codex, Kiro, Amp, and Droid.

use std::borrow::Cow;
use std::collections::HashMap;
Expand Down Expand Up @@ -47,6 +47,14 @@ pub(crate) const OPENAI_COLOR: ColorU = ColorU {
a: 255,
};

/// Kiro brand purple (from the official Kiro app icon gradient)
const KIRO_PURPLE: ColorU = ColorU {
r: 192,
g: 156,
b: 255,
a: 255,
};

/// Amp brand color (#F34E3F)
const AMP_COLOR: ColorU = ColorU {
r: 243,
Expand Down Expand Up @@ -135,12 +143,13 @@ const MISTRAL_ORANGE: ColorU = ColorU {
a: 255,
};

/// Represents a CLI agent (e.g., Claude Code, Gemini CLI, Codex, Amp, Droid, OpenCode, Copilot, Pi, Auggie, Cursor, Goose, Hermes, Mistral Vibe)
/// Represents a CLI agent (e.g., Claude Code, Gemini CLI, Codex, Kiro, Amp, Droid, OpenCode, Copilot, Pi, Auggie, Cursor, Goose, Hermes, Mistral Vibe)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Sequence, Serialize, Deserialize)]
pub enum CLIAgent {
Claude,
Gemini,
Codex,
Kiro,
Amp,
Droid,
OpenCode,
Expand All @@ -166,6 +175,7 @@ impl CLIAgent {
CLIAgent::Claude => &["claude"],
CLIAgent::Gemini => &["gemini"],
CLIAgent::Codex => &["codex"],
CLIAgent::Kiro => &["kiro-cli", "kiro", "kiro-cli-chat", "kiro-cli-term"],
CLIAgent::Amp => &["amp"],
CLIAgent::Droid => &["droid"],
CLIAgent::OpenCode => &["opencode"],
Expand Down Expand Up @@ -229,6 +239,7 @@ impl CLIAgent {
CLIAgent::Claude => "Claude Code",
CLIAgent::Gemini => "Gemini",
CLIAgent::Codex => "Codex",
CLIAgent::Kiro => "Kiro",
CLIAgent::Amp => "Amp",
CLIAgent::Droid => "Droid",
CLIAgent::OpenCode => "OpenCode",
Expand All @@ -252,6 +263,7 @@ impl CLIAgent {
CLIAgent::Claude => Some(Icon::ClaudeLogo),
CLIAgent::Gemini => Some(Icon::GeminiLogo),
CLIAgent::Codex => Some(Icon::OpenAILogo),
CLIAgent::Kiro => Some(Icon::KiroLogo),
CLIAgent::Amp => Some(Icon::AmpLogo),
CLIAgent::Droid => Some(Icon::DroidLogo),
CLIAgent::OpenCode => Some(Icon::OpenCodeLogo),
Expand Down Expand Up @@ -283,6 +295,7 @@ impl CLIAgent {
SkillProvider::Claude,
SkillProvider::Codex,
],
CLIAgent::Kiro => &[SkillProvider::Kiro],
CLIAgent::OpenCode => &[
SkillProvider::OpenCode,
SkillProvider::Agents,
Expand Down Expand Up @@ -338,6 +351,7 @@ impl CLIAgent {
CLIAgent::Claude => Some(CLAUDE_ORANGE),
CLIAgent::Gemini => Some(GEMINI_BLUE),
CLIAgent::Codex => Some(OPENAI_COLOR),
CLIAgent::Kiro => Some(KIRO_PURPLE),
CLIAgent::Amp => Some(AMP_COLOR),
CLIAgent::Droid => Some(DROID_COLOR),
CLIAgent::OpenCode => Some(OPENCODE_COLOR),
Expand All @@ -362,6 +376,7 @@ impl CLIAgent {
CLIAgent::Pi
| CLIAgent::OhMyPi
| CLIAgent::Auggie
| CLIAgent::Kiro
| CLIAgent::Droid
| CLIAgent::Antigravity => ColorU::new(0, 0, 0, 255),
_ => ColorU::white(),
Expand Down Expand Up @@ -614,6 +629,7 @@ impl From<CLIAgent> for CLIAgentType {
CLIAgent::Claude => CLIAgentType::Claude,
CLIAgent::Gemini => CLIAgentType::Gemini,
CLIAgent::Codex => CLIAgentType::Codex,
CLIAgent::Kiro => CLIAgentType::Kiro,
CLIAgent::Amp => CLIAgentType::Amp,
CLIAgent::Droid => CLIAgentType::Droid,
CLIAgent::OpenCode => CLIAgentType::OpenCode,
Expand Down
1 change: 1 addition & 0 deletions app/src/terminal/cli_agent_sessions/listener/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ fn create_handler(agent: &CLIAgent) -> Option<Box<dyn CLIAgentSessionHandler>> {
| CLIAgent::WarpTui => Some(Box::new(DefaultSessionListener)),
CLIAgent::Codex => Some(Box::new(CodexSessionHandler)),
CLIAgent::Hermes
| CLIAgent::Kiro
| CLIAgent::Amp
| CLIAgent::Copilot
| CLIAgent::CursorCli
Expand Down
1 change: 1 addition & 0 deletions app/src/terminal/cli_agent_sessions/plugin_manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ pub(crate) fn plugin_manager_for_with_shell(
CLIAgent::OpenCode
| CLIAgent::Codex
| CLIAgent::Gemini
| CLIAgent::Kiro
| CLIAgent::Amp
| CLIAgent::Droid
| CLIAgent::Copilot
Expand Down
31 changes: 31 additions & 0 deletions app/src/terminal/cli_agent_tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::collections::HashMap;
use std::sync::Arc;

use ai::skills::SkillProvider;
use chrono::Local;
use pathfinder_color::ColorU;
use smol_str::SmolStr;
Expand Down Expand Up @@ -259,6 +260,10 @@ fn test_detect_known_agents() {
("claude", CLIAgent::Claude),
("gemini", CLIAgent::Gemini),
("codex", CLIAgent::Codex),
("kiro", CLIAgent::Kiro),
("kiro-cli", CLIAgent::Kiro),
("kiro-cli-chat", CLIAgent::Kiro),
("kiro-cli-term", CLIAgent::Kiro),
("amp", CLIAgent::Amp),
("droid", CLIAgent::Droid),
("opencode", CLIAgent::OpenCode),
Expand Down Expand Up @@ -580,6 +585,32 @@ fn test_oh_my_pi_supports_bash_mode() {
assert!(CLIAgent::OhMyPi.supports_bash_mode());
}

#[test]
fn test_kiro_variant_properties() {
assert_eq!(CLIAgent::Kiro.command_prefix(), "kiro-cli");
assert_eq!(
CLIAgent::Kiro.command_prefixes(),
&["kiro-cli", "kiro", "kiro-cli-chat", "kiro-cli-term"]
);
assert_eq!(CLIAgent::Kiro.display_name(), "Kiro");
assert_eq!(
CLIAgent::Kiro.brand_color(),
Some(ColorU::new(192, 156, 255, 255))
);
assert_eq!(CLIAgent::Kiro.icon(), Some(Icon::KiroLogo));
assert_eq!(CLIAgent::Kiro.brand_icon_color(), ColorU::black());
assert_eq!(
CLIAgent::Kiro.supported_skill_providers(),
&[SkillProvider::Kiro]
);
assert!(!CLIAgent::Kiro.supports_bash_mode());
assert!(CLIAgent::Kiro.supports_cli_agent_footer());
assert!(matches!(
crate::server::telemetry::CLIAgentType::from(CLIAgent::Kiro),
crate::server::telemetry::CLIAgentType::Kiro
));
}

#[test]
fn test_warp_tui_matches_binaries_and_launchers() {
// Direct binary names.
Expand Down
1 change: 1 addition & 0 deletions app/src/terminal/view/use_agent_footer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ fn rich_input_submit_strategy(agent: CLIAgent) -> RichInputSubmitStrategy {
CLIAgent::OhMyPi => RichInputSubmitStrategy::BracketedPaste,
CLIAgent::Copilot => RichInputSubmitStrategy::BracketedPasteDelayedEnter,
CLIAgent::Claude
| CLIAgent::Kiro
| CLIAgent::OpenCode
| CLIAgent::Gemini
| CLIAgent::Auggie
Expand Down
1 change: 1 addition & 0 deletions app/src/terminal/view_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6869,6 +6869,7 @@ fn cli_agent_rich_input_hint_text_mentions_active_cli_agent() {
(CLIAgent::Claude, "Enter prompt for Claude Code..."),
(CLIAgent::Gemini, "Enter prompt for Gemini..."),
(CLIAgent::Codex, "Enter prompt for Codex..."),
(CLIAgent::Kiro, "Enter prompt for Kiro..."),
(CLIAgent::Unknown, "Tell the agent what to build..."),
] {
let terminal = open_cli_agent_rich_input_for_agent(&mut app, agent);
Expand Down
4 changes: 2 additions & 2 deletions crates/ai/src/skills/skill_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,12 @@ impl SkillProvider {
SkillProvider::Gemini => Icon::GeminiLogo,
SkillProvider::Droid => Icon::DroidLogo,
SkillProvider::OpenCode => Icon::OpenCodeLogo,
SkillProvider::Kiro => Icon::KiroLogo,
SkillProvider::Warp
| SkillProvider::Agents
| SkillProvider::Cursor
| SkillProvider::Copilot
| SkillProvider::Github
| SkillProvider::Kiro => Icon::WarpLogoLight,
| SkillProvider::Github => Icon::WarpLogoLight,
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/integration/src/bin/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ fn register_tests() -> HashMap<&'static str, BoxedBuilderFn> {
register_test!(test_typeahead);
register_test!(test_input_reporting_posix_shells);
register_test!(test_input_reporting_powershell);
register_test!(test_kiro_cli_rich_input_shows_kiro_branding);
register_test!(test_background_output);
register_test!(test_home_key_should_not_appear_in_input);
register_test!(test_change_font_size);
Expand Down
2 changes: 2 additions & 0 deletions crates/integration/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mod goto_line;
mod history;
mod input;
mod keyboard_protocol;
mod kiro_cli;
mod launch_configs;
mod notebooks;
mod osc8_hyperlinks;
Expand Down Expand Up @@ -61,6 +62,7 @@ pub use goto_line::*;
pub use history::*;
pub use input::*;
pub use keyboard_protocol::*;
pub use kiro_cli::*;
pub use launch_configs::*;
pub use notebooks::*;
pub use osc8_hyperlinks::*;
Expand Down
82 changes: 82 additions & 0 deletions crates/integration/src/test/kiro_cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use std::time::Duration;

use settings::Setting as _;
use warp::features::FeatureFlag;
use warp::integration_testing::input::{
active_cli_agent_is, cli_agent_rich_input_is_open, open_cli_agent_rich_input_for_agent,
rich_input_placeholder_exists,
};
use warp::integration_testing::step::new_step_with_default_assertions;
use warp::integration_testing::terminal::{
clear_blocklist_to_remove_bootstrapped_blocks, wait_until_bootstrapped_single_pane_for_tab,
};
use warp::integration_testing::view_getters::workspace_view;
use warp::terminal::CLIAgent;
use warp::workspace::WorkspaceAction;
use warp::workspace::tab_settings::{TabSettings, VerticalTabsDisplayGranularity};
use warpui_core::integration::TestStep;
use warpui_core::{SingletonEntity, TypedActionView};

use super::new_builder;
use crate::Builder;

pub fn test_kiro_cli_rich_input_shows_kiro_branding() -> Builder {
FeatureFlag::CLIAgentRichInput.set_enabled(true);
FeatureFlag::VerticalTabs.set_enabled(true);

new_builder()
.with_real_display()
.with_step(wait_until_bootstrapped_single_pane_for_tab(0))
.with_step(clear_blocklist_to_remove_bootstrapped_blocks())
.with_step(open_cli_agent_rich_input_for_agent(0, CLIAgent::Kiro))
.with_step(
TestStep::new("Assert Kiro Rich Input state and capture screenshot")
.set_timeout(Duration::from_secs(20))
.set_post_step_pause(Duration::from_secs(2))
.with_take_screenshot("kiro_cli_rich_input.png")
.add_assertion(cli_agent_rich_input_is_open(0))
.add_assertion(active_cli_agent_is(0, CLIAgent::Kiro))
.add_assertion(rich_input_placeholder_exists(0)),
)
.with_step(
new_step_with_default_assertions("Enable and open vertical tabs").with_action(
|app, window_id, _| {
TabSettings::handle(app).update(app, |settings, ctx| {
settings
.use_vertical_tabs
.set_value(true, ctx)
.expect("vertical tabs setting should update");
settings
.vertical_tabs_display_granularity
.set_value(VerticalTabsDisplayGranularity::Panes, ctx)
.expect("vertical tabs display granularity should update");
});

let workspace = workspace_view(app, window_id);
workspace.update(app, |workspace, ctx| {
workspace.handle_action(&WorkspaceAction::OpenVerticalTabsPanel, ctx);
});
},
),
)
.with_step(
TestStep::new("Assert Kiro vertical tab branding and capture screenshot")
.set_timeout(Duration::from_secs(20))
.set_post_step_pause(Duration::from_secs(2))
.with_take_screenshot("kiro_cli_vertical_tab.png")
.add_assertion(|app, window_id| {
let presenter = app.presenter(window_id).expect("presenter should exist");
let panel_is_rendered = presenter
.borrow()
.position_cache()
.get_position("workspace_view:vertical_tabs_panel")
.is_some();
warpui_core::async_assert!(
panel_is_rendered,
"Expected vertical tabs panel to be rendered"
)
})
.add_assertion(cli_agent_rich_input_is_open(0))
.add_assertion(active_cli_agent_is(0, CLIAgent::Kiro)),
)
}
Loading