use super::*;
use crate::agent_navigation::AgentNavigation;
use crate::chat::InputRoutingState;
use crate::event_renderer::selection_intent::{EmptyUiTarget, SelectionIntent, UiTarget};
#[test]
fn agent_trace_help_shows_compact_toon_lite_defaults() {
let command = path_super_cli::Cli::command();
let mut trace = command
.find_subcommand("agent")
.and_then(|agent| agent.find_subcommand("trace"))
.expect("agent trace command")
.clone();
let help = trace.render_long_help().to_string();
assert!(help.contains("Project a validated durable agent snapshot"));
assert!(help.contains("[default: agent-tools-toon]"));
assert!(help.contains("[default: lite]"));
assert!(help.contains("at most 4 KiB of each text/output item"));
assert!(help.contains("complete semantic text/normalized output"));
}
#[test]
fn role_cycling_only_enabled_in_explicit_creation_mode() {
let current_agent_state = Arc::new(Mutex::new(SelectionIntent::default()));
assert!(!role_cycling_enabled(¤t_agent_state));
current_agent_state
.lock()
.expect("current agent")
.set_target(UiTarget::Viewing(agent_id("engineer_abc12345")));
assert!(!role_cycling_enabled(¤t_agent_state));
current_agent_state
.lock()
.expect("current agent")
.set_target(UiTarget::Creating);
assert!(role_cycling_enabled(¤t_agent_state));
}
#[test]
fn effort_completion_tracks_new_agent_state() {
let (_term, handle, _vt) = setup(80, 24);
let completion_data = tau_cli_term::CompletionData::new();
let mut renderer = EventRenderer::new(handle, completion_data.clone(), cli_test_theme());
renderer.handle(&Event::HarnessRolesAvailable(HarnessRolesAvailable {
roles: vec![HarnessRoleInfo {
name: "engineer".to_owned(),
description: String::new(),
role_description: None,
details: None,
}],
groups: Vec::new(),
custom_prompts: Vec::new(),
}));
let candidates = |buffer: &str| {
tau_cli_term::completion::build_candidates(&[], &completion_data, buffer, buffer.len())
};
assert!(candidates(":eff").is_empty());
renderer.set_harness_protocol_version(Some(tau_proto::ProtocolVersion::new(7, 1)));
assert!(candidates(":eff").is_empty());
renderer.set_harness_protocol_version(Some(tau_proto::ProtocolVersion::new(7, 2)));
let intent = renderer.current_agent_state();
let epoch = intent
.lock()
.expect("selection intent")
.set_target(UiTarget::Creating);
renderer.apply_claimed_empty_target(epoch, EmptyUiTarget::Creating);
assert_eq!(candidates(":eff")[0].replacement, ":effort");
let effort_values = candidates(":effort ");
assert_eq!(
effort_values
.iter()
.map(|candidate| (candidate.label.as_str(), candidate.description.as_str()))
.collect::<Vec<_>>(),
vec![
("reset", "clear this agent effort override"),
(
"provider_default",
"omit effort and use the provider default"
),
("disabled", "request disabled reasoning"),
("0.0", "minimum portable reasoning intensity"),
("0.25", "light portable reasoning intensity"),
("0.5", "medium-like portable reasoning intensity"),
("0.75", "strong portable reasoning intensity"),
("1.0", "maximum portable reasoning intensity"),
]
);
let role_effort_values = tau_cli_term::completion::build_candidates(
&[tau_cli_term::CommandCompletion::new(
":role",
"configure role",
)],
&completion_data,
":role engineer effort ",
":role engineer effort ".len(),
);
for candidate in effort_values
.iter()
.filter(|candidate| candidate.label != "reset")
{
let role_candidate = role_effort_values
.iter()
.find(|role_candidate| role_candidate.label == candidate.label)
.expect("agent effort suggestion must exist in role effort suggestions");
assert_eq!(
candidate.description, role_candidate.description,
"{} must use the role-effort description",
candidate.label
);
}
renderer.switch_agent(agent_id("existing"));
assert_eq!(candidates(":eff")[0].replacement, ":effort");
renderer.clear_selected_agent();
assert!(candidates(":eff").is_empty());
}
#[test]
fn attach_boundary_selects_only_from_unclaimed_empty_state() {
let (_term, handle, _vt) = setup(100, 24);
let mut renderer = marker_test_renderer(handle);
let selected = renderer.current_agent_state();
let boundary = Event::SessionReplayComplete(tau_proto::SessionReplayComplete {
session_id: test_session_id("s1"),
error: Some("legacy agent journal is incompatible".to_owned()),
});
renderer.handle_attach_agent_selection_socket_delivery(
&boundary,
&agent_id("restored-agent"),
tau_proto::UnixMicros::new(1),
tau_cli_term::RendererDeliveryId::new(1),
);
assert_eq!(
selected.lock().expect("selected agent").as_ref(),
Some(&agent_id("restored-agent"))
);
assert_eq!(selected.lock().expect("selected agent").epoch(), 1);
renderer.handle_attach_agent_selection_socket_delivery(
&boundary,
&agent_id("other-agent"),
tau_proto::UnixMicros::new(2),
tau_cli_term::RendererDeliveryId::new(2),
);
assert_eq!(
selected.lock().expect("selected agent").as_ref(),
Some(&agent_id("restored-agent"))
);
}
#[test]
fn attach_boundary_cas_preserves_newer_local_intent() {
let (_term, handle, _vt) = setup(100, 24);
let mut renderer = marker_test_renderer(handle);
let selected = renderer.current_agent_state();
let routing = InputRoutingState::new(
selected.clone(),
Arc::new(Mutex::new(Vec::new())),
Arc::new(Mutex::new(AgentNavigation::default())),
Arc::new(Mutex::new(HashSet::new())),
);
let boundary = Event::SessionReplayComplete(tau_proto::SessionReplayComplete {
session_id: test_session_id("s1"),
error: None,
});
let stale_epoch = routing.set_selected_agent(Some(agent_id("local-agent")));
renderer.handle_attach_agent_selection_socket_delivery(
&boundary,
&agent_id("restored-agent"),
tau_proto::UnixMicros::new(1),
tau_cli_term::RendererDeliveryId::new(1),
);
assert_eq!(
routing.selected_agent_id().as_deref(),
Some("local-agent"),
"already-admitted replay must not retarget immediate prompt routing"
);
renderer.apply_claimed_agent(agent_id("stale-renderer-agent"), stale_epoch);
assert_eq!(
routing.selected_agent_id().as_deref(),
Some("local-agent"),
"display-only renderer commands must not write into input authority"
);
routing.set_selected_agent(None);
renderer.handle_attach_agent_selection_socket_delivery(
&boundary,
&agent_id("other-restored-agent"),
tau_proto::UnixMicros::new(2),
tau_cli_term::RendererDeliveryId::new(2),
);
let intent = selected.lock().expect("selected agent");
assert!(intent.selected_agent_id().is_none());
assert_eq!(intent.epoch(), 2);
}
#[test]
fn admitted_attach_selection_cannot_render_after_newer_local_intent() {
let (_term, handle, _vt) = setup(100, 24);
let mut renderer = marker_test_renderer(handle);
let selected = renderer.current_agent_state();
let routing = InputRoutingState::new(
selected.clone(),
Arc::new(Mutex::new(Vec::new())),
Arc::new(Mutex::new(AgentNavigation::default())),
Arc::new(Mutex::new(HashSet::new())),
);
let restored = agent_id("restored-agent");
let admitted_epoch = {
let mut intent = selected.lock().expect("selection intent");
*intent = SelectionIntent::test_viewing(1, restored.clone());
intent.epoch()
};
routing.set_selected_agent(Some(agent_id("local-agent")));
renderer.apply_claimed_agent(restored, admitted_epoch);
assert_eq!(routing.selected_agent_id().as_deref(), Some("local-agent"));
assert_eq!(renderer.displayed_agent_id_for_test(), None);
}
#[test]
fn admitted_replay_before_local_display_preserves_input_intent() {
let (_term, handle, _vt) = setup(100, 24);
let mut renderer = marker_test_renderer(handle);
let selected = renderer.current_agent_state();
let routing = InputRoutingState::new(
selected,
Arc::new(Mutex::new(Vec::new())),
Arc::new(Mutex::new(AgentNavigation::default())),
Arc::new(Mutex::new(HashSet::new())),
);
let local = agent_id("local-agent");
let replay = Event::UiPromptSubmitted(tau_proto::UiPromptSubmitted {
literal: false,
session_id: test_session_id("s1"),
text: "retained replay".into(),
agent_id: agent_id("replay-agent"),
message_class: tau_proto::PromptMessageClass::User,
originator: tau_proto::PromptOriginator::User,
ctx_id: None,
});
let local_epoch = routing.set_selected_agent(Some(local.clone()));
renderer.handle_replay_socket_delivery(
&replay,
tau_proto::UnixMicros::new(1),
tau_cli_term::RendererDeliveryId::new(1),
);
assert_eq!(routing.selected_agent_id().as_ref(), Some(&local));
assert_eq!(
renderer.displayed_agent_id_for_test(),
None,
"replay must wait for the queued local display command"
);
renderer.apply_claimed_agent(local.clone(), local_epoch);
assert_eq!(renderer.displayed_agent_id_for_test(), Some(&local));
let (_term, handle, _vt) = setup(100, 24);
let mut renderer = marker_test_renderer(handle);
let selected = renderer.current_agent_state();
let routing = InputRoutingState::new(
selected,
Arc::new(Mutex::new(Vec::new())),
Arc::new(Mutex::new(AgentNavigation::default())),
Arc::new(Mutex::new(HashSet::new())),
);
routing.set_selected_agent(None);
renderer.handle_replay_socket_delivery(
&replay,
tau_proto::UnixMicros::new(2),
tau_cli_term::RendererDeliveryId::new(2),
);
assert!(
routing.selected_agent_id().is_none() && renderer.displayed_agent_id_for_test().is_none(),
"explicit clearing must also prevent replay-derived selection"
);
}
#[test]
fn agent_switching_cycles_active_agents_and_skips_suspended() {
let known_agents = vec!["alpha".to_owned(), "bravo".to_owned(), "charlie".to_owned()];
let active_agents = HashSet::from([agent_id("alpha"), agent_id("charlie")]);
assert_eq!(
next_agent_cycle_selection(Some("alpha"), &known_agents, &active_agents, 1).as_deref(),
Some("charlie")
);
assert_eq!(
next_agent_cycle_selection(Some("alpha"), &known_agents, &active_agents, -1),
Some("charlie".to_owned())
);
assert_eq!(
next_agent_cycle_selection(Some("charlie"), &known_agents, &active_agents, 1),
Some("alpha".to_owned())
);
}
#[test]
fn agent_switching_without_selection_starts_at_edge_for_direction() {
let known_agents = vec!["alpha".to_owned(), "bravo".to_owned()];
let active_agents = HashSet::from([agent_id("alpha"), agent_id("bravo")]);
assert_eq!(
next_agent_cycle_selection(None, &known_agents, &active_agents, 1).as_deref(),
Some("alpha")
);
assert_eq!(
next_agent_cycle_selection(None, &known_agents, &active_agents, -1).as_deref(),
Some("bravo")
);
}
#[test]
fn agent_switching_without_active_agents_stays_on_overview() {
let known_agents = vec!["suspended".to_owned()];
let active_agents = HashSet::<tau_proto::AgentId>::new();
assert_eq!(
next_agent_cycle_selection(None, &known_agents, &active_agents, 1),
None
);
assert_eq!(
next_agent_cycle_selection(None, &known_agents, &active_agents, -1),
None
);
}
#[test]
fn first_agent_event_does_not_force_full_redraw() {
let (_term, handle, _vt) = setup(80, 24);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.handle(&Event::SessionStarted(tau_proto::SessionStarted {
session_id: test_session_id("s1"),
reason: tau_proto::SessionStartReason::Initial,
}));
let initial_render_count = handle.full_render_count();
renderer.handle(&Event::AgentStarted(tau_proto::AgentStarted {
creator: Some(tau_proto::AgentCreator::default()),
parent_agent: None,
agent_id: agent_id("engineer_abc12345"),
role: "engineer".to_owned(),
display_name: None,
metadata: Vec::new(),
ephemeral: false,
}));
renderer.handle(&Event::AgentPromptCreated(AgentPromptCreated {
agent_id: agent_id("engineer_abc12345"),
..agent_prompt_created("sp1", "s1")
}));
sync(&handle);
assert_eq!(handle.full_render_count(), initial_render_count);
}
#[test]
fn selecting_same_agent_does_not_force_full_redraw() {
let (_term, handle, _vt) = setup(80, 24);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.switch_agent(agent_id("worker-1"));
sync(&handle);
let full_render_count = handle.full_render_count();
renderer.switch_agent(agent_id("worker-1"));
sync(&handle);
assert_eq!(handle.full_render_count(), full_render_count);
}
#[test]
fn clear_selection_first_frame_has_new_agent_placeholder() {
let (_term, handle, vt) = setup(100, 24);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
let generation = vt.frame_generation();
handle.with_redraw_suppressed(|| {
renderer.switch_agent(agent_id("worker-1"));
renderer.handle(&Event::UiPromptSubmitted(UiPromptSubmitted {
literal: false,
session_id: test_session_id("s1"),
text: "selected agent transcript".into(),
agent_id: agent_id("worker-1"),
message_class: tau_proto::PromptMessageClass::User,
originator: tau_proto::PromptOriginator::User,
ctx_id: None,
}));
});
let generation = vt.wait_for_frame_containing_after(generation, "selected agent transcript");
renderer.clear_selected_agent_after_display_update_for_test(|| handle.redraw_sync());
let frame = vt.wait_for_frame_after(generation);
assert!(
!frame
.iter()
.any(|row| row.contains("selected agent transcript")),
"{frame:?}"
);
assert!(
frame
.iter()
.any(|row| row.contains("Overview — select an agent or use :agent new."))
);
assert!(
!frame
.iter()
.any(|row| row.contains("Write a message to worker-1"))
);
}
#[test]
fn switching_agents_preserves_estimated_cache_efficiency() {
let (_term, handle, vt) = setup(80, 24);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.apply_setting("show-turn-stats", "true");
renderer.switch_agent(agent_id("worker-1"));
renderer.handle(&Event::ProviderResponseFinished(
finished_response_with_usage(
"worker-1-sp-0",
"worker-1",
20_000,
0,
0,
"first worker response",
),
));
renderer.handle(&Event::ProviderResponseFinished(
finished_response_with_usage(
"worker-1-sp-1",
"worker-1",
20_100,
19_000,
0,
"second worker response",
),
));
renderer.switch_agent(agent_id("worker-2"));
renderer.switch_agent(agent_id("worker-1"));
sync(&handle);
assert!(vt.screen_contains(80, "Δ95%? 19k/20k?"));
}
#[test]
fn switching_to_hidden_agent_preserves_estimated_cache_efficiency() {
let (_term, handle, vt) = setup(80, 24);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.apply_setting("show-turn-stats", "true");
renderer.switch_agent(agent_id("worker-1"));
renderer.handle(&Event::ProviderResponseFinished(
finished_response_with_usage(
"worker-2-sp-0",
"worker-2",
20_000,
0,
0,
"hidden first response",
),
));
renderer.handle(&Event::ProviderResponseFinished(
finished_response_with_usage(
"worker-2-sp-1",
"worker-2",
20_100,
19_000,
0,
"hidden second response",
),
));
renderer.switch_agent(agent_id("worker-2"));
sync(&handle);
assert!(vt.screen_contains(80, "Δ95%? 19k/20k?"));
}
#[test]
fn extension_context_ready_routes_to_agent_ui_state() {
let (_term, handle, vt) = setup(80, 24);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.apply_setting("notice-level", "debug");
renderer.handle(&Event::ExtensionContextReady(
tau_proto::ExtensionContextReady {
agent_initialization_id: tau_proto::AgentInitializationId::parse("test-init")
.expect("test identifier must be valid"),
session_id: test_session_id("s1"),
agent_id: agent_id("worker-1"),
},
));
sync(&handle);
assert!(!vt.screen_contains(80, "context ready"));
renderer.switch_agent(agent_id("worker-1"));
sync(&handle);
assert!(vt.screen_contains(80, "agent @worker-1 context ready"));
}
#[test]
fn agent_context_initialization_summary_is_concise_and_literal() {
let advertised = tau_proto::DiscoveryEffectiveSkill {
name: "advertised".into(),
description: "description must stay hidden".to_owned(),
source: tau_proto::DiscoveryEffectiveSkillSource::BuiltIn,
add_to_prompt: true,
user_invocable: true,
disable_model_invocation: false,
argument_hint: None,
};
let initialized = tau_proto::HarnessAgentContextInitialized {
session_id: test_session_id("session-1"),
agent_id: agent_id("agent-1"),
agent_initialization_id: tau_proto::AgentInitializationId::parse("init-1")
.expect("test identifier must be valid"),
listed_skills: vec![advertised],
agents_files: vec![
tau_proto::DiscoveryAgentsFileSummary {
file_path: "/home/dpc/.config/agents/AGENTS.md".into(),
lines: 10,
bytes: 100,
},
tau_proto::DiscoveryAgentsFileSummary {
file_path: "/repo/AGENTS.md".into(),
lines: 20,
bytes: 200,
},
],
};
let block =
crate::tool_render::agent_context_initialized_block(&cli_test_theme(), &initialized, 2);
let spans = block.content.spans();
let text = spans
.iter()
.map(|span| span.text.as_str())
.collect::<String>();
assert_eq!(
text,
"▤ initialized agent-1\nskills:\n advertised 1L, 28B\n 2 other session skills available\nAGENTS.md:\n /home/dpc/.config/agents/AGENTS.md 10L, 100B\n /repo/AGENTS.md 20L, 200B"
);
assert!(!text.contains("\\n"));
assert!(!text.contains("description must stay hidden"));
assert_eq!(
spans
.iter()
.filter(|span| span.style.fg == Some(Color::DarkCyan))
.map(|span| span.text.as_str())
.collect::<Vec<_>>(),
[" 1L, 28B", " 10L, 100B", " 20L, 200B"]
);
}
#[test]
fn agent_context_initialization_summary_omits_empty_sections() {
let initialized = tau_proto::HarnessAgentContextInitialized {
session_id: test_session_id("session-1"),
agent_id: agent_id("agent-1"),
agent_initialization_id: tau_proto::AgentInitializationId::parse("init-1")
.expect("test identifier must be valid"),
listed_skills: Vec::new(),
agents_files: Vec::new(),
};
let text = |count| {
crate::tool_render::agent_context_initialized_block(&cli_test_theme(), &initialized, count)
.content
.spans()
.iter()
.map(|span| span.text.as_str())
.collect::<String>()
};
assert_eq!(text(0), "▤ initialized agent-1");
assert_eq!(
text(1),
"▤ initialized agent-1\nskills:\n 1 other session skill available"
);
}
#[test]
fn agent_context_initialization_event_aggregates_session_skills() {
let (_term, handle, vt) = setup(80, 24);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
let skill = |name: &str| tau_proto::DiscoveryEffectiveSkill {
name: name.into(),
description: format!("{name} description"),
source: tau_proto::DiscoveryEffectiveSkillSource::BuiltIn,
add_to_prompt: true,
user_invocable: true,
disable_model_invocation: false,
argument_hint: None,
};
renderer.handle(&Event::HarnessSessionSkillsAvailable(
tau_proto::HarnessSessionSkillsAvailable {
session_id: test_session_id("session-1"),
skills: vec![skill("advertised"), skill("other")],
},
));
renderer.switch_agent(agent_id("agent-1"));
renderer.handle(&Event::HarnessAgentContextInitialized(
tau_proto::HarnessAgentContextInitialized {
session_id: test_session_id("session-1"),
agent_id: agent_id("agent-1"),
agent_initialization_id: tau_proto::AgentInitializationId::parse("init-1")
.expect("test identifier must be valid"),
listed_skills: vec![skill("advertised")],
agents_files: vec![tau_proto::DiscoveryAgentsFileSummary {
file_path: "/repo/AGENTS.md".into(),
lines: 20,
bytes: 200,
}],
},
));
sync(&handle);
assert!(vt.screen_contains(80, "initialized agent-1"));
assert!(vt.screen_contains(80, "advertised"));
assert!(vt.screen_contains(80, "1 other session skill available"));
assert!(vt.screen_contains(80, "/repo/AGENTS.md"));
assert!(vt.screen_contains(80, "advertised 1L, 22B"));
assert!(vt.screen_contains(80, "/repo/AGENTS.md 20L, 200B"));
assert!(!vt.screen_contains(80, "other description"));
}
#[test]
fn catch_up_agent_context_initialization_waits_for_agent_selection() {
let (_term, handle, vt) = setup(80, 24);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.apply_setting("notice-level", "debug");
renderer.handle(&Event::HarnessNotice(tau_proto::HarnessNotice {
kind: "startup".into(),
message: "startup output adopted by first agent".into(),
level: tau_proto::NoticeLevel::Info,
purpose: tau_proto::NoticePurpose::Alert,
}));
renderer.handle(&Event::HarnessAgentContextInitialized(
tau_proto::HarnessAgentContextInitialized {
session_id: test_session_id("session-1"),
agent_id: agent_id("restored"),
agent_initialization_id: tau_proto::AgentInitializationId::parse("restored-init")
.expect("test identifier must be valid"),
listed_skills: Vec::new(),
agents_files: vec![tau_proto::DiscoveryAgentsFileSummary {
file_path: "/restored/AGENTS.md".into(),
lines: 6,
bytes: 550,
}],
},
));
renderer.handle(&Event::ExtensionContextReady(
tau_proto::ExtensionContextReady {
agent_initialization_id: tau_proto::AgentInitializationId::parse("restored-init")
.expect("test identifier must be valid"),
session_id: test_session_id("session-1"),
agent_id: agent_id("restored"),
},
));
sync(&handle);
assert!(vt.screen_contains(80, "startup output adopted by first agent"));
assert!(!vt.screen_contains(80, "/restored/AGENTS.md"));
assert!(!vt.screen_contains(80, "context ready"));
renderer.handle_attach_agent_selection_socket_delivery(
&Event::SessionReplayComplete(tau_proto::SessionReplayComplete {
session_id: test_session_id("session-1"),
error: None,
}),
&agent_id("restored"),
tau_proto::UnixMicros::new(1),
tau_cli_term::RendererDeliveryId::new(1),
);
sync(&handle);
assert_eq!(
renderer
.current_agent_state()
.lock()
.expect("current agent")
.as_deref(),
Some("restored")
);
assert!(vt.screen_contains(80, "startup output adopted by first agent"));
assert!(vt.screen_contains(80, "/restored/AGENTS.md 6L, 550B"));
assert!(vt.screen_contains(80, "context ready"));
let restored = vt.screen_text(80).join("\n");
assert!(
restored.find("/restored/AGENTS.md").expect("discovery row")
< restored
.find("context ready")
.expect("later context-ready row")
);
renderer.switch_agent(agent_id("background"));
sync(&handle);
assert!(!vt.screen_contains(80, "startup output adopted by first agent"));
assert!(!vt.screen_contains(80, "/restored/AGENTS.md"));
renderer.switch_agent(agent_id("restored"));
sync(&handle);
assert!(vt.screen_contains(80, "startup output adopted by first agent"));
assert!(vt.screen_contains(80, "/restored/AGENTS.md 6L, 550B"));
}
#[test]
fn initial_no_agent_extension_lifecycle_appends_to_first_agent() {
let (_term, handle, vt) = setup(80, 24);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.handle(&Event::ExtensionStarting(tau_proto::ExtensionStarting {
instance_id: 8.into(),
extension_name: tau_proto::ExtensionName::parse("std-global")
.expect("test identifier must satisfy its grammar"),
pid: Some(456),
}));
sync(&handle);
assert!(vt.screen_contains(80, "extension std-global starting"));
let full_render_count = handle.full_render_count();
renderer.switch_agent(agent_id("fresh-agent"));
sync(&handle);
assert!(vt.screen_contains(80, "extension std-global starting"));
assert_eq!(handle.full_render_count(), full_render_count);
renderer.handle(&Event::ExtensionExited(tau_proto::ExtensionExited {
instance_id: 8.into(),
extension_name: tau_proto::ExtensionName::parse("std-global")
.expect("test identifier must satisfy its grammar"),
pid: Some(456),
exit_code: Some(1),
signal: None,
}));
sync(&handle);
assert!(!vt.screen_contains(80, "extension std-global starting"));
assert!(vt.screen_contains(80, "extension std-global exited"));
}
#[test]
fn explicit_no_agent_extension_lifecycle_routes_to_no_agent_snapshot() {
let (_term, handle, vt) = setup(80, 24);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.switch_agent(agent_id("previous-agent"));
renderer.clear_selected_agent();
renderer.handle(&Event::ExtensionStarting(tau_proto::ExtensionStarting {
instance_id: 8.into(),
extension_name: tau_proto::ExtensionName::parse("std-global")
.expect("test identifier must satisfy its grammar"),
pid: Some(456),
}));
sync(&handle);
assert!(vt.screen_contains(80, "extension std-global starting"));
renderer.switch_agent(agent_id("fresh-agent"));
sync(&handle);
assert!(!vt.screen_contains(80, "extension std-global starting"));
renderer.handle(&Event::ExtensionExited(tau_proto::ExtensionExited {
instance_id: 8.into(),
extension_name: tau_proto::ExtensionName::parse("std-global")
.expect("test identifier must satisfy its grammar"),
pid: Some(456),
exit_code: Some(1),
signal: None,
}));
sync(&handle);
assert!(!vt.screen_contains(80, "extension std-global exited"));
assert!(!vt.screen_contains(80, "extension std-global starting"));
renderer.clear_selected_agent();
sync(&handle);
assert!(vt.screen_contains(80, "extension std-global exited"));
assert!(!vt.screen_contains(80, "extension std-global starting"));
}
#[test]
fn initial_replayed_global_message_fact_survives_first_agent_selection() {
let (_term, handle, vt) = setup(100, 30);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
let fact = Event::MessageDelivered(tau_proto::MessageDelivered::new(
tau_proto::MessagePublisherId::parse("bridge-main")
.expect("canonical publisher id must satisfy the identifier grammar"),
tau_proto::MessageAgentTarget::new("unavailable-agent"),
tau_proto::MessageFactId::new("replayed-message"),
tau_proto::MessageParty {
stable_id: "sender-1".to_owned(),
display_name: None,
sender_auth: None,
sender_trust: None,
},
None,
"initial replay body",
));
let delivery = tau_proto::EventDelivery::replay(tau_proto::UnixMicros::new(1_000_000), fact);
let (event, replay, recorded_at) = delivery.into_parts();
assert!(replay);
renderer.handle_recorded_at(&event, recorded_at.expect("replay timestamp"));
sync(&handle);
assert!(vt.screen_contains(100, "initial replay body"));
renderer.switch_agent(agent_id("first-fresh-agent"));
sync(&handle);
assert!(!vt.screen_contains(100, "initial replay body"));
renderer.clear_selected_agent();
sync(&handle);
assert!(vt.screen_contains(100, "initial replay body"));
}
#[test]
fn deselected_live_global_message_fact_survives_fresh_agent_selection() {
let (_term, handle, vt) = setup(100, 30);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.switch_agent(agent_id("existing-agent"));
renderer.clear_selected_agent();
renderer.handle(&Event::MessageDelivered(tau_proto::MessageDelivered::new(
tau_proto::MessagePublisherId::parse("bridge-main")
.expect("canonical publisher id must satisfy the identifier grammar"),
tau_proto::MessageAgentTarget::new("unavailable-agent"),
tau_proto::MessageFactId::new("live-message"),
tau_proto::MessageParty {
stable_id: "sender-1".to_owned(),
display_name: None,
sender_auth: None,
sender_trust: None,
},
None,
"deselected live body",
)));
sync(&handle);
assert!(vt.screen_contains(100, "deselected live body"));
renderer.switch_agent(agent_id("never-cached-agent"));
sync(&handle);
assert!(!vt.screen_contains(100, "deselected live body"));
renderer.clear_selected_agent();
sync(&handle);
assert!(vt.screen_contains(100, "deselected live body"));
}
#[test]
fn initial_no_agent_action_error_appends_to_first_agent() {
let (_term, handle, vt) = setup(80, 24);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.record_action_invocation(
tau_proto::ActionInvocationId::parse("action-2")
.expect("test identifier must satisfy its grammar"),
None,
);
renderer.switch_agent(agent_id("fresh-agent"));
renderer.handle(&Event::ActionError(tau_proto::ActionError {
invocation_id: tau_proto::ActionInvocationId::parse("action-2")
.expect("test identifier must satisfy its grammar"),
action_id: "demo.action".to_owned(),
message: "no-agent action failed".to_owned(),
details: None,
}));
sync(&handle);
assert!(vt.screen_contains(80, "no-agent action failed"));
}
#[test]
fn explicit_no_agent_action_error_routes_to_no_agent_snapshot() {
let (_term, handle, vt) = setup(80, 24);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.switch_agent(agent_id("previous-agent"));
renderer.clear_selected_agent();
renderer.record_action_invocation(
tau_proto::ActionInvocationId::parse("action-2")
.expect("test identifier must satisfy its grammar"),
None,
);
renderer.switch_agent(agent_id("fresh-agent"));
renderer.handle(&Event::ActionError(tau_proto::ActionError {
invocation_id: tau_proto::ActionInvocationId::parse("action-2")
.expect("test identifier must satisfy its grammar"),
action_id: "demo.action".to_owned(),
message: "no-agent action failed".to_owned(),
details: None,
}));
sync(&handle);
assert!(!vt.screen_contains(80, "no-agent action failed"));
renderer.clear_selected_agent();
sync(&handle);
assert!(vt.screen_contains(80, "no-agent action failed"));
}
#[test]
fn initial_no_agent_action_result_appends_to_first_agent() {
let (_term, handle, vt) = setup(80, 24);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.record_action_invocation(
tau_proto::ActionInvocationId::parse("action-3")
.expect("test identifier must satisfy its grammar"),
None,
);
renderer.handle(&Event::ActionResult(tau_proto::ActionResult {
invocation_id: tau_proto::ActionInvocationId::parse("action-3")
.expect("test identifier must satisfy its grammar"),
action_id: "demo.action".to_owned(),
output: tau_proto::ActionOutput::Text {
text: "visible no-agent action output".to_owned(),
},
}));
sync(&handle);
assert!(vt.screen_contains(80, "visible no-agent action output"));
renderer.switch_agent(agent_id("fresh-agent"));
sync(&handle);
assert!(vt.screen_contains(80, "visible no-agent action output"));
}
#[test]
fn explicit_no_agent_action_result_is_preserved_when_switching_to_fresh_agent() {
let (_term, handle, vt) = setup(80, 24);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.switch_agent(agent_id("previous-agent"));
renderer.clear_selected_agent();
renderer.record_action_invocation(
tau_proto::ActionInvocationId::parse("action-3")
.expect("test identifier must satisfy its grammar"),
None,
);
renderer.handle(&Event::ActionResult(tau_proto::ActionResult {
invocation_id: tau_proto::ActionInvocationId::parse("action-3")
.expect("test identifier must satisfy its grammar"),
action_id: "demo.action".to_owned(),
output: tau_proto::ActionOutput::Text {
text: "visible no-agent action output".to_owned(),
},
}));
sync(&handle);
assert!(vt.screen_contains(80, "visible no-agent action output"));
renderer.switch_agent(agent_id("fresh-agent"));
sync(&handle);
assert!(!vt.screen_contains(80, "visible no-agent action output"));
renderer.clear_selected_agent();
sync(&handle);
assert!(vt.screen_contains(80, "visible no-agent action output"));
}
#[test]
fn hidden_agent_events_do_not_force_visible_full_redraw() {
let (_term, handle, _vt) = setup(80, 24);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.handle(&Event::SessionStarted(tau_proto::SessionStarted {
session_id: test_session_id("s1"),
reason: tau_proto::SessionStartReason::Initial,
}));
renderer.handle(&Event::AgentPromptCreated(agent_prompt_created(
"main-sp", "s1",
)));
sync(&handle);
let full_render_count = handle.full_render_count();
renderer.handle(&Event::StartAgentAccepted(tau_proto::StartAgentAccepted {
start_id: tau_proto::StartOperationId(1),
query_id: "q-worker".to_owned(),
agent_id: agent_id("worker-1"),
}));
renderer.handle(&Event::AgentStarted(tau_proto::AgentStarted {
creator: Some(tau_proto::AgentCreator::default()),
parent_agent: None,
agent_id: agent_id("worker-1"),
role: "engineer".to_owned(),
display_name: None,
metadata: Vec::new(),
ephemeral: false,
}));
renderer.handle(&Event::AgentPromptCreated(AgentPromptCreated {
originator: tau_proto::PromptOriginator::Extension {
name: tau_proto::ExtensionName::parse("core-subagents")
.expect("test identifier must satisfy its grammar"),
query_id: "q-worker".to_owned(),
},
..agent_prompt_created("worker-sp", "s1")
}));
sync(&handle);
assert_eq!(handle.full_render_count(), full_render_count);
}
#[test]
fn agent_stats_does_not_overwrite_display_name() {
let (_term, handle, _vt) = setup(80, 24);
let mut renderer = EventRenderer::new(
handle,
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.handle(&Event::AgentStarted(tau_proto::AgentStarted {
creator: Some(tau_proto::AgentCreator::default()),
parent_agent: None,
agent_id: agent_id("engineer-Ab12"),
role: "engineer".to_owned(),
display_name: Some("engineer: look it up".to_owned()),
metadata: Vec::new(),
ephemeral: false,
}));
renderer.handle(&Event::AgentStatsUpdated(tau_proto::AgentStatsUpdated {
session_id: test_session_id("s1"),
agent_id: agent_id("engineer-Ab12"),
navigation_mode: tau_proto::AgentNavigationMode::Active,
runtime_state: tau_proto::AgentRuntimeState::Idle,
turn_activity: tau_proto::AgentTurnActivity::Idle,
tools: tau_proto::AgentToolStats::default(),
context: tau_proto::AgentContextStats::default(),
inner_turns_total: None,
estimated_api_cost: Default::default(),
creator_subtree_estimated_api_cost: Default::default(),
work_status: Default::default(),
}));
let display_names = renderer.agent_display_names();
let display_names = display_names.lock().expect("display names");
assert_eq!(
display_names.get("engineer-Ab12").map(String::as_str),
Some("engineer: look it up")
);
}
#[test]
fn navigation_mode_results_do_not_mutate_cache() {
let (_term, handle, _vt) = setup(80, 24);
let mut renderer = EventRenderer::new(
handle,
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.handle(&Event::SessionStarted(SessionStarted {
session_id: test_session_id("s1"),
reason: SessionStartReason::Initial,
}));
renderer.handle(&Event::AgentStarted(tau_proto::AgentStarted {
creator: Some(tau_proto::AgentCreator::default()),
parent_agent: None,
agent_id: agent_id("worker-1"),
role: "engineer".to_owned(),
display_name: None,
metadata: Vec::new(),
ephemeral: false,
}));
apply_test_navigation_mode(&mut renderer, tau_proto::AgentNavigationMode::Suspended);
for outcome in [
tau_proto::UiSetAgentNavigationModeOutcome::Applied,
tau_proto::UiSetAgentNavigationModeOutcome::Rejected {
reason: tau_proto::UiSetAgentNavigationModeRejection::StaleSession,
},
tau_proto::UiSetAgentNavigationModeOutcome::Rejected {
reason: tau_proto::UiSetAgentNavigationModeRejection::AgentNotLoaded,
},
] {
renderer.handle(&Event::UiSetAgentNavigationModeResult(
tau_proto::UiSetAgentNavigationModeResult {
request_id: "result".to_owned(),
session_id: test_session_id("s1"),
agent_id: agent_id("worker-1"),
outcome,
},
));
assert_eq!(
renderer
.agent_navigation()
.lock()
.expect("navigation")
.mode(&agent_id("worker-1")),
tau_proto::AgentNavigationMode::Suspended
);
}
apply_test_navigation_mode(&mut renderer, tau_proto::AgentNavigationMode::Active);
assert_eq!(
renderer
.agent_navigation()
.lock()
.expect("navigation")
.mode(&agent_id("worker-1")),
tau_proto::AgentNavigationMode::Active
);
}
#[test]
fn selected_hidden_agent_placeholder_distinguishes_modes() {
let (_term, handle, vt) = setup(100, 24);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.handle(&Event::StartAgentAccepted(tau_proto::StartAgentAccepted {
start_id: tau_proto::StartOperationId(1),
query_id: "q-worker".to_owned(),
agent_id: agent_id("worker-1"),
}));
renderer.handle(&Event::AgentStarted(tau_proto::AgentStarted {
creator: Some(tau_proto::AgentCreator::default()),
parent_agent: None,
agent_id: agent_id("worker-1"),
role: "engineer".to_owned(),
display_name: None,
metadata: Vec::new(),
ephemeral: false,
}));
renderer.handle(&Event::AgentStatsUpdated(tau_proto::AgentStatsUpdated {
session_id: test_session_id("s1"),
agent_id: agent_id("worker-1"),
navigation_mode: tau_proto::AgentNavigationMode::ActiveAuto,
runtime_state: tau_proto::AgentRuntimeState::Idle,
turn_activity: tau_proto::AgentTurnActivity::Idle,
tools: Default::default(),
context: Default::default(),
inner_turns_total: None,
estimated_api_cost: Default::default(),
creator_subtree_estimated_api_cost: Default::default(),
work_status: Default::default(),
}));
renderer.switch_agent(agent_id("worker-1"));
sync(&handle);
assert!(vt.screen_contains(100, "active-auto agent is idle"));
renderer.clear_model_status_for_test();
sync(&handle);
let generation = vt.frame_generation();
apply_test_navigation_mode(&mut renderer, tau_proto::AgentNavigationMode::Suspended);
let generation = vt.wait_for_frame_containing_after(generation, "This agent is suspended");
apply_test_navigation_mode(&mut renderer, tau_proto::AgentNavigationMode::Active);
vt.wait_for_frame_containing_after(generation, "Write a message to worker-1");
}
#[test]
fn delayed_navigation_refresh_cannot_resurrect_unloaded_agent() {
let (_term, handle, _vt) = setup(80, 24);
let mut renderer = EventRenderer::new(
handle,
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.handle(&Event::StartAgentAccepted(tau_proto::StartAgentAccepted {
start_id: tau_proto::StartOperationId(1),
query_id: "q-worker-1".to_owned(),
agent_id: agent_id("worker-1"),
}));
renderer.handle(&Event::AgentStatsUpdated(tau_proto::AgentStatsUpdated {
session_id: test_session_id("s1"),
agent_id: agent_id("worker-1"),
navigation_mode: tau_proto::AgentNavigationMode::ActiveAuto,
runtime_state: tau_proto::AgentRuntimeState::Idle,
turn_activity: tau_proto::AgentTurnActivity::Idle,
tools: Default::default(),
context: Default::default(),
inner_turns_total: None,
estimated_api_cost: Default::default(),
creator_subtree_estimated_api_cost: Default::default(),
work_status: Default::default(),
}));
apply_test_navigation_mode(&mut renderer, tau_proto::AgentNavigationMode::Active);
renderer.handle(&Event::SessionAgentUnloaded(
tau_proto::SessionAgentUnloaded {
session_id: test_session_id("s1"),
agent_id: agent_id("worker-1"),
},
));
assert!(
!renderer
.agent_navigation()
.lock()
.expect("agent navigation")
.is_live(&agent_id("worker-1"))
);
renderer.handle(&Event::StartAgentAccepted(tau_proto::StartAgentAccepted {
start_id: tau_proto::StartOperationId(1),
query_id: "q-worker-2".to_owned(),
agent_id: agent_id("worker-1"),
}));
let navigation = renderer.agent_navigation();
let navigation = navigation.lock().expect("agent navigation");
assert_eq!(
navigation.mode(&agent_id("worker-1")),
AgentNavigationState::Active
);
assert!(!navigation.is_active(&agent_id("worker-1")));
}
#[test]
fn hidden_agent_activity_keeps_global_in_progress() {
let (_term, handle, _vt) = setup(80, 24);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
let in_progress = renderer.agent_in_progress_state();
renderer.handle(&Event::AgentPromptCreated(agent_prompt_created(
"main-sp", "s1",
)));
renderer.handle(&Event::StartAgentAccepted(tau_proto::StartAgentAccepted {
start_id: tau_proto::StartOperationId(1),
query_id: "q-worker".to_owned(),
agent_id: agent_id("worker-1"),
}));
renderer.handle(&Event::AgentPromptCreated(AgentPromptCreated {
originator: tau_proto::PromptOriginator::Extension {
name: tau_proto::ExtensionName::parse("core-subagents")
.expect("test identifier must satisfy its grammar"),
query_id: "q-worker".to_owned(),
},
..agent_prompt_created("worker-sp", "s1")
}));
renderer.handle(&Event::ProviderResponseFinished(ProviderResponseFinished {
automatic_compaction_decision: None,
output_length_disposition: tau_proto::OutputLengthDisposition::None,
originator: tau_proto::PromptOriginator::Extension {
name: tau_proto::ExtensionName::parse("core-subagents")
.expect("test identifier must satisfy its grammar"),
query_id: "q-worker".to_owned(),
},
..finished_response("worker-sp", vec![assistant_message_item("done")])
}));
assert!(in_progress.load(std::sync::atomic::Ordering::Relaxed));
}
#[test]
fn watched_agent_stats_route_to_hidden_watcher_owner() {
let (_term, handle, vt) = setup(90, 24);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.handle(&Event::SessionStarted(tau_proto::SessionStarted {
session_id: test_session_id("s1"),
reason: tau_proto::SessionStartReason::Initial,
}));
renderer.handle(&Event::StartAgentAccepted(tau_proto::StartAgentAccepted {
start_id: tau_proto::StartOperationId(1),
query_id: "q-worker".to_owned(),
agent_id: agent_id("worker-1"),
}));
renderer.handle(&Event::AgentWatchesUpdated(
tau_proto::AgentWatchesUpdated {
session_id: test_session_id("s1"),
watcher_id: agent_id("worker-1"),
watched_agent_ids: vec![agent_id("engineer_1")],
changed_agent_id: Some(agent_id("engineer_1")),
cause: tau_proto::AgentWatchUpdateCause::AgentStart,
},
));
renderer.handle(&Event::AgentPromptStarted(tau_proto::AgentPromptStarted {
agent_id: agent_id("engineer_1"),
originator: tau_proto::PromptOriginator::Extension {
name: tau_proto::ExtensionName::parse("__harness__")
.expect("test identifier must satisfy its grammar"),
query_id: "delegate-1".to_owned(),
},
..agent_prompt_started("ap-engineer_1-0", "s1")
}));
renderer.handle(&Event::AgentStatsUpdated(tau_proto::AgentStatsUpdated {
session_id: test_session_id("s1"),
agent_id: agent_id("engineer_1"),
navigation_mode: tau_proto::AgentNavigationMode::Active,
runtime_state: tau_proto::AgentRuntimeState::Running,
turn_activity: tau_proto::AgentTurnActivity::Idle,
tools: tau_proto::AgentToolStats {
in_flight: 1,
started_total: 2,
},
context: tau_proto::AgentContextStats::default(),
inner_turns_total: None,
estimated_api_cost: Default::default(),
creator_subtree_estimated_api_cost: Default::default(),
work_status: Default::default(),
}));
sync(&handle);
assert!(!vt.screen_contains(90, "❓💤 @engineer_1"));
renderer.switch_agent(agent_id("worker-1"));
sync(&handle);
assert!(vt.screen_contains(90, "❓💤 @engineer_1"));
assert!(vt.screen_contains(90, "%1/2"));
}
#[test]
fn old_agent_message_updates_overview_without_selecting_sender() {
let (_term, handle, vt) = setup(80, 24);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.handle(&Event::SessionStarted(tau_proto::SessionStarted {
session_id: test_session_id("s1"),
reason: tau_proto::SessionStartReason::Initial,
}));
renderer.handle(&Event::UiPromptSubmitted(UiPromptSubmitted {
literal: false,
session_id: test_session_id("s1"),
text: "old agent prompt".to_owned(),
agent_id: agent_id("old-agent"),
message_class: tau_proto::PromptMessageClass::User,
originator: tau_proto::PromptOriginator::User,
ctx_id: None,
}));
sync(&handle);
assert!(vt.screen_contains(80, "old agent prompt"));
renderer.clear_selected_agent();
sync(&handle);
assert!(!vt.screen_contains(80, "old agent prompt"));
renderer.handle(&agent_message(
"old-agent",
"other-agent",
"hidden old-agent message",
));
sync(&handle);
assert!(vt.screen_contains(80, "Message from @old-agent to @other-agent"));
assert!(vt.screen_contains(80, "hidden old-agent message"));
assert_eq!(
renderer
.current_agent_state()
.lock()
.expect("current agent")
.selected_agent_id()
.cloned(),
None
);
renderer.switch_agent(agent_id("old-agent"));
sync(&handle);
assert!(vt.screen_contains(80, "hidden old-agent message"));
}
#[test]
fn manual_compaction_selects_agent_from_empty_state() {
let (_term, handle, _vt) = setup(80, 24);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.handle(&Event::AgentCompactionTriggered(AgentCompactionTriggered {
agent_id: tau_proto::AgentId::parse("live-agent").expect("agent id"),
originator: tau_proto::PromptOriginator::User,
resume_inference: false,
}));
sync(&handle);
assert_eq!(
renderer
.current_agent_state()
.lock()
.expect("current agent")
.selected_agent_id()
.cloned(),
Some(agent_id("live-agent"))
);
}
#[test]
fn role_setting_updates_are_typed_and_reset_aware() {
use std::num::NonZeroU8;
use super::super::ui_commands::parse_role_setting_update;
let tool_names = || {
vec![
tau_proto::ToolName::new("web_search"),
tau_proto::ToolName::new("grep"),
]
};
let tool_group_names = || {
vec![
tau_proto::ToolGroupName::new("web"),
tau_proto::ToolGroupName::new("shell"),
]
};
for (setting, value, expected) in [
(
"model",
"openai/gpt-4o",
UiRoleUpdateAction::SetModel {
model: Some("openai/gpt-4o".parse().expect("valid model id")),
},
),
(
"effort",
"disabled",
UiRoleUpdateAction::SetEffort {
effort: Some(tau_proto::ReasoningIntent::Disabled),
},
),
(
"effort",
"reset",
UiRoleUpdateAction::SetEffort { effort: None },
),
(
"effort",
"increase:2",
UiRoleUpdateAction::AdjustEffort {
adjustment: tau_proto::ReasoningIntensityDelta::new(2_000_000).expect("nonzero"),
},
),
(
"verbosity",
"high",
UiRoleUpdateAction::SetVerbosity {
verbosity: Some(Verbosity::High),
},
),
(
"verbosity",
"decrease",
UiRoleUpdateAction::AdjustVerbosity {
adjustment: tau_proto::UiRoleSettingAdjustment::Decrease(
NonZeroU8::new(1).expect("positive"),
),
},
),
(
"thinking-summary",
"off",
UiRoleUpdateAction::SetThinkingSummary {
thinking_summary: Some(ThinkingSummary::Off),
},
),
(
"thinking-summary",
"increase:3",
UiRoleUpdateAction::AdjustThinkingSummary {
adjustment: tau_proto::UiRoleSettingAdjustment::Increase(
NonZeroU8::new(3).expect("positive"),
),
},
),
(
"service-tier",
"fast",
UiRoleUpdateAction::SetServiceTier {
service_tier: Some(ServiceTier::Fast),
},
),
(
"service-tier",
"reset",
UiRoleUpdateAction::SetServiceTier { service_tier: None },
),
(
"compaction-threshold",
"85000",
UiRoleUpdateAction::SetCompactionThreshold {
compaction_threshold: Some(tau_proto::TokenCount::new(85000)),
},
),
(
"compaction-threshold",
"reset",
UiRoleUpdateAction::SetCompactionThreshold {
compaction_threshold: None,
},
),
(
"tools",
"web_search,grep",
UiRoleUpdateAction::SetTools {
tools: Some(tool_names()),
},
),
(
"tools",
"reset",
UiRoleUpdateAction::SetTools { tools: None },
),
(
"enable-tool-groups",
"web,shell",
UiRoleUpdateAction::SetEnableToolGroups {
enable_tool_groups: tool_group_names(),
},
),
(
"disable-tool-groups",
"web,shell",
UiRoleUpdateAction::SetDisableToolGroups {
disable_tool_groups: tool_group_names(),
},
),
(
"enable-tools",
"web_search,grep",
UiRoleUpdateAction::SetEnableTools {
enable_tools: tool_names(),
},
),
(
"enable-tools",
"reset",
UiRoleUpdateAction::SetEnableTools {
enable_tools: Vec::new(),
},
),
(
"disable-tools",
"web_search,grep",
UiRoleUpdateAction::SetDisableTools {
disable_tools: tool_names(),
},
),
(
"disable-tools",
"reset",
UiRoleUpdateAction::SetDisableTools {
disable_tools: Vec::new(),
},
),
] {
assert_eq!(
parse_role_setting_update(setting, value).expect("role setting parses"),
expected,
"{setting} {value}"
);
}
assert!(parse_role_setting_update("service-tier", "off").is_err());
assert!(parse_role_setting_update("compaction-threshold", "999").is_err());
for value in ["increase:0", "increase:-0.25", "decrease:-0.25"] {
assert!(
parse_role_setting_update("effort", value).is_err(),
"{value} must not reverse or erase its named direction"
);
}
for value in ["-0.25", "1.000001", "2.0"] {
assert!(
parse_role_setting_update("effort", value).is_err(),
"{value} must not bypass the absolute portable range"
);
}
assert_eq!(
parse_role_setting_update("unknown", "value").expect_err("unknown setting"),
"unknown setting"
);
}
#[test]
fn no_agent_overview_deduplicates_agent_message_projections() {
let (_term, handle, vt) = setup(96, 20);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.switch_agent(agent_id("sender-agent"));
renderer.handle(&agent_message(
"sender-agent",
"recipient-agent",
"overview semantic body",
));
renderer.handle(&Event::AgentMessageReceived(
tau_proto::AgentMessageReceived {
message_id: tau_proto::AgentMessageId::parse("msg-sender-agent-recipient-agent")
.expect("test message id must satisfy its grammar"),
sender_id: agent_id("sender-agent"),
sender_session_id: None,
recipient_id: agent_id("recipient-agent"),
kind: tau_proto::AgentMessageKind::Message,
watch_provider_status: None,
watch_work_status: None,
watch_long_wait: None,
watch_lifecycle: None,
sender_notice: None,
recipient_notice: None,
message: "overview semantic body".to_owned(),
},
));
sync(&handle);
assert!(vt.screen_contains(96, "overview semantic body"));
renderer.clear_selected_agent();
sync(&handle);
assert_eq!(
visible_lines(&vt, 96)
.iter()
.filter(|line| line.contains("overview semantic body"))
.count(),
1
);
renderer.handle(&agent_message(
"sender-agent",
"third-agent",
"live overview body",
));
sync(&handle);
assert!(vt.screen_contains(96, "live overview body"));
assert_eq!(
renderer
.current_agent_state()
.lock()
.expect("current agent")
.selected_agent_id()
.cloned(),
None
);
renderer.switch_agent(agent_id("recipient-agent"));
sync(&handle);
assert!(vt.screen_contains(96, "overview semantic body"));
renderer.clear_selected_agent();
renderer.handle(&Event::UiPromptSubmitted(UiPromptSubmitted {
literal: false,
session_id: test_session_id("s1"),
text: "start fresh from overview".to_owned(),
agent_id: agent_id("fresh-agent"),
message_class: tau_proto::PromptMessageClass::User,
originator: tau_proto::PromptOriginator::User,
ctx_id: None,
}));
sync(&handle);
assert!(!vt.screen_contains(96, "start fresh from overview"));
assert!(vt.screen_contains(96, "overview semantic body"));
renderer.clear_selected_agent();
sync(&handle);
assert!(vt.screen_contains(96, "overview semantic body"));
}
#[test]
fn hidden_message_snapshots_reproject_late_agent_names() {
let (_term, handle, vt) = setup(100, 10);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.apply_setting("show-messages", "all-full");
renderer.switch_agent(agent_id("agent-a"));
renderer.handle(&agent_message("agent-a", "agent-b", "agent history"));
renderer.switch_agent(agent_id("viewer"));
renderer.handle(&Event::AgentDisplayNameSet(
tau_proto::AgentDisplayNameSet {
agent_id: agent_id("agent-b"),
display_name: "late worker".to_owned(),
},
));
sync(&handle);
let generation = vt.frame_generation();
renderer.switch_agent(agent_id("agent-a"));
vt.wait_for_frame_containing_after(
generation,
"Message from @agent-a to @agent-b (late worker):",
);
assert!(vt.screen_contains(100, "Message from @agent-a to @agent-b (late worker):"));
renderer.clear_selected_agent();
renderer.handle(&agent_message(
"agent-a",
"agent-c",
"overview message history",
));
renderer.switch_agent(agent_id("viewer"));
renderer.handle(&Event::AgentDisplayNameSet(
tau_proto::AgentDisplayNameSet {
agent_id: agent_id("agent-a"),
display_name: "late sender".to_owned(),
},
));
sync(&handle);
let generation = vt.frame_generation();
renderer.clear_selected_agent();
vt.wait_for_frame_containing_after(
generation,
"Message from @agent-a (late sender) to @agent-c:",
);
assert!(vt.screen_contains(100, "Message from @agent-a (late sender) to @agent-c:"));
assert!(vt.screen_contains(100, "overview message history"));
}
#[test]
fn selected_agent_quota_repaints_for_both_event_orderings() {
let model = tau_proto::ModelId::from("chatgpt/gpt-5.6-sol");
for quota_first in [true, false] {
let (_term, handle, vt) = setup(80, 24);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.handle(&Event::HarnessRoleSelected(HarnessRoleSelected {
model: Some("other/model".into()),
context_window: None,
role: "engineer".into(),
baseline_params: None,
model_params: tau_proto::ModelParams::default(),
}));
renderer.handle(&Event::SessionStarted(SessionStarted {
session_id: test_session_id("quota-order"),
reason: SessionStartReason::Initial,
}));
let started = Event::AgentPromptStarted(tau_proto::AgentPromptStarted {
model: model.clone(),
..agent_prompt_started("quota-sp", "quota-order")
});
let quota = danger_quota_event(&model);
if quota_first {
renderer.handle("a);
sync(&handle);
assert!(!vt.screen_contains(80, "Q!"));
renderer.handle(&started);
} else {
renderer.handle(&started);
sync(&handle);
assert!(!vt.screen_contains(80, "Q!"));
renderer.handle("a);
}
sync(&handle);
assert!(
vt.screen_contains(80, "Q!"),
"selected-agent quota was not repainted when quota_first={quota_first}"
);
}
}
#[test]
fn focused_agent_context_usage_event_replaces_unknown_context_window() {
let (_term, handle, vt) = setup(100, 24);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.handle(&Event::HarnessRoleSelected(HarnessRoleSelected {
model: Some("test/model".into()),
context_window: Some(200_000),
role: "engineer".into(),
baseline_params: None,
model_params: tau_proto::ModelParams::default(),
}));
renderer.handle(&Event::AgentPromptCreated(agent_prompt_created(
"main-sp", "s1",
)));
renderer.handle(&Event::HarnessAgentContextUsageChanged(
tau_proto::HarnessAgentContextUsageChanged {
agent_id: tau_proto::AgentId::parse("main").expect("agent id"),
input_tokens: Some(12_000),
cached_tokens: Some(0),
context_window: Some(200_000),
percent_used: Some(6),
},
));
sync(&handle);
let status_row = vt
.screen_text(100)
.into_iter()
.find(|row| row.contains("@main"))
.expect("status row");
assert!(status_row.ends_with("#12k/200k -/-"));
assert!(!status_row.contains("#-/200k"));
}
#[test]
fn role_default_knobs_are_hidden_and_overrides_follow_role() {
let (_term, handle, vt) = setup(80, 24);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.handle(&Event::HarnessRolesAvailable(HarnessRolesAvailable {
roles: vec![HarnessRoleInfo {
name: "engineer".to_owned(),
description: "model=test/model, effort=medium, verbosity=medium, thinking-summary=auto"
.to_owned(),
role_description: None,
details: None,
}],
groups: Vec::new(),
custom_prompts: Vec::new(),
}));
renderer.handle(&Event::HarnessRoleSelected(HarnessRoleSelected {
model: Some("test/model".into()),
context_window: Some(200_000),
role: "engineer".into(),
model_params: tau_proto::ModelParams {
effort: tau_proto::ReasoningSelection::native(tau_proto::NativeReasoningEffort::Medium),
verbosity: Verbosity::Medium,
thinking_summary: tau_proto::ThinkingSummary::Auto,
service_tier: None,
},
baseline_params: Some(tau_proto::ModelParams {
effort: tau_proto::ReasoningSelection::native(tau_proto::NativeReasoningEffort::Medium),
verbosity: Verbosity::Medium,
thinking_summary: tau_proto::ThinkingSummary::Auto,
service_tier: None,
}),
}));
renderer.handle(&Event::SessionStarted(SessionStarted {
session_id: test_session_id("s2"),
reason: SessionStartReason::Initial,
}));
sync(&handle);
assert!(vt.screen_contains(80, "+engineer"));
assert!(!vt.screen_contains(80, "^medium"));
assert!(!vt.screen_contains(80, "~medium"));
renderer.handle(&Event::HarnessRoleSelected(HarnessRoleSelected {
model: Some("test/model".into()),
context_window: Some(200_000),
role: "engineer".into(),
model_params: tau_proto::ModelParams {
effort: tau_proto::ReasoningSelection::native(tau_proto::NativeReasoningEffort::Medium),
verbosity: Verbosity::High,
thinking_summary: tau_proto::ThinkingSummary::Auto,
service_tier: None,
},
baseline_params: Some(tau_proto::ModelParams {
effort: tau_proto::ReasoningSelection::native(tau_proto::NativeReasoningEffort::Medium),
verbosity: Verbosity::Medium,
thinking_summary: tau_proto::ThinkingSummary::Auto,
service_tier: None,
}),
}));
sync(&handle);
assert!(vt.screen_contains(80, "+engineer ~high"));
}
#[test]
fn role_state_overrides_are_compared_to_role_baseline() {
let (_term, handle, vt) = setup(80, 24);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.handle(&Event::HarnessRolesAvailable(HarnessRolesAvailable {
roles: vec![HarnessRoleInfo {
name: "engineer".to_owned(),
description: "model=test/model, effort=low, verbosity=high, thinking-summary=auto"
.to_owned(),
role_description: None,
details: None,
}],
groups: Vec::new(),
custom_prompts: Vec::new(),
}));
renderer.handle(&Event::HarnessRoleSelected(HarnessRoleSelected {
model: Some("test/model".into()),
context_window: None,
role: "engineer".into(),
model_params: tau_proto::ModelParams {
effort: tau_proto::ReasoningSelection::native(tau_proto::NativeReasoningEffort::Low),
verbosity: Verbosity::High,
thinking_summary: tau_proto::ThinkingSummary::Auto,
service_tier: None,
},
baseline_params: Some(tau_proto::ModelParams {
effort: tau_proto::ReasoningSelection::native(tau_proto::NativeReasoningEffort::Medium),
verbosity: Verbosity::Medium,
thinking_summary: tau_proto::ThinkingSummary::Auto,
service_tier: Some(tau_proto::ServiceTier::Fast),
}),
}));
renderer.handle(&Event::SessionStarted(SessionStarted {
session_id: test_session_id("s3"),
reason: SessionStartReason::Initial,
}));
sync(&handle);
assert!(vt.screen_contains(80, "+engineer ^0.25→low ~high !off"));
}
#[test]
fn pending_agent_response_stays_above_watched_agent_rows() {
for response_arrives_first in [true, false] {
let (_term, handle, vt) = setup(100, 24);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.switch_agent(agent_id("main"));
let render_response = |renderer: &mut EventRenderer| {
renderer.handle(&Event::AgentPromptCreated(agent_prompt_created(
"sp-pending",
"s1",
)));
renderer.handle(&Event::ProviderResponseUpdated(
provider_response_delta_update(
test_agent_prompt_id("sp-pending"),
"pending agent response",
None,
tau_proto::PromptOriginator::User,
),
));
};
let render_watch = |renderer: &mut EventRenderer| {
renderer.handle(&Event::AgentWatchesUpdated(
tau_proto::AgentWatchesUpdated {
session_id: test_session_id("s1"),
watcher_id: agent_id("main"),
watched_agent_ids: vec![agent_id("engineer_1")],
changed_agent_id: Some(agent_id("engineer_1")),
cause: tau_proto::AgentWatchUpdateCause::AgentWatchEnable,
},
));
};
if response_arrives_first {
render_response(&mut renderer);
render_watch(&mut renderer);
} else {
render_watch(&mut renderer);
render_response(&mut renderer);
}
sync(&handle);
let screen = vt.screen_text(100);
let response = screen
.iter()
.position(|line| line.contains("pending agent response"))
.unwrap_or_else(|| panic!("missing pending response in {screen:?}"));
let watched = screen
.iter()
.position(|line| line.contains("❓💤 @engineer_1"))
.unwrap_or_else(|| panic!("missing watched row in {screen:?}"));
assert!(
response < watched,
"pending response should stay above watched rows: {screen:?}"
);
}
}
#[test]
fn watched_agent_recursive_rows_keep_attribution_and_distinct_witness_context() {
let (_term, handle, vt) = setup(100, 24);
let mut renderer = EventRenderer::new(
handle.clone(),
tau_cli_term::CompletionData::new(),
cli_test_theme(),
);
renderer.handle(&Event::SessionStarted(tau_proto::SessionStarted {
session_id: test_session_id("s1"),
reason: tau_proto::SessionStartReason::Initial,
}));
renderer.switch_agent(agent_id("manager"));
for (watcher, watched) in [("manager", "reviewer"), ("reviewer", "worker")] {
renderer.handle(&Event::AgentWatchesUpdated(
tau_proto::AgentWatchesUpdated {
session_id: test_session_id("s1"),
watcher_id: agent_id(watcher),
watched_agent_ids: vec![agent_id(watched)],
changed_agent_id: Some(agent_id(watched)),
cause: tau_proto::AgentWatchUpdateCause::AgentWatchEnable,
},
));
}
sync(&handle);
assert!(vt.screen_contains(100, "❓💤 @reviewer"));
assert!(
vt.screen_contains(100, "❓💤 @reviewer -> @worker"),
"topology-only indirect rows must remain visible without stats"
);
let prompt_started = |agent: &str| {
Event::AgentPromptStarted(tau_proto::AgentPromptStarted {
model_params: Some(tau_proto::ModelParams::default()),
outer_turn_id: None,
session_id: test_session_id("s1"),
agent_id: agent_id(agent),
agent_prompt_id: test_agent_prompt_id(format!("ap-{agent}")),
model: "test/model".parse().expect("model id"),
operation: tau_proto::PromptOperation::Inference,
originator: tau_proto::PromptOriginator::User,
ctx_id: None,
})
};
renderer.handle(&prompt_started("worker"));
sync(&handle);
assert!(vt.screen_contains(100, "❓💤 @reviewer watching -> @worker"));
assert!(
vt.screen_contains(100, "❓✨ @reviewer -> @worker"),
"parent-first attribution and direct running state must coexist"
);
renderer.handle(&prompt_started("reviewer"));
sync(&handle);
assert!(vt.screen_contains(100, "❓✨ @reviewer"));
assert!(vt.screen_contains(100, "❓✨ @reviewer -> @worker"));
assert!(
!vt.screen_contains(100, "@reviewer watching -> @worker"),
"direct-running state must replace the transitive witness"
);
}
#[test]
fn info_chip_agent_classification_is_strict() {
use tau_proto::{ToolUseState, ToolUseStatus};
let display = render_tool_use_state(
"tool",
&ToolUseState {
info_chips: vec!["@engineer_child".into(), "@not an agent".into()],
status: ToolUseStatus::Success,
status_text: "ok".into(),
..Default::default()
},
);
assert!(matches!(display.suffixes[0].status, ToolStatus::Agent));
assert!(matches!(display.suffixes[1].status, ToolStatus::Info));
}
#[test]
fn watched_agent_indirect_attribution_respects_width_priorities() {
let theme = cli_test_theme();
let status = tau_proto::AgentWatchWorkStatusNotification {
session_id: test_session_id("s1"),
subscription_id: "watch-1".to_owned(),
status_epoch: tau_proto::AgentWorkStatusEpoch::from_raw(1),
phase: tau_proto::AgentWorkStatusPhase::Working,
title: Some("review changes".to_owned()),
initial: false,
};
let stats = tau_proto::AgentStatsUpdated {
session_id: test_session_id("s1"),
agent_id: agent_id("worker-with-long-id"),
navigation_mode: tau_proto::AgentNavigationMode::Active,
runtime_state: tau_proto::AgentRuntimeState::Idle,
turn_activity: tau_proto::AgentTurnActivity::Idle,
tools: tau_proto::AgentToolStats {
in_flight: 0,
started_total: 1,
},
context: tau_proto::AgentContextStats {
input_tokens: None,
cached_tokens: None,
context_window: None,
percent_used: Some(67),
},
inner_turns_total: None,
estimated_api_cost: Default::default(),
creator_subtree_estimated_api_cost: Default::default(),
work_status: Default::default(),
};
let display = watched_agent_tool_display(
Some("worker name"),
"worker-with-long-id",
Some("reviewer"),
Some(&stats),
WatchedAgentActivity::Idle,
Some(&status),
);
let block = render_tool_block(&theme, &display);
assert_eq!(
priority_header_text(&block, 100),
"🚀💤 @reviewer -> @worker-with-long-id (worker name) review changes %1/1 #67%"
);
let cells = priority_header_cells(&block, 100);
let watched_identity_start = cells
.iter()
.rposition(|cell| cell.ch == '@')
.expect("recursive watched descendant identity");
assert_eq!(
cells[watched_identity_start].style,
tau_cli_term::resolve::resolve(&theme, tau_themes::names::WATCHING_NAME),
"recursive watched descendant identity uses the watched-agent style"
);
let arrow_start = cells
.iter()
.position(|cell| cell.ch == '-')
.expect("recursive watch relationship marker");
assert_eq!(
cells[arrow_start].style,
tau_cli_term::resolve::resolve(&theme, tau_themes::names::TOOL_STATUS_INFO),
"recursive watch relationship marker remains agent-context metadata"
);
let boundary = priority_header_text(&block, 39);
assert_eq!(boundary, "🚀💤 @r┄er -> @worker-with-long-id %1/1");
assert!(boundary.starts_with("🚀💤 @"), "{boundary:?}");
for width in [12, 16, 24, 40] {
let text = priority_header_text(&block, width);
assert!(text.starts_with("🚀💤 @"), "{width}: {text:?}");
assert!(
tau_term_screen::display_width(&text) <= width,
"{width}: {text:?}"
);
}
assert_eq!(priority_header_text(&block, 24), "🚀💤 @reviewer");
}