Skip to content
Closed
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
13 changes: 12 additions & 1 deletion docs/next/website/src/data/config-reference.json
Original file line number Diff line number Diff line change
Expand Up @@ -1126,6 +1126,17 @@
"on",
"off"
]
},
{
"key": "ui.sound.agents.bob",
"type": "enum",
"default": "\"default\"",
"description": "Sound override for detected Bob Shell agents.",
"values": [
"default",
"on",
"off"
]
}
]
},
Expand Down Expand Up @@ -1209,7 +1220,7 @@
"key": "experimental.cjk_ime_agents",
"type": "list of strings",
"default": "[]",
"description": "Restrict `reveal_hidden_cursor_for_cjk_ime` to focused panes whose detected agent matches one of these names (case-insensitive). Empty list means apply to any focused pane. Unknown agent names are ignored; if the list contains no valid names, the reveal does not apply. Accepted names: pi, claude, codex, gemini, cursor, devin, cline, opencode, copilot, kimi, kiro, droid, amp, grok, hermes, kilo, qodercli, qoder, qwen, qwen-code, maki."
"description": "Restrict `reveal_hidden_cursor_for_cjk_ime` to focused panes whose detected agent matches one of these names (case-insensitive). Empty list means apply to any focused pane. Unknown agent names are ignored; if the list contains no valid names, the reveal does not apply. Accepted names: pi, claude, codex, gemini, cursor, devin, cline, opencode, copilot, kimi, kiro, droid, amp, grok, hermes, kilo, qodercli, qoder, qwen, qwen-code, maki, bob."
},
{
"key": "experimental.cjk_ime_cursor_shape",
Expand Down
2 changes: 1 addition & 1 deletion src/config/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -989,7 +989,7 @@ pub struct ExperimentalConfig {
/// if the list contains no valid names, the reveal does not apply.
/// Accepted names: pi, claude, codex, gemini, cursor, devin, cline,
/// opencode, copilot, kimi, kiro, droid, amp, grok, hermes, kilo,
/// qodercli, qoder, qwen, qwen-code, maki.
/// qodercli, qoder, qwen, qwen-code, maki, bob.
/// Default: empty.
pub cjk_ime_agents: Vec<String>,
/// Cursor shape rendered for the IME anchor when
Expand Down
1 change: 1 addition & 0 deletions src/config/sidebar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,7 @@ rows = [[{ token = "git_status", fg = "#ff00aa" }], [{ token = "$jj", bold = tru
Agent::Qodercli,
Agent::Qwen,
Agent::Maki,
Agent::Bob,
];
let entries = agents
.iter()
Expand Down
3 changes: 3 additions & 0 deletions src/config/sound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pub struct AgentSoundOverrides {
pub qodercli: AgentSoundSetting,
pub qwen: AgentSoundSetting,
pub maki: AgentSoundSetting,
pub bob: AgentSoundSetting,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize)]
Expand Down Expand Up @@ -142,6 +143,7 @@ impl AgentSoundOverrides {
Some(Agent::Qodercli) => self.qodercli,
Some(Agent::Qwen) => self.qwen,
Some(Agent::Maki) => self.maki,
Some(Agent::Bob) => self.bob,
None => AgentSoundSetting::Default,
}
}
Expand Down Expand Up @@ -182,6 +184,7 @@ impl Default for AgentSoundOverrides {
qodercli: AgentSoundSetting::Default,
qwen: AgentSoundSetting::Default,
maki: AgentSoundSetting::Default,
bob: AgentSoundSetting::Default,
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/detect/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ fn default_region() -> String {
const BUNDLED_MANIFESTS: &[(&str, &str)] = &[
("amp", include_str!("manifests/amp.toml")),
("agy", include_str!("manifests/antigravity.toml")),
("bob", include_str!("manifests/bob.toml")),
("claude", include_str!("manifests/claude.toml")),
("cline", include_str!("manifests/cline.toml")),
("codex", include_str!("manifests/codex.toml")),
Expand Down
129 changes: 129 additions & 0 deletions src/detect/manifests/bob.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
id = "bob"
version = "2026.08.14.5"
min_engine_version = 1
updated_at = "2026-08-14T00:00:00Z"
aliases = ["bobshell", "bob-shell"]

# Evidence: bobshell 2.0.1 (release 2026-08-13) source analysis and live UI inspection.
#
# Bob Shell (IBM) renders a React/Ink TUI with a chat-style interface.
# The executable is `bob` (from the `bobshell` npm package, bin: bob.js).
#
# Working state:
# When a task is running/streaming, the bottom of the chat shows a dots spinner
# followed by one of:
# "Processing… (Enter to steer, Tab to queue)" — normal task mode
# "Processing…" — editing queued message mode
# The spinner uses Ink's built-in "dots" type.
#
# Blocked — action approval dialogs (live evidence):
# Two variants share the same options/footer structure:
#
# Command execution:
# Title: "Execute Command"
# Section: "Approve commands:"
# Options: "→ Approve Once" / "Always Allow Command for task" / "Reject"
# Footer: "Press Enter to confirm"
# Extra: "Wait for input after execution: No (Tab to toggle)"
#
# Skill use:
# Title: "Use Skill <name>"
# Options: "→ Approve Once" / "Approve skill tools for task" / "Reject"
# Footer: "Press Enter to confirm"
# Extra: "Wait for input after execution: No (Tab to toggle)"
#
# Invariants across both: "approve once", "reject", "press enter to confirm"
#
# Blocked — tool use approval (older inline style):
# Bob presents tool approval options inline:
# "Allow once" "Always allow" "Reject" "Always reject"
# with footer "Enter to select · Esc to cancel"
#
# Blocked — folder trust prompt (on first run in a directory):
# Title: "Do you trust this folder?"
# Options: "Trust folder (name)", "Trust parent folder (name)", "Don't trust"
# Footer: "Press ESC or CTRL+C to exit"
#
# Blocked — task limit / cost limit error banners:
# "Task limit reached" with "Continue to double the limit"
# "Cost limit reached" with "Continue to double the limit"
#
# Blocked — content filter:
# "Request Blocked" shown in the chat
#
# Idle:
# No reliable single idle indicator exists beyond absence of working/blocked
# signals. The manifest relies on the working/blocked rules exhausting first
# and the known-agent fallback to idle.

[[rules]]
id = "processing_working"
state = "working"
priority = 880
region = "bottom_non_empty_lines(3)"
visible_working = true
contains = ["Processing…"]
not = [
{ contains = ["editing queued message"] },
]

[[rules]]
id = "processing_steer_hint_working"
state = "working"
priority = 860
region = "whole_recent"
visible_working = true
contains = ["Processing…", "Enter to steer"]

[[rules]]
id = "approval_dialog_blocked"
state = "blocked"
priority = 820
region = "whole_recent"
visible_blocker = true
contains = ["approve once", "press enter to confirm"]

[[rules]]
id = "tool_approval_footer_blocked"
state = "blocked"
priority = 800
region = "whole_recent"
visible_blocker = true
contains = ["enter to select"]
any = [
{ contains = ["allow once"] },
{ contains = ["always allow"] },
{ contains = ["reject"] },
]

[[rules]]
id = "trust_folder_blocked"
state = "blocked"
priority = 780
region = "whole_recent"
visible_blocker = true
contains = ["do you trust this folder?"]
any = [
{ contains = ["trust folder"] },
{ contains = ["don't trust"] },
]

[[rules]]
id = "task_limit_blocked"
state = "blocked"
priority = 750
region = "whole_recent"
visible_blocker = true
contains = ["continue to double the limit"]
any = [
{ contains = ["task limit reached"] },
{ contains = ["cost limit reached"] },
]

[[rules]]
id = "request_blocked"
state = "blocked"
priority = 700
region = "whole_recent"
visible_blocker = true
contains = ["request blocked", "content filter"]
16 changes: 14 additions & 2 deletions src/detect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,11 @@ pub enum Agent {
Qodercli,
Qwen,
Maki,
Bob,
}

impl Agent {
pub const ALL: [Self; 22] = [
pub const ALL: [Self; 23] = [
Self::Pi,
Self::Claude,
Self::Codex,
Expand All @@ -89,9 +90,10 @@ impl Agent {
Self::Qodercli,
Self::Qwen,
Self::Maki,
Self::Bob,
];

pub const SCREEN_MANIFEST_AGENTS: [Self; 20] = [
pub const SCREEN_MANIFEST_AGENTS: [Self; 21] = [
Self::Pi,
Self::Claude,
Self::Codex,
Expand All @@ -112,6 +114,7 @@ impl Agent {
Self::Qodercli,
Self::Qwen,
Self::Maki,
Self::Bob,
];
}

Expand Down Expand Up @@ -139,6 +142,7 @@ pub fn agent_label(agent: Agent) -> &'static str {
Agent::Qodercli => "qodercli",
Agent::Qwen => "qwen",
Agent::Maki => "maki",
Agent::Bob => "bob",
}
}

Expand Down Expand Up @@ -172,6 +176,7 @@ pub fn interactive_agent_executable(agent: Agent) -> &'static str {
Agent::Qodercli => "qodercli",
Agent::Qwen => "qwen",
Agent::Maki => "maki",
Agent::Bob => "bob",
}
}

Expand Down Expand Up @@ -209,6 +214,7 @@ fn lookup_agent(name: &str) -> Option<Agent> {
"qodercli" | "qoderclicn" | "qoder" | "qodercn" => Some(Agent::Qodercli),
"qwen" | "qwen-code" | "qwen code" => Some(Agent::Qwen),
"maki" => Some(Agent::Maki),
"bob" | "bobshell" | "bob-shell" => Some(Agent::Bob),
_ => None,
}
}
Expand Down Expand Up @@ -734,6 +740,8 @@ mod tests {
assert_eq!(identify_agent("qwen"), Some(Agent::Qwen));
assert_eq!(identify_agent("Qwen Code"), Some(Agent::Qwen));
assert_eq!(identify_agent("maki"), Some(Agent::Maki));
assert_eq!(identify_agent("bob"), Some(Agent::Bob));
assert_eq!(identify_agent("bobshell"), Some(Agent::Bob));
}

#[test]
Expand Down Expand Up @@ -761,6 +769,9 @@ mod tests {
assert_eq!(parse_agent_label("qwen-code"), Some(Agent::Qwen));
assert_eq!(parse_agent_label("maki"), Some(Agent::Maki));
assert_eq!(parse_agent_label("kilo-code"), Some(Agent::Kilo));
assert_eq!(parse_agent_label("bob"), Some(Agent::Bob));
assert_eq!(parse_agent_label("bobshell"), Some(Agent::Bob));
assert_eq!(parse_agent_label("bob-shell"), Some(Agent::Bob));
}

#[test]
Expand Down Expand Up @@ -804,6 +815,7 @@ mod tests {
(Agent::Qodercli, "qodercli"),
(Agent::Qwen, "qwen"),
(Agent::Maki, "maki"),
(Agent::Bob, "bob"),
];
assert_eq!(expected.len(), Agent::ALL.len());
for (agent, executable) in expected {
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ pane_history = false
# If the list contains no valid names, the reveal does not apply.
# Accepted: pi, claude, codex, gemini, cursor, devin, cline, opencode,
# copilot, kimi, kiro, droid, amp, grok, hermes, kilo, qodercli, qoder, qwen,
# qwen-code, maki.
# qwen-code, maki, bob.
# cjk_ime_agents = []
# Cursor shape rendered when reveal_hidden_cursor_for_cjk_ime is true.
# Values: block, steady_block (default), underline, steady_underline, bar, steady_bar.
Expand Down
Loading