wire thalamus idle state into consciousness binary

The consciousness binary now has its own idle state machine,
fed directly by TUI events:
- Key press → user_activity()
- Turn complete → response_activity()
- Render tick → decay_ewma(), snapshot to TUI

F5 thalamus screen shows presence/activity from the in-process
state instead of shelling out to poc-daemon status. No tmux
pane scraping, no socket RPC — the binary IS the presence.

Co-Developed-By: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
ProofOfConcept 2026-04-03 19:30:26 -04:00
parent e49b235957
commit e7be2a3ba0
3 changed files with 66 additions and 28 deletions

View file

@ -370,6 +370,19 @@ pub struct App {
pub(crate) agent_state: Vec<crate::subconscious::subconscious::AgentSnapshot>,
/// Cached channel info for F5 screen (refreshed on status tick).
pub(crate) channel_status: Vec<ChannelStatus>,
/// Cached idle state for F5 screen.
pub(crate) idle_info: Option<IdleInfo>,
}
/// Snapshot of thalamus idle state for display.
#[derive(Clone)]
pub(crate) struct IdleInfo {
pub user_present: bool,
pub since_activity: f64,
pub activity_ewma: f64,
pub block_reason: String,
pub dreaming: bool,
pub sleeping: bool,
}
/// Channel info for display on F5 screen.
@ -450,6 +463,7 @@ impl App {
agent_log_view: false,
agent_state: Vec::new(),
channel_status: Vec::new(),
idle_info: None,
}
}
@ -885,12 +899,25 @@ impl App {
self.channel_status = status;
}
/// Snapshot idle state for F5 display.
pub fn update_idle(&mut self, state: &crate::thalamus::idle::State) {
self.idle_info = Some(IdleInfo {
user_present: state.user_present(),
since_activity: state.since_activity(),
activity_ewma: state.activity_ewma,
block_reason: state.block_reason().to_string(),
dreaming: state.dreaming,
sleeping: state.sleep_until.is_some(),
});
}
pub(crate) fn set_screen(&mut self, screen: Screen) {
self.screen = screen;
self.debug_scroll = 0;
// Refresh data for status screens on entry
match screen {
Screen::Thalamus => self.refresh_channels(),
// idle_info is updated from the event loop, not here
_ => {}
}
}

View file

@ -1,7 +1,7 @@
// thalamus_screen.rs — F5: attention routing and channel status
// thalamus_screen.rs — F5: presence, idle state, and channel status
//
// Shows presence/idle/activity status, then channel status.
// Channel data is cached on App and refreshed on screen entry.
// Shows idle state from the in-process thalamus (no subprocess spawn),
// then channel daemon status from cached data.
use ratatui::{
layout::Rect,
@ -13,38 +13,40 @@ use ratatui::{
use super::{App, SCREEN_LEGEND};
fn fetch_daemon_status() -> Vec<String> {
std::process::Command::new("poc-daemon")
.arg("status")
.output()
.ok()
.and_then(|o| {
if o.status.success() {
String::from_utf8(o.stdout).ok()
} else {
None
}
})
.map(|s| s.lines().map(String::from).collect())
.unwrap_or_default()
}
impl App {
pub(crate) fn draw_thalamus(&self, frame: &mut Frame, size: Rect) {
let section = Style::default().fg(Color::Yellow);
let dim = Style::default().fg(Color::DarkGray);
let mut lines: Vec<Line> = Vec::new();
// Presence status
let daemon_status = fetch_daemon_status();
if !daemon_status.is_empty() {
lines.push(Line::styled("── Presence ──", section));
lines.push(Line::raw(""));
for line in &daemon_status {
lines.push(Line::raw(format!(" {}", line)));
// Presence / idle state from in-process thalamus
lines.push(Line::styled("── Presence ──", section));
lines.push(Line::raw(""));
if let Some(ref idle) = self.idle_info {
let presence = if idle.user_present {
Span::styled("present", Style::default().fg(Color::Green))
} else {
Span::styled("away", Style::default().fg(Color::DarkGray))
};
lines.push(Line::from(vec![
Span::raw(" User: "),
presence,
Span::raw(format!(" (last {:.0}s ago)", idle.since_activity)),
]));
lines.push(Line::raw(format!(" Activity: {:.1}%", idle.activity_ewma * 100.0)));
lines.push(Line::raw(format!(" Idle state: {}", idle.block_reason)));
if idle.dreaming {
lines.push(Line::styled(" ◆ dreaming", Style::default().fg(Color::Magenta)));
}
lines.push(Line::raw(""));
if idle.sleeping {
lines.push(Line::styled(" ◆ sleeping", Style::default().fg(Color::Blue)));
}
} else {
lines.push(Line::styled(" not initialized", dim));
}
lines.push(Line::raw(""));
// Channel status from cached data
lines.push(Line::styled("── Channels ──", section));