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:
parent
e49b235957
commit
e7be2a3ba0
3 changed files with 66 additions and 28 deletions
|
|
@ -810,6 +810,10 @@ async fn run(cli: cli::CliArgs) -> Result<()> {
|
||||||
channel_supervisor.load_config();
|
channel_supervisor.load_config();
|
||||||
channel_supervisor.ensure_running();
|
channel_supervisor.ensure_running();
|
||||||
|
|
||||||
|
// Initialize idle state machine
|
||||||
|
let mut idle_state = poc_memory::thalamus::idle::State::new();
|
||||||
|
idle_state.load();
|
||||||
|
|
||||||
// Create UI channel
|
// Create UI channel
|
||||||
let (ui_tx, mut ui_rx) = ui_channel::channel();
|
let (ui_tx, mut ui_rx) = ui_channel::channel();
|
||||||
|
|
||||||
|
|
@ -935,6 +939,7 @@ async fn run(cli: cli::CliArgs) -> Result<()> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
app.handle_key(key);
|
app.handle_key(key);
|
||||||
|
idle_state.user_activity();
|
||||||
dirty = true;
|
dirty = true;
|
||||||
}
|
}
|
||||||
Some(Ok(Event::Mouse(mouse))) => {
|
Some(Ok(Event::Mouse(mouse))) => {
|
||||||
|
|
@ -961,16 +966,20 @@ async fn run(cli: cli::CliArgs) -> Result<()> {
|
||||||
// Turn completed in background task
|
// Turn completed in background task
|
||||||
Some((result, target)) = turn_rx.recv() => {
|
Some((result, target)) = turn_rx.recv() => {
|
||||||
session.handle_turn_result(result, target).await;
|
session.handle_turn_result(result, target).await;
|
||||||
|
idle_state.response_activity();
|
||||||
dirty = true;
|
dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render tick
|
// Render tick — update periodic state
|
||||||
_ = render_interval.tick() => {
|
_ = render_interval.tick() => {
|
||||||
let new_count = session.process_tracker.list().await.len() as u32;
|
let new_count = session.process_tracker.list().await.len() as u32;
|
||||||
if new_count != app.running_processes {
|
if new_count != app.running_processes {
|
||||||
app.running_processes = new_count;
|
app.running_processes = new_count;
|
||||||
dirty = true;
|
dirty = true;
|
||||||
}
|
}
|
||||||
|
// Update idle state for F5 screen
|
||||||
|
idle_state.decay_ewma();
|
||||||
|
app.update_idle(&idle_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
// DMN timer (only when no turn is running)
|
// DMN timer (only when no turn is running)
|
||||||
|
|
|
||||||
|
|
@ -370,6 +370,19 @@ pub struct App {
|
||||||
pub(crate) agent_state: Vec<crate::subconscious::subconscious::AgentSnapshot>,
|
pub(crate) agent_state: Vec<crate::subconscious::subconscious::AgentSnapshot>,
|
||||||
/// Cached channel info for F5 screen (refreshed on status tick).
|
/// Cached channel info for F5 screen (refreshed on status tick).
|
||||||
pub(crate) channel_status: Vec<ChannelStatus>,
|
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.
|
/// Channel info for display on F5 screen.
|
||||||
|
|
@ -450,6 +463,7 @@ impl App {
|
||||||
agent_log_view: false,
|
agent_log_view: false,
|
||||||
agent_state: Vec::new(),
|
agent_state: Vec::new(),
|
||||||
channel_status: Vec::new(),
|
channel_status: Vec::new(),
|
||||||
|
idle_info: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -885,12 +899,25 @@ impl App {
|
||||||
self.channel_status = status;
|
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) {
|
pub(crate) fn set_screen(&mut self, screen: Screen) {
|
||||||
self.screen = screen;
|
self.screen = screen;
|
||||||
self.debug_scroll = 0;
|
self.debug_scroll = 0;
|
||||||
// Refresh data for status screens on entry
|
// Refresh data for status screens on entry
|
||||||
match screen {
|
match screen {
|
||||||
Screen::Thalamus => self.refresh_channels(),
|
Screen::Thalamus => self.refresh_channels(),
|
||||||
|
// idle_info is updated from the event loop, not here
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
// Shows idle state from the in-process thalamus (no subprocess spawn),
|
||||||
// Channel data is cached on App and refreshed on screen entry.
|
// then channel daemon status from cached data.
|
||||||
|
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::Rect,
|
layout::Rect,
|
||||||
|
|
@ -13,38 +13,40 @@ use ratatui::{
|
||||||
|
|
||||||
use super::{App, SCREEN_LEGEND};
|
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 {
|
impl App {
|
||||||
pub(crate) fn draw_thalamus(&self, frame: &mut Frame, size: Rect) {
|
pub(crate) fn draw_thalamus(&self, frame: &mut Frame, size: Rect) {
|
||||||
let section = Style::default().fg(Color::Yellow);
|
let section = Style::default().fg(Color::Yellow);
|
||||||
let dim = Style::default().fg(Color::DarkGray);
|
let dim = Style::default().fg(Color::DarkGray);
|
||||||
let mut lines: Vec<Line> = Vec::new();
|
let mut lines: Vec<Line> = Vec::new();
|
||||||
|
|
||||||
// Presence status
|
// Presence / idle state from in-process thalamus
|
||||||
let daemon_status = fetch_daemon_status();
|
|
||||||
if !daemon_status.is_empty() {
|
|
||||||
lines.push(Line::styled("── Presence ──", section));
|
lines.push(Line::styled("── Presence ──", section));
|
||||||
lines.push(Line::raw(""));
|
lines.push(Line::raw(""));
|
||||||
for line in &daemon_status {
|
|
||||||
lines.push(Line::raw(format!(" {}", line)));
|
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)));
|
||||||
|
}
|
||||||
|
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(""));
|
lines.push(Line::raw(""));
|
||||||
}
|
|
||||||
|
|
||||||
// Channel status from cached data
|
// Channel status from cached data
|
||||||
lines.push(Line::styled("── Channels ──", section));
|
lines.push(Line::styled("── Channels ──", section));
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue