mod driver;
pub use driver::run;
use std::collections::HashSet;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use pixtuoid_core::state::{ActivityState, MAX_FLOORS};
use pixtuoid_core::SceneState;
use tokio::sync::watch;
pub type SceneRx = watch::Receiver<Arc<SceneState>>;
const FALLBACK_DESKS: usize = 16;
pub struct RunConfig {
pub socket: Option<PathBuf>,
pub projects_root: Option<PathBuf>,
pub codex_sessions_root: Option<PathBuf>,
pub pack_dir: Option<PathBuf>,
pub desk_cap: Option<usize>,
pub headless: bool,
pub config_path: PathBuf,
pub theme: &'static crate::tui::theme::Theme,
pub pets: Vec<crate::tui::pet::Pet>,
pub connected: HashSet<String>,
}
#[derive(Clone, Default)]
pub struct ConnectedSources(Arc<Mutex<HashSet<String>>>);
impl ConnectedSources {
pub fn new(initial: HashSet<String>) -> Self {
Self(Arc::new(Mutex::new(initial)))
}
fn guard(&self) -> std::sync::MutexGuard<'_, HashSet<String>> {
self.0.lock().unwrap_or_else(|e| e.into_inner())
}
pub fn is_connected(&self, source_id: &str) -> bool {
self.guard().contains(source_id)
}
pub fn snapshot(&self) -> HashSet<String> {
self.guard().clone()
}
pub fn set(&self, source_id: &str, connected: bool) {
let mut g = self.guard();
if connected {
g.insert(source_id.to_string());
} else {
g.remove(source_id);
}
}
}
pub(crate) fn boot_capacities_for(cols: u16, rows: u16) -> [usize; MAX_FLOORS] {
std::array::from_fn(|i| {
let seed = (i as u64).wrapping_mul(crate::tui::floor::FLOOR_SEED_MULTIPLIER);
let cap = capacity_for_terminal(cols, rows, seed);
if cap == 0 {
FALLBACK_DESKS
} else {
cap
}
})
}
fn cap_boot_capacities(base: [usize; MAX_FLOORS], cap: Option<usize>) -> [usize; MAX_FLOORS] {
match cap {
Some(c) => base.map(|x| x.min(c)),
None => base,
}
}
pub(crate) fn capacity_for_terminal(cols: u16, rows: u16, floor_seed: u64) -> usize {
let buf_h = rows.saturating_sub(1) * 2;
pixtuoid_core::layout::SceneLayout::compute_with_seed(
cols,
buf_h,
pixtuoid_core::layout::MAX_VISIBLE_DESKS,
floor_seed,
)
.map(|l| l.home_desks.len())
.unwrap_or(0)
}
fn sanitize_line(s: &str) -> String {
s.chars().filter(|c| !c.is_control()).collect()
}
fn summarize(scene: &SceneState) -> String {
let agents: Vec<String> = scene
.agents
.values()
.map(|a| {
let state = match &a.state {
ActivityState::Idle => "idle".to_string(),
ActivityState::Active { detail, .. } => {
format!(
"active({})",
sanitize_line(detail.as_deref().unwrap_or("?"))
)
}
ActivityState::Waiting { reason } => {
format!("waiting({})", sanitize_line(reason))
}
};
format!("{}@{}:{}", sanitize_line(&a.label), a.desk_index.0, state)
})
.collect();
format!("agents=[{}]", agents.join(", "))
}
#[cfg(test)]
mod tests {
use super::*;
use pixtuoid_core::{Reducer, Transport};
use std::time::SystemTime;
fn floor_seed(i: u64) -> u64 {
i.wrapping_mul(crate::tui::floor::FLOOR_SEED_MULTIPLIER)
}
#[test]
fn capacity_for_normal_terminal() {
let cap = capacity_for_terminal(192, 48, 0);
assert!(cap > 0 && cap <= pixtuoid_core::layout::MAX_VISIBLE_DESKS);
}
#[test]
fn capacity_for_small_terminal() {
let cap = capacity_for_terminal(80, 35, 0);
assert!(cap > 0, "80x35 should fit at least one desk");
}
#[test]
fn capacity_for_tiny_terminal_returns_zero() {
assert_eq!(capacity_for_terminal(10, 10, 0), 0);
}
#[test]
fn capacity_for_zero_rows_returns_zero() {
assert_eq!(capacity_for_terminal(192, 0, 0), 0);
}
#[test]
fn capacity_matches_renderer_formula() {
let cols: u16 = 160;
let rows: u16 = 50;
let buf_h = rows.saturating_sub(1) * 2;
let expected = pixtuoid_core::layout::SceneLayout::compute_with_seed(
cols,
buf_h,
pixtuoid_core::layout::MAX_VISIBLE_DESKS,
0,
)
.map(|l| l.home_desks.len())
.unwrap_or(0);
assert_eq!(capacity_for_terminal(cols, rows, 0), expected);
}
#[test]
fn seed_can_produce_distinct_capacities() {
let mut found = false;
'outer: for cols in [120u16, 140, 160, 180, 200, 220, 240] {
for rows in [30u16, 36, 40, 48, 56, 64] {
let mut unique = std::collections::HashSet::new();
for i in 0..MAX_FLOORS as u64 {
unique.insert(capacity_for_terminal(cols, rows, floor_seed(i)));
}
if unique.len() > 1 {
found = true;
break 'outer;
}
}
}
assert!(
found,
"expected at least one terminal size in the swept range where \
per-floor seeds produce distinct capacities"
);
}
#[test]
fn boot_capacities_uses_each_floor_seed() {
let caps = boot_capacities_for(192, 48);
let expected: [usize; MAX_FLOORS] = std::array::from_fn(|i| {
let c = capacity_for_terminal(192, 48, floor_seed(i as u64));
if c == 0 {
FALLBACK_DESKS
} else {
c
}
});
assert_eq!(caps, expected);
}
#[test]
fn boot_capacities_falls_back_to_default_on_tiny_terminal() {
let caps = boot_capacities_for(10, 10);
assert_eq!(caps, [FALLBACK_DESKS; MAX_FLOORS]);
}
#[test]
fn summarize_reports_each_activity_state() {
use pixtuoid_core::source::AgentEvent;
use pixtuoid_core::AgentId;
let mut scene = SceneState::new([8; MAX_FLOORS]);
let mut reducer = Reducer::new();
let now = SystemTime::now();
let seat = |reducer: &mut Reducer, scene: &mut SceneState, id: AgentId| {
reducer.apply(
scene,
AgentEvent::SessionStart {
agent_id: id,
source: "claude-code".into(),
session_id: "s".into(),
cwd: std::path::PathBuf::from("/repo"),
parent_id: None,
},
now,
Transport::Hook,
);
};
let a = AgentId::from_transcript_path("/p/a.jsonl");
seat(&mut reducer, &mut scene, a);
reducer.apply(
&mut scene,
AgentEvent::ActivityStart {
agent_id: a,
tool_use_id: Some("t1".into()),
detail: Some("Edit: foo.rs".into()),
},
now,
Transport::Hook,
);
let b = AgentId::from_transcript_path("/p/b.jsonl");
seat(&mut reducer, &mut scene, b);
reducer.apply(
&mut scene,
AgentEvent::Waiting {
agent_id: b,
reason: "permission".into(),
},
now,
Transport::Hook,
);
let c = AgentId::from_transcript_path("/p/c.jsonl");
seat(&mut reducer, &mut scene, c);
let summary = summarize(&scene);
assert!(summary.starts_with("agents=["), "got: {summary}");
assert!(summary.contains("active(Edit: foo.rs)"), "got: {summary}");
assert!(summary.contains("waiting(permission)"), "got: {summary}");
assert!(summary.contains(":idle"), "got: {summary}");
assert!(summary.contains('@'), "got: {summary}");
}
#[test]
fn summarize_strips_terminal_escapes_from_untrusted_fields() {
use pixtuoid_core::source::AgentEvent;
use pixtuoid_core::AgentId;
let mut scene = SceneState::new([8; MAX_FLOORS]);
let mut reducer = Reducer::new();
let now = SystemTime::now();
let id = AgentId::from_parts("cc", "esc");
reducer.apply(
&mut scene,
AgentEvent::SessionStart {
agent_id: id,
source: "claude-code".into(),
session_id: "s".into(),
cwd: std::path::PathBuf::from("/repo\u{1b}]0;pwned\u{7}"),
parent_id: None,
},
now,
Transport::Hook,
);
reducer.apply(
&mut scene,
AgentEvent::Waiting {
agent_id: id,
reason: "needs \u{1b}[2J approval".to_string(),
},
now,
Transport::Hook,
);
let out = summarize(&scene);
assert!(
!out.chars().any(|c| c.is_control()),
"summary must carry no control chars (terminal-escape injection): {out:?}"
);
assert!(out.contains("repo]0;pwned"), "got: {out}");
assert!(out.contains("needs [2J approval"), "got: {out}");
}
#[test]
fn explicit_cap_clamps_to_layout_capacity_not_above() {
let base = boot_capacities_for(192, 48);
let layout_max = *base.iter().max().unwrap();
assert_eq!(
cap_boot_capacities(base, Some(layout_max + 100)),
base,
"cap above layout capacity must clamp down to the layout, not inflate"
);
assert!(cap_boot_capacities(base, Some(1)).iter().all(|&c| c <= 1));
assert_eq!(cap_boot_capacities(base, None), base);
}
#[test]
fn connected_sources_set_get_snapshot() {
let cs = ConnectedSources::new(HashSet::from(["claude-code".to_string()]));
assert!(cs.is_connected("claude-code"));
assert!(!cs.is_connected("codex"));
cs.set("codex", true);
assert!(cs.is_connected("codex"));
cs.set("claude-code", false);
assert!(!cs.is_connected("claude-code"));
assert_eq!(cs.snapshot(), HashSet::from(["codex".to_string()]));
}
}