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:
parent
1c190a3925
commit
a90bd4fd47
3 changed files with 65 additions and 79 deletions
|
|
@ -1089,9 +1089,9 @@ impl App {
|
||||||
frame.render_widget(para, size);
|
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 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 mut lines: Vec<Line> = Vec::new();
|
||||||
let section = Style::default().fg(Color::Yellow);
|
let section = Style::default().fg(Color::Yellow);
|
||||||
let hint = Style::default().fg(Color::DarkGray).add_modifier(Modifier::ITALIC);
|
let hint = Style::default().fg(Color::DarkGray).add_modifier(Modifier::ITALIC);
|
||||||
|
|
@ -1101,71 +1101,40 @@ impl App {
|
||||||
lines.push(Line::styled(" (Esc/← back, PgUp/PgDn scroll)", hint));
|
lines.push(Line::styled(" (Esc/← back, PgUp/PgDn scroll)", hint));
|
||||||
lines.push(Line::raw(""));
|
lines.push(Line::raw(""));
|
||||||
|
|
||||||
// Show pid status
|
// Show pid status from state
|
||||||
let live = crate::subconscious::knowledge::scan_pid_files(&agent_dir, 0);
|
match agent.and_then(|a| a.pid) {
|
||||||
if live.is_empty() {
|
Some(pid) => {
|
||||||
lines.push(Line::styled(" Status: idle", Style::default().fg(Color::DarkGray)));
|
let phase = agent.and_then(|a| a.phase.as_deref()).unwrap_or("?");
|
||||||
} else {
|
|
||||||
for (phase, pid) in &live {
|
|
||||||
lines.push(Line::from(vec![
|
lines.push(Line::from(vec![
|
||||||
Span::styled(" Status: ", Style::default()),
|
Span::styled(" Status: ", Style::default()),
|
||||||
Span::styled(format!("● running pid {} phase: {}", pid, phase),
|
Span::styled(format!("● running pid {} phase: {}", pid, phase),
|
||||||
Style::default().fg(Color::Green)),
|
Style::default().fg(Color::Green)),
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
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(""));
|
lines.push(Line::raw(""));
|
||||||
|
|
||||||
// Show output files
|
// Show agent log tail
|
||||||
lines.push(Line::styled("── Output Files ──", section));
|
lines.push(Line::styled("── Agent Log ──", section));
|
||||||
let mut files: Vec<_> = std::fs::read_dir(&agent_dir)
|
if let Some(content) = agent
|
||||||
.into_iter().flatten().flatten()
|
.and_then(|a| a.log_path.as_ref())
|
||||||
.filter(|e| {
|
.and_then(|p| std::fs::read_to_string(p).ok())
|
||||||
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 log_lines: Vec<&str> = content.lines().collect();
|
||||||
let start = log_lines.len().saturating_sub(30);
|
let start = log_lines.len().saturating_sub(40);
|
||||||
for line in &log_lines[start..] {
|
for line in &log_lines[start..] {
|
||||||
lines.push(Line::raw(format!(" {}", line)));
|
lines.push(Line::raw(format!(" {}", line)));
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
}
|
lines.push(Line::styled(" (no log available)", hint));
|
||||||
}
|
}
|
||||||
|
|
||||||
let block = Block::default()
|
let block = Block::default()
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,8 @@ pub struct AgentInfo {
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
pub pid: Option<u32>,
|
pub pid: Option<u32>,
|
||||||
pub phase: Option<String>,
|
pub phase: Option<String>,
|
||||||
|
/// Path to the most recent agent log file.
|
||||||
|
pub log_path: Option<std::path::PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Persistent state for the agent orchestration cycle.
|
/// Persistent state for the agent orchestration cycle.
|
||||||
|
|
@ -164,7 +166,7 @@ impl AgentCycleState {
|
||||||
.create(true).append(true).open(log_path).ok();
|
.create(true).append(true).open(log_path).ok();
|
||||||
|
|
||||||
let agents = AGENT_CYCLE_NAMES.iter()
|
let agents = AGENT_CYCLE_NAMES.iter()
|
||||||
.map(|&name| AgentInfo { name, pid: None, phase: None })
|
.map(|&name| AgentInfo { name, pid: None, phase: None, log_path: None })
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
AgentCycleState {
|
AgentCycleState {
|
||||||
|
|
@ -185,10 +187,14 @@ impl AgentCycleState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_agent(&mut self, name: &str, pid: Option<u32>, phase: Option<String>) {
|
fn update_agent(&mut self, name: &str, pid: Option<u32>, phase: Option<String>,
|
||||||
|
log_path: Option<std::path::PathBuf>) {
|
||||||
if let Some(agent) = self.agents.iter_mut().find(|a| a.name == name) {
|
if let Some(agent) = self.agents.iter_mut().find(|a| a.name == name) {
|
||||||
agent.pid = pid;
|
agent.pid = pid;
|
||||||
agent.phase = phase;
|
agent.phase = phase;
|
||||||
|
if log_path.is_some() {
|
||||||
|
agent.log_path = log_path;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -266,10 +272,10 @@ impl AgentCycleState {
|
||||||
|
|
||||||
let live = crate::agents::knowledge::scan_pid_files(&state_dir, timeout);
|
let live = crate::agents::knowledge::scan_pid_files(&state_dir, timeout);
|
||||||
if let Some((phase, pid)) = live.first() {
|
if let Some((phase, pid)) = live.first() {
|
||||||
self.update_agent("surface-observe", Some(*pid), Some(phase.clone()));
|
self.update_agent("surface-observe", Some(*pid), Some(phase.clone()), None);
|
||||||
self.log(format_args!("alive pid-{}: phase={}\n", pid, phase));
|
self.log(format_args!("alive pid-{}: phase={}\n", pid, phase));
|
||||||
} else {
|
} else {
|
||||||
self.update_agent("surface-observe", None, None);
|
self.update_agent("surface-observe", None, None, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read surfaced keys
|
// Read surfaced keys
|
||||||
|
|
@ -304,11 +310,12 @@ impl AgentCycleState {
|
||||||
if transcript.size > 0 {
|
if transcript.size > 0 {
|
||||||
fs::write(&offset_path, transcript.size.to_string()).ok();
|
fs::write(&offset_path, transcript.size.to_string()).ok();
|
||||||
}
|
}
|
||||||
let pid = crate::agents::knowledge::spawn_agent(
|
let spawned = crate::agents::knowledge::spawn_agent(
|
||||||
"surface-observe", &state_dir, &session.session_id);
|
"surface-observe", &state_dir, &session.session_id);
|
||||||
self.update_agent("surface-observe",
|
self.update_agent("surface-observe",
|
||||||
pid, Some("surface".into()));
|
spawned.as_ref().map(|s| s.pid), Some("surface".into()),
|
||||||
self.log(format_args!("spawned agent {:?}\n", pid));
|
spawned.as_ref().map(|s| s.log_path.clone()));
|
||||||
|
self.log(format_args!("spawned agent {:?}\n", spawned.as_ref().map(|s| s.pid)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait if agent is significantly behind
|
// Wait if agent is significantly behind
|
||||||
|
|
@ -353,7 +360,7 @@ impl AgentCycleState {
|
||||||
|
|
||||||
let live = crate::agents::knowledge::scan_pid_files(&state_dir, 300);
|
let live = crate::agents::knowledge::scan_pid_files(&state_dir, 300);
|
||||||
if let Some((phase, pid)) = live.first() {
|
if let Some((phase, pid)) = live.first() {
|
||||||
self.update_agent("reflect", Some(*pid), Some(phase.clone()));
|
self.update_agent("reflect", Some(*pid), Some(phase.clone()), None);
|
||||||
self.log(format_args!("reflect: already running pid {}\n", pid));
|
self.log(format_args!("reflect: already running pid {}\n", pid));
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
@ -373,10 +380,12 @@ impl AgentCycleState {
|
||||||
}
|
}
|
||||||
|
|
||||||
fs::write(&offset_path, transcript.size.to_string()).ok();
|
fs::write(&offset_path, transcript.size.to_string()).ok();
|
||||||
let pid = crate::agents::knowledge::spawn_agent(
|
let spawned = crate::agents::knowledge::spawn_agent(
|
||||||
"reflect", &state_dir, &session.session_id);
|
"reflect", &state_dir, &session.session_id);
|
||||||
self.update_agent("reflect", pid, Some("step-0".into()));
|
self.update_agent("reflect",
|
||||||
self.log(format_args!("reflect: spawned {:?}\n", pid));
|
spawned.as_ref().map(|s| s.pid), Some("step-0".into()),
|
||||||
|
spawned.as_ref().map(|s| s.log_path.clone()));
|
||||||
|
self.log(format_args!("reflect: spawned {:?}\n", spawned.as_ref().map(|s| s.pid)));
|
||||||
|
|
||||||
reflection
|
reflection
|
||||||
}
|
}
|
||||||
|
|
@ -397,16 +406,18 @@ impl AgentCycleState {
|
||||||
|
|
||||||
let live = crate::agents::knowledge::scan_pid_files(&state_dir, 300);
|
let live = crate::agents::knowledge::scan_pid_files(&state_dir, 300);
|
||||||
if let Some((phase, pid)) = live.first() {
|
if let Some((phase, pid)) = live.first() {
|
||||||
self.update_agent("journal", Some(*pid), Some(phase.clone()));
|
self.update_agent("journal", Some(*pid), Some(phase.clone()), None);
|
||||||
self.log(format_args!("journal: already running pid {}\n", pid));
|
self.log(format_args!("journal: already running pid {}\n", pid));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fs::write(&offset_path, transcript.size.to_string()).ok();
|
fs::write(&offset_path, transcript.size.to_string()).ok();
|
||||||
let pid = crate::agents::knowledge::spawn_agent(
|
let spawned = crate::agents::knowledge::spawn_agent(
|
||||||
"journal", &state_dir, &session.session_id);
|
"journal", &state_dir, &session.session_id);
|
||||||
self.update_agent("journal", pid, Some("step-0".into()));
|
self.update_agent("journal",
|
||||||
self.log(format_args!("journal: spawned {:?}\n", pid));
|
spawned.as_ref().map(|s| s.pid), Some("step-0".into()),
|
||||||
|
spawned.as_ref().map(|s| s.log_path.clone()));
|
||||||
|
self.log(format_args!("journal: spawned {:?}\n", spawned.as_ref().map(|s| s.pid)));
|
||||||
}
|
}
|
||||||
} // end impl AgentCycleState (cycle methods)
|
} // end impl AgentCycleState (cycle methods)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -252,11 +252,17 @@ pub fn scan_pid_files(state_dir: &std::path::Path, timeout_secs: u64) -> Vec<(St
|
||||||
|
|
||||||
/// Spawn an agent asynchronously. Writes the pid file before returning
|
/// Spawn an agent asynchronously. Writes the pid file before returning
|
||||||
/// so the caller immediately sees the agent as running.
|
/// so the caller immediately sees the agent as running.
|
||||||
|
/// Spawn result: pid and path to the agent's log file.
|
||||||
|
pub struct SpawnResult {
|
||||||
|
pub pid: u32,
|
||||||
|
pub log_path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn spawn_agent(
|
pub fn spawn_agent(
|
||||||
agent_name: &str,
|
agent_name: &str,
|
||||||
state_dir: &std::path::Path,
|
state_dir: &std::path::Path,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
) -> Option<u32> {
|
) -> Option<SpawnResult> {
|
||||||
let def = super::defs::get_def(agent_name)?;
|
let def = super::defs::get_def(agent_name)?;
|
||||||
let first_phase = def.steps.first()
|
let first_phase = def.steps.first()
|
||||||
.map(|s| s.phase.as_str())
|
.map(|s| s.phase.as_str())
|
||||||
|
|
@ -265,8 +271,8 @@ pub fn spawn_agent(
|
||||||
let log_dir = dirs::home_dir().unwrap_or_default()
|
let log_dir = dirs::home_dir().unwrap_or_default()
|
||||||
.join(format!(".consciousness/logs/{}", agent_name));
|
.join(format!(".consciousness/logs/{}", agent_name));
|
||||||
fs::create_dir_all(&log_dir).ok();
|
fs::create_dir_all(&log_dir).ok();
|
||||||
let agent_log = fs::File::create(
|
let log_path = log_dir.join(format!("{}.log", store::compact_timestamp()));
|
||||||
log_dir.join(format!("{}.log", store::compact_timestamp())))
|
let agent_log = fs::File::create(&log_path)
|
||||||
.unwrap_or_else(|_| fs::File::create("/dev/null").unwrap());
|
.unwrap_or_else(|_| fs::File::create("/dev/null").unwrap());
|
||||||
|
|
||||||
let child = std::process::Command::new("poc-memory")
|
let child = std::process::Command::new("poc-memory")
|
||||||
|
|
@ -281,7 +287,7 @@ pub fn spawn_agent(
|
||||||
let pid = child.id();
|
let pid = child.id();
|
||||||
let pid_path = state_dir.join(format!("pid-{}", pid));
|
let pid_path = state_dir.join(format!("pid-{}", pid));
|
||||||
fs::write(&pid_path, first_phase).ok();
|
fs::write(&pid_path, first_phase).ok();
|
||||||
Some(pid)
|
Some(SpawnResult { pid, log_path })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_one_agent_inner(
|
fn run_one_agent_inner(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue