Agent log screen: show agent output, not hook log

spawn_agent() now returns SpawnResult { pid, log_path } so the
log path is known at spawn time. No more filesystem scanning.
AgentInfo carries log_path, TUI reads it directly.

F2 → Enter shows the actual agent log (stdout/stderr from the
poc-memory agent process), not the hook orchestration log.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-04-02 01:04:54 -04:00
parent 1c190a3925
commit a90bd4fd47
3 changed files with 65 additions and 79 deletions

View file

@ -1089,9 +1089,9 @@ impl App {
frame.render_widget(para, size);
}
fn draw_agent_log(&self, frame: &mut Frame, size: Rect, output_dir: &std::path::Path) {
fn draw_agent_log(&self, frame: &mut Frame, size: Rect, _output_dir: &std::path::Path) {
let name = AGENT_NAMES.get(self.agent_selected).unwrap_or(&"?");
let agent_dir = output_dir.join(name);
let agent = self.agent_state.iter().find(|a| a.name == *name);
let mut lines: Vec<Line> = Vec::new();
let section = Style::default().fg(Color::Yellow);
let hint = Style::default().fg(Color::DarkGray).add_modifier(Modifier::ITALIC);
@ -1101,73 +1101,42 @@ impl App {
lines.push(Line::styled(" (Esc/← back, PgUp/PgDn scroll)", hint));
lines.push(Line::raw(""));
// Show pid status
let live = crate::subconscious::knowledge::scan_pid_files(&agent_dir, 0);
if live.is_empty() {
lines.push(Line::styled(" Status: idle", Style::default().fg(Color::DarkGray)));
} else {
for (phase, pid) in &live {
// Show pid status from state
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(" Status: ", Style::default()),
Span::styled(format!("● running pid {} phase: {}", pid, phase),
Style::default().fg(Color::Green)),
]));
}
}
lines.push(Line::raw(""));
// Show output files
lines.push(Line::styled("── Output Files ──", section));
let mut files: Vec<_> = std::fs::read_dir(&agent_dir)
.into_iter().flatten().flatten()
.filter(|e| {
let n = e.file_name().to_string_lossy().to_string();
!n.starts_with("pid-") && !n.starts_with("transcript-offset")
&& !n.starts_with("chunks-") && !n.starts_with("seen")
})
.collect();
files.sort_by_key(|e| std::cmp::Reverse(
e.metadata().ok().and_then(|m| m.modified().ok())
.unwrap_or(std::time::SystemTime::UNIX_EPOCH)
));
for entry in files.iter().take(10) {
let name = entry.file_name().to_string_lossy().to_string();
let ago = entry.metadata().ok()
.and_then(|m| m.modified().ok())
.and_then(|t| t.elapsed().ok())
.map(|d| Self::format_duration(d))
.unwrap_or_else(|| "?".into());
let size = entry.metadata().ok().map(|m| m.len()).unwrap_or(0);
lines.push(Line::raw(format!(" {:<30} {:>6}B {}", name, size, ago)));
}
// Show hook log tail
lines.push(Line::raw(""));
lines.push(Line::styled("── Hook Log ──", section));
let log_dir = dirs::home_dir().unwrap_or_default().join(".consciousness/logs");
// Find latest hook log
if let Ok(mut entries) = std::fs::read_dir(&log_dir) {
let mut logs: Vec<_> = entries.by_ref().flatten()
.filter(|e| e.file_name().to_string_lossy().starts_with("hook-"))
.collect();
logs.sort_by_key(|e| std::cmp::Reverse(
e.metadata().ok().and_then(|m| m.modified().ok())
.unwrap_or(std::time::SystemTime::UNIX_EPOCH)
));
if let Some(log_entry) = logs.first() {
if let Ok(content) = std::fs::read_to_string(log_entry.path()) {
// Show last ~30 lines
let log_lines: Vec<&str> = content.lines().collect();
let start = log_lines.len().saturating_sub(30);
for line in &log_lines[start..] {
lines.push(Line::raw(format!(" {}", line)));
}
}
None => {
lines.push(Line::styled(" Status: idle", Style::default().fg(Color::DarkGray)));
}
}
// Show log path
if let Some(log_path) = agent.and_then(|a| a.log_path.as_ref()) {
lines.push(Line::raw(format!(" Log: {}", log_path.display())));
}
lines.push(Line::raw(""));
// Show agent log tail
lines.push(Line::styled("── Agent Log ──", section));
if let Some(content) = agent
.and_then(|a| a.log_path.as_ref())
.and_then(|p| std::fs::read_to_string(p).ok())
{
let log_lines: Vec<&str> = content.lines().collect();
let start = log_lines.len().saturating_sub(40);
for line in &log_lines[start..] {
lines.push(Line::raw(format!(" {}", line)));
}
} else {
lines.push(Line::styled(" (no log available)", hint));
}
let block = Block::default()
.title_top(Line::from(SCREEN_LEGEND).left_aligned())
.title_top(Line::from(format!(" {} ", name)).right_aligned())