Wire AgentCycleState through runner and TUI

Runner owns AgentCycleState, calls trigger() on each user message
instead of the old run_hook() JSON round-trip. Sends AgentUpdate
messages to TUI after each cycle.

TUI F2 screen reads agent state from messages instead of scanning
the filesystem on every frame. HookSession::from_fields() lets
poc-agent construct sessions without JSON serialization.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-04-02 00:52:57 -04:00
parent d097c8e067
commit 1c190a3925
5 changed files with 48 additions and 38 deletions

View file

@ -348,6 +348,8 @@ pub struct App {
agent_selected: usize,
/// Agent screen: viewing log for selected agent.
agent_log_view: bool,
/// Agent state from last cycle update.
agent_state: Vec<crate::subconscious::hook::AgentInfo>,
}
/// Overlay screens toggled by F-keys.
@ -410,6 +412,7 @@ impl App {
shared_context,
agent_selected: 0,
agent_log_view: false,
agent_state: Vec::new(),
}
}
@ -508,6 +511,9 @@ impl App {
UiMessage::ContextInfoUpdate(info) => {
self.context_info = Some(info);
}
UiMessage::AgentUpdate(agents) => {
self.agent_state = agents;
}
}
}
@ -1047,41 +1053,27 @@ impl App {
lines.push(Line::raw(""));
for (i, &name) in AGENT_NAMES.iter().enumerate() {
let agent_dir = output_dir.join(name);
let live = crate::subconscious::knowledge::scan_pid_files(&agent_dir, 0);
let selected = i == self.agent_selected;
let prefix = if selected { "" } else { " " };
let bg = if selected { Style::default().bg(Color::DarkGray) } else { Style::default() };
if live.is_empty() {
lines.push(Line::from(vec![
Span::styled(format!("{}{:<20}", prefix, name), bg.fg(Color::Gray)),
Span::styled("○ idle", bg.fg(Color::DarkGray)),
]));
} else {
for (phase, pid) in &live {
let agent = self.agent_state.iter().find(|a| a.name == name);
match agent.and_then(|a| a.pid) {
Some(pid) => {
let phase = agent.and_then(|a| a.phase.as_deref()).unwrap_or("?");
lines.push(Line::from(vec![
Span::styled(format!("{}{:<20}", prefix, name), bg.fg(Color::Green)),
Span::styled("", bg.fg(Color::Green)),
Span::styled(format!("pid {} phase: {}", pid, phase), bg),
]));
}
}
}
// Recent output
lines.push(Line::raw(""));
lines.push(Line::styled("── Recent Activity ──", section));
lines.push(Line::raw(""));
for &name in AGENT_NAMES {
let agent_dir = output_dir.join(name);
if let Some((file, ago)) = Self::most_recent_file(&agent_dir) {
lines.push(Line::from(vec![
Span::styled(format!(" {:<20}", name), dim),
Span::raw(format!("{} ({})", file, ago)),
]));
None => {
lines.push(Line::from(vec![
Span::styled(format!("{}{:<20}", prefix, name), bg.fg(Color::Gray)),
Span::styled("○ idle", bg.fg(Color::DarkGray)),
]));
}
}
}