refactor: clear pi-era residue + make CI gate green (harness-neutral cleanup) #158
12 changed files with 121 additions and 76 deletions
12
AGENTS.md
12
AGENTS.md
|
|
@ -102,6 +102,18 @@ cargo test --workspace
|
|||
cargo build --workspace --release
|
||||
```
|
||||
|
||||
**Mandatory before every merge to `main`:** run the full gate via
|
||||
|
||||
```sh
|
||||
./scripts/ci-checks.sh # fmt --check, clippy -D warnings, test, markdown gate
|
||||
```
|
||||
|
||||
`.forgejo/workflows/ci.yml` encodes the same checks, but **no Actions runner is
|
||||
currently registered**, so nothing enforces them server-side. Until a runner is
|
||||
active, `ci-checks.sh` passing locally is the only gate — a green run is a
|
||||
prerequisite for merging. (A compile break reached `main` once because this step
|
||||
was skipped; do not skip it.)
|
||||
|
||||
## ISO Takeover Gates
|
||||
|
||||
| Gate | Target | Status |
|
||||
|
|
|
|||
|
|
@ -327,7 +327,7 @@ mod tests {
|
|||
"id": "pane-1",
|
||||
"agent": "pi",
|
||||
"state": "working",
|
||||
"pi_session_id": "pi-session-1",
|
||||
"session_id": "pi-session-1",
|
||||
"last_event_at": "2026-05-27T11:59:59.000Z",
|
||||
"cwd": "/repo"
|
||||
}]
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ async fn daemon_client_live_socket_check_with_local_fake_agent() {
|
|||
.iter()
|
||||
.find(|pane| pane.id == agent_id)
|
||||
.unwrap();
|
||||
assert_eq!(pane.pi_session_id.as_deref(), Some("manual-test"));
|
||||
assert_eq!(pane.session_id.as_deref(), Some("manual-test"));
|
||||
assert!(pane.cwd.is_some());
|
||||
|
||||
let kill = client.kill_agent(agent_id).await.unwrap();
|
||||
|
|
@ -370,7 +370,7 @@ async fn harness_double_spawn_session_isolation() {
|
|||
assert_ne!(pane_a.id, pane_b.id);
|
||||
|
||||
// Verify session isolation: both share the same session_id (test agent default)
|
||||
assert_eq!(pane_a.pi_session_id, pane_b.pi_session_id);
|
||||
assert_eq!(pane_a.session_id, pane_b.session_id);
|
||||
|
||||
// Kill one agent — snapshot may still include stopped panes briefly
|
||||
let kill = client.kill_agent(&pane_a.id).await.unwrap();
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ pub struct RuntimeInventory {
|
|||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub pi: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub zot: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub npm_prefix: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub package_manager: Option<String>,
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
let socket_handle =
|
||||
tokio::spawn(async move { socket::serve(socket_state, socket_shutdown).await });
|
||||
|
||||
// Auto-spawn one Pi agent if configured (live "Operator Image" OOTB flow).
|
||||
// Auto-spawn one agent if configured (live "Operator Image" OOTB flow).
|
||||
// Runs after the socket server is up so the spawn registers on the board;
|
||||
// no-op unless COLIBRI_AUTOSPAWN is set and a DeepSeek key is present.
|
||||
socket::autospawn_agent_if_configured(&state).await;
|
||||
|
|
|
|||
|
|
@ -416,9 +416,7 @@ pub async fn autospawn_agent_if_configured(state: &SharedState) {
|
|||
return;
|
||||
}
|
||||
if state.config.deepseek_api_key.is_none() {
|
||||
info!(
|
||||
"autospawn: DEEPSEEK_API_KEY not set; skipping (operator can add it via Join Hive)"
|
||||
);
|
||||
info!("autospawn: DEEPSEEK_API_KEY not set; skipping (operator can add it via Join Hive)");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -438,22 +436,13 @@ pub async fn autospawn_agent_if_configured(state: &SharedState) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Default argv depends on the harness: zot is a request/response peer
|
||||
// driven over stdin (`zot rpc`), while pi self-drives (`pi --mode json`).
|
||||
// `--mode json` is NOT a valid zot flag, so a single default would break one
|
||||
// of them — pick per binary. Override with COLIBRI_AUTOSPAWN_ARGS.
|
||||
let default_args = if agent_name == "zot" {
|
||||
"rpc"
|
||||
} else {
|
||||
"--mode json"
|
||||
};
|
||||
// Default argv depends on the harness (see default_agent_args). Override
|
||||
// wholesale with COLIBRI_AUTOSPAWN_ARGS.
|
||||
let args: Vec<String> = std::env::var("COLIBRI_AUTOSPAWN_ARGS")
|
||||
.ok()
|
||||
.filter(|s| !s.trim().is_empty())
|
||||
.unwrap_or_else(|| default_args.to_string())
|
||||
.split_whitespace()
|
||||
.map(str::to_string)
|
||||
.collect();
|
||||
.map(|s| s.split_whitespace().map(str::to_string).collect())
|
||||
.unwrap_or_else(|| default_agent_args(&agent_binary));
|
||||
|
||||
info!(binary = %agent_binary, ?args, "autospawn: spawning agent on host (DeepSeek-backed)");
|
||||
|
||||
|
|
@ -554,6 +543,20 @@ fn basename(path: &str) -> String {
|
|||
.to_string()
|
||||
}
|
||||
|
||||
/// Default argv for an agent harness, by binary basename. zot is an RPC peer
|
||||
/// (`zot rpc`, driven over stdin); every other harness is assumed to be a
|
||||
/// self-driving JSON emitter (`pi --mode json`). `--mode json` is not a valid
|
||||
/// zot flag, so the default must vary by harness — a single shared default
|
||||
/// would break one of them. Single source of truth for both autospawn and the
|
||||
/// non-local spawn path.
|
||||
fn default_agent_args(binary: &str) -> Vec<String> {
|
||||
if basename(binary) == "zot" {
|
||||
vec!["rpc".to_string()]
|
||||
} else {
|
||||
vec!["--mode".to_string(), "json".to_string()]
|
||||
}
|
||||
}
|
||||
|
||||
fn env_truthy(name: &str) -> bool {
|
||||
std::env::var(name)
|
||||
.map(|v| {
|
||||
|
|
@ -565,6 +568,10 @@ fn env_truthy(name: &str) -> bool {
|
|||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
// Spawn parameters are passed positionally to mirror the socket command shape;
|
||||
// grouping them into a struct would not improve clarity here. Matches the same
|
||||
// allow on prepare_spawn_command.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn cmd_spawn_agent(
|
||||
state: &SharedState,
|
||||
provider_str: String,
|
||||
|
|
@ -590,10 +597,12 @@ async fn cmd_spawn_agent(
|
|||
let args = local_args.unwrap_or_default();
|
||||
(model.clone(), args)
|
||||
} else {
|
||||
(
|
||||
std::env::var("COLIBRI_AGENT_BINARY").unwrap_or_else(|_| "hermes-agent".to_string()),
|
||||
vec!["--mode".to_string(), "json".to_string()],
|
||||
)
|
||||
// Non-local provider: a managed agent harness. Default to zot (the
|
||||
// current default harness); argv follows the harness via
|
||||
// default_agent_args so we never spawn `zot --mode json`.
|
||||
let binary = std::env::var("COLIBRI_AGENT_BINARY").unwrap_or_else(|_| "zot".to_string());
|
||||
let args = default_agent_args(&binary);
|
||||
(binary, args)
|
||||
};
|
||||
|
||||
// An agent invoked in RPC mode (`zot rpc` / `--rpc`) blocks on stdin until
|
||||
|
|
@ -1093,7 +1102,7 @@ mod tests {
|
|||
let data = response.data.unwrap();
|
||||
assert_eq!(data["schema"], "clawdie.glasspane.snapshot.v1");
|
||||
assert_eq!(data["panes"][0]["id"], "pane-a");
|
||||
assert_eq!(data["panes"][0]["pi_session_id"], "pi-s");
|
||||
assert_eq!(data["panes"][0]["session_id"], "pi-s");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
@ -1130,7 +1139,7 @@ mod tests {
|
|||
.find(|pane| pane.id == pane_id)
|
||||
.unwrap();
|
||||
assert_eq!(pane.state, colibri_glasspane::AgentState::Done);
|
||||
assert_eq!(pane.pi_session_id.as_deref(), Some("pi-fake"));
|
||||
assert_eq!(pane.session_id.as_deref(), Some("pi-fake"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
|
|||
|
|
@ -317,11 +317,7 @@ impl RpcSender {
|
|||
/// Send one prompt as a newline-delimited request on the agent's stdin.
|
||||
/// Returns the request id used. Errors if the stdin pipe has closed.
|
||||
pub async fn send_prompt(&self, message: &str) -> std::io::Result<String> {
|
||||
let id = (self
|
||||
.seq
|
||||
.fetch_add(1, std::sync::atomic::Ordering::SeqCst)
|
||||
+ 1)
|
||||
.to_string();
|
||||
let id = (self.seq.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1).to_string();
|
||||
let line = build_rpc_prompt(&id, message);
|
||||
let mut guard = self.stdin.lock().await;
|
||||
let stdin = guard.as_mut().ok_or_else(|| {
|
||||
|
|
@ -552,7 +548,7 @@ pub fn jail_wrap(
|
|||
/// Configuration for spawning an agent subprocess.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AgentSpawnConfig {
|
||||
/// Agent binary or command to run (e.g. "pi", "hermes-agent", etc.).
|
||||
/// Agent binary or command to run (e.g. "zot", "pi", etc.).
|
||||
pub binary: String,
|
||||
/// Command-line arguments.
|
||||
#[serde(default)]
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ mod integration {
|
|||
ingestor.ingest_line_at(line, t + Duration::from_secs(i as u64));
|
||||
}
|
||||
assert_eq!(ingestor.state(), AgentState::Done);
|
||||
assert_eq!(ingestor.pi_session_id(), Some("s1"));
|
||||
assert_eq!(ingestor.session_id(), Some("s1"));
|
||||
}
|
||||
|
||||
/// Agent stays idle through unknown events (no crash, forward-compatible)
|
||||
|
|
@ -81,7 +81,7 @@ mod integration {
|
|||
id: "a".into(),
|
||||
agent: "pi".into(),
|
||||
state: AgentState::Working,
|
||||
pi_session_id: Some("s1".into()),
|
||||
session_id: Some("s1".into()),
|
||||
last_event_at: Some("2026-05-27T10:00:00Z".into()),
|
||||
cwd: None,
|
||||
stalled: false,
|
||||
|
|
@ -90,7 +90,7 @@ mod integration {
|
|||
id: "b".into(),
|
||||
agent: "pi".into(),
|
||||
state: AgentState::Blocked,
|
||||
pi_session_id: Some("s2".into()),
|
||||
session_id: Some("s2".into()),
|
||||
last_event_at: Some("2026-05-27T10:00:01Z".into()),
|
||||
cwd: None,
|
||||
stalled: false,
|
||||
|
|
@ -99,7 +99,7 @@ mod integration {
|
|||
id: "c".into(),
|
||||
agent: "pi".into(),
|
||||
state: AgentState::Idle,
|
||||
pi_session_id: None,
|
||||
session_id: None,
|
||||
last_event_at: None,
|
||||
cwd: None,
|
||||
stalled: false,
|
||||
|
|
@ -124,7 +124,7 @@ mod integration {
|
|||
id: "p1".into(),
|
||||
agent: "pi".into(),
|
||||
state: AgentState::Working,
|
||||
pi_session_id: Some("sess-1".into()),
|
||||
session_id: Some("sess-1".into()),
|
||||
last_event_at: Some("2026-05-27T10:00:00Z".into()),
|
||||
cwd: None,
|
||||
stalled: false,
|
||||
|
|
@ -141,7 +141,7 @@ mod integration {
|
|||
fn default_ingestor_is_idle() {
|
||||
let ingestor = PiJsonlIngestor::default();
|
||||
assert_eq!(ingestor.state(), AgentState::Idle);
|
||||
assert!(ingestor.pi_session_id().is_none());
|
||||
assert!(ingestor.session_id().is_none());
|
||||
}
|
||||
|
||||
/// Queue update blocks agent
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
//! Uses scripts/fake-pi-agent.py which emits the colibri-pi-events JSONL taxonomy.
|
||||
//!
|
||||
//! With real Pi binary (when installed):
|
||||
//! COLIBRI_PI_BINARY=pi cargo test -p colibri-daemon --test pi_spawn_live -- --nocapture
|
||||
//! COLIBRI_AGENT_BINARY=pi cargo test -p colibri-daemon --test pi_spawn_live -- --nocapture
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::process::Stdio;
|
||||
|
|
@ -79,13 +79,13 @@ async fn pi_spawn_path_produces_correct_glasspane_state() {
|
|||
"expected Done after full agent run"
|
||||
);
|
||||
assert!(
|
||||
pane.pi_session_id.is_some(),
|
||||
"expected pi_session_id from session event, got {:?}",
|
||||
pane.pi_session_id
|
||||
pane.session_id.is_some(),
|
||||
"expected session_id from session event, got {:?}",
|
||||
pane.session_id
|
||||
);
|
||||
|
||||
eprintln!(
|
||||
"✓ Pi spawn proof: {} accepted, state={:?}, session={:?}",
|
||||
accepted, pane.state, pane.pi_session_id
|
||||
accepted, pane.state, pane.session_id
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ struct App {
|
|||
// harness additions
|
||||
status_msg: Option<(String, u8)>, // (message, ttl ticks)
|
||||
detail_pane: Option<usize>, // index into snapshot.panes, or None
|
||||
session_filter: Option<String>, // filter by pi_session_id, or None
|
||||
session_filter: Option<String>, // filter by session_id, or None
|
||||
sessions: Vec<String>, // all known session IDs
|
||||
session_idx: usize, // which session is selected
|
||||
}
|
||||
|
|
@ -113,7 +113,7 @@ impl App {
|
|||
Some(sid) => snap
|
||||
.panes
|
||||
.iter()
|
||||
.filter(|p| p.pi_session_id.as_deref() == Some(sid.as_str()))
|
||||
.filter(|p| p.session_id.as_deref() == Some(sid.as_str()))
|
||||
.collect(),
|
||||
None => snap.panes.iter().collect(),
|
||||
}
|
||||
|
|
@ -132,7 +132,7 @@ impl App {
|
|||
let mut ids: Vec<String> = snap
|
||||
.panes
|
||||
.iter()
|
||||
.filter_map(|p| p.pi_session_id.clone())
|
||||
.filter_map(|p| p.session_id.clone())
|
||||
.collect();
|
||||
ids.sort();
|
||||
ids.dedup();
|
||||
|
|
@ -377,7 +377,7 @@ impl App {
|
|||
format!("{:?}", p.state),
|
||||
Style::default().fg(color),
|
||||
)),
|
||||
Cell::from(p.pi_session_id.as_deref().unwrap_or("—")),
|
||||
Cell::from(p.session_id.as_deref().unwrap_or("—")),
|
||||
Cell::from(p.cwd.as_deref().unwrap_or("—")),
|
||||
Cell::from(if p.stalled { "⚠" } else { "" }),
|
||||
])
|
||||
|
|
@ -429,7 +429,7 @@ impl App {
|
|||
),
|
||||
Span::raw(" "),
|
||||
Span::styled("Session: ", Style::default().add_modifier(Modifier::BOLD)),
|
||||
Span::raw(pane.pi_session_id.as_deref().unwrap_or("—")),
|
||||
Span::raw(pane.session_id.as_deref().unwrap_or("—")),
|
||||
]),
|
||||
Line::from(vec![
|
||||
Span::styled("CWD: ", Style::default().add_modifier(Modifier::BOLD)),
|
||||
|
|
@ -649,7 +649,7 @@ mod tests {
|
|||
id: "a".into(),
|
||||
agent: "pi".into(),
|
||||
state: AgentState::Working,
|
||||
pi_session_id: Some("s1".into()),
|
||||
session_id: Some("s1".into()),
|
||||
last_event_at: None,
|
||||
cwd: None,
|
||||
stalled: false,
|
||||
|
|
@ -658,7 +658,7 @@ mod tests {
|
|||
id: "b".into(),
|
||||
agent: "pi".into(),
|
||||
state: AgentState::Idle,
|
||||
pi_session_id: Some("s2".into()),
|
||||
session_id: Some("s2".into()),
|
||||
last_event_at: None,
|
||||
cwd: None,
|
||||
stalled: false,
|
||||
|
|
@ -693,7 +693,7 @@ mod tests {
|
|||
id: "a".into(),
|
||||
agent: "pi".into(),
|
||||
state: AgentState::Working,
|
||||
pi_session_id: Some("s2".into()),
|
||||
session_id: Some("s2".into()),
|
||||
last_event_at: None,
|
||||
cwd: None,
|
||||
stalled: false,
|
||||
|
|
@ -702,7 +702,7 @@ mod tests {
|
|||
id: "b".into(),
|
||||
agent: "pi".into(),
|
||||
state: AgentState::Working,
|
||||
pi_session_id: Some("s1".into()),
|
||||
session_id: Some("s1".into()),
|
||||
last_event_at: None,
|
||||
cwd: None,
|
||||
stalled: false,
|
||||
|
|
@ -711,7 +711,7 @@ mod tests {
|
|||
id: "c".into(),
|
||||
agent: "pi".into(),
|
||||
state: AgentState::Working,
|
||||
pi_session_id: Some("s1".into()),
|
||||
session_id: Some("s1".into()),
|
||||
last_event_at: None,
|
||||
cwd: None,
|
||||
stalled: false,
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ pub fn pane_from_jsonl_with_id(
|
|||
id: pane_id.into(),
|
||||
agent: agent.into(),
|
||||
state: ingestor.state,
|
||||
pi_session_id: ingestor.pi_session_id,
|
||||
session_id: ingestor.session_id,
|
||||
last_event_at: None,
|
||||
cwd: ingestor.cwd,
|
||||
stalled: false,
|
||||
|
|
@ -223,7 +223,7 @@ pub fn pane_from_jsonl(agent: impl Into<String>, jsonl: &str) -> Pane {
|
|||
pub struct PiStreamUpdate {
|
||||
pub pi_type: String,
|
||||
pub state: AgentState,
|
||||
pub pi_session_id: Option<String>,
|
||||
pub session_id: Option<String>,
|
||||
pub cwd: Option<String>,
|
||||
pub observed_at: SystemTime,
|
||||
}
|
||||
|
|
@ -244,7 +244,7 @@ pub struct PaneReaderStats {
|
|||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PiJsonlIngestor {
|
||||
state: AgentState,
|
||||
pi_session_id: Option<String>,
|
||||
session_id: Option<String>,
|
||||
cwd: Option<String>,
|
||||
last_event_at: Option<SystemTime>,
|
||||
/// Which harness produced the stream. Pi events are read directly; zot
|
||||
|
|
@ -256,7 +256,7 @@ impl Default for PiJsonlIngestor {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
state: AgentState::Idle,
|
||||
pi_session_id: None,
|
||||
session_id: None,
|
||||
cwd: None,
|
||||
last_event_at: None,
|
||||
runtime: AgentRuntime::Pi,
|
||||
|
|
@ -277,8 +277,8 @@ impl PiJsonlIngestor {
|
|||
self.state
|
||||
}
|
||||
|
||||
pub fn pi_session_id(&self) -> Option<&str> {
|
||||
self.pi_session_id.as_deref()
|
||||
pub fn session_id(&self) -> Option<&str> {
|
||||
self.session_id.as_deref()
|
||||
}
|
||||
|
||||
pub fn cwd(&self) -> Option<&str> {
|
||||
|
|
@ -312,7 +312,7 @@ impl PiJsonlIngestor {
|
|||
|
||||
if ty == "session" || ty == "session_started" {
|
||||
if let Some(sid) = value.get("id").and_then(Value::as_str) {
|
||||
self.pi_session_id = Some(sid.to_string());
|
||||
self.session_id = Some(sid.to_string());
|
||||
}
|
||||
if let Some(cwd) = value.get("cwd").and_then(Value::as_str) {
|
||||
self.cwd = Some(cwd.to_string());
|
||||
|
|
@ -322,7 +322,7 @@ impl PiJsonlIngestor {
|
|||
Some(PiStreamUpdate {
|
||||
pi_type: ty.to_string(),
|
||||
state: self.state,
|
||||
pi_session_id: self.pi_session_id.clone(),
|
||||
session_id: self.session_id.clone(),
|
||||
cwd: self.cwd.clone(),
|
||||
observed_at,
|
||||
})
|
||||
|
|
@ -373,8 +373,8 @@ impl SupervisedPane {
|
|||
self.ingestor.state()
|
||||
}
|
||||
|
||||
pub fn pi_session_id(&self) -> Option<&str> {
|
||||
self.ingestor.pi_session_id()
|
||||
pub fn session_id(&self) -> Option<&str> {
|
||||
self.ingestor.session_id()
|
||||
}
|
||||
|
||||
pub fn cwd(&self) -> Option<&str> {
|
||||
|
|
@ -407,7 +407,7 @@ impl SupervisedPane {
|
|||
id: self.id.clone(),
|
||||
agent: self.agent.clone(),
|
||||
state: self.state(),
|
||||
pi_session_id: self.pi_session_id().map(str::to_string),
|
||||
session_id: self.session_id().map(str::to_string),
|
||||
last_event_at: self.last_event_at().map(system_time_to_rfc3339),
|
||||
cwd: self.cwd().map(str::to_string),
|
||||
stalled: self.is_stalled_at(now, stall_after),
|
||||
|
|
@ -627,12 +627,18 @@ fn system_time_to_rfc3339(time: SystemTime) -> String {
|
|||
/// A supervised pane — one agent occupying one PTY/session slot.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Pane {
|
||||
/// Colibri-owned pane id. Distinct from the Pi session id.
|
||||
/// Colibri-owned pane id. Distinct from the agent's own session id.
|
||||
pub id: String,
|
||||
pub agent: String,
|
||||
pub state: AgentState,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub pi_session_id: Option<String>,
|
||||
// `alias` keeps deserializing the legacy `pi_session_id` key (pre-zot wire
|
||||
// format / persisted snapshots) onto the harness-neutral field.
|
||||
#[serde(
|
||||
default,
|
||||
alias = "pi_session_id",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub session_id: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub last_event_at: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
|
|
@ -737,7 +743,7 @@ mod tests {
|
|||
id: "p1".into(),
|
||||
agent: "pi".into(),
|
||||
state: AgentState::Working,
|
||||
pi_session_id: Some("pi-session-1".into()),
|
||||
session_id: Some("pi-session-1".into()),
|
||||
last_event_at: None,
|
||||
cwd: Some("/repo".into()),
|
||||
stalled: false,
|
||||
|
|
@ -746,7 +752,7 @@ mod tests {
|
|||
id: "p2".into(),
|
||||
agent: "pi".into(),
|
||||
state: AgentState::Blocked,
|
||||
pi_session_id: None,
|
||||
session_id: None,
|
||||
last_event_at: None,
|
||||
cwd: None,
|
||||
stalled: true,
|
||||
|
|
@ -779,7 +785,7 @@ mod tests {
|
|||
assert_eq!(pane.state, AgentState::Done);
|
||||
assert_eq!(pane.id, "pane-a");
|
||||
assert_eq!(
|
||||
pane.pi_session_id.as_deref(),
|
||||
pane.session_id.as_deref(),
|
||||
Some("019e5e59-6645-7e21-aca2-b57ccf0f8578")
|
||||
);
|
||||
assert_eq!(pane.cwd.as_deref(), Some("/home/clawdija/clawdie-ai"));
|
||||
|
|
@ -815,7 +821,7 @@ mod tests {
|
|||
.unwrap();
|
||||
assert_eq!(update.pi_type, "session");
|
||||
assert_eq!(update.observed_at, t(10));
|
||||
assert_eq!(ingestor.pi_session_id(), Some("pi-s"));
|
||||
assert_eq!(ingestor.session_id(), Some("pi-s"));
|
||||
assert_eq!(ingestor.cwd(), Some("/repo"));
|
||||
assert_eq!(ingestor.last_event_at(), Some(t(10)));
|
||||
|
||||
|
|
@ -825,7 +831,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn supervisor_keeps_colibri_pane_id_separate_from_pi_session_id() {
|
||||
fn supervisor_keeps_colibri_pane_id_separate_from_session_id() {
|
||||
let mut supervisor = PaneSupervisor::new();
|
||||
supervisor.attach_pane_at("pane-1", "pi", t(0));
|
||||
supervisor.ingest_line_at(
|
||||
|
|
@ -840,8 +846,8 @@ mod tests {
|
|||
.pop()
|
||||
.unwrap();
|
||||
assert_eq!(pane.id, "pane-1");
|
||||
assert_eq!(pane.pi_session_id.as_deref(), Some("pi-session-1"));
|
||||
assert_ne!(pane.id, pane.pi_session_id.unwrap());
|
||||
assert_eq!(pane.session_id.as_deref(), Some("pi-session-1"));
|
||||
assert_ne!(pane.id, pane.session_id.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -861,7 +867,7 @@ mod tests {
|
|||
|
||||
let pane = supervisor.get("pane-a").unwrap();
|
||||
assert_eq!(pane.state(), AgentState::Working);
|
||||
assert_eq!(pane.pi_session_id(), Some("pi-s"));
|
||||
assert_eq!(pane.session_id(), Some("pi-s"));
|
||||
assert_eq!(
|
||||
pane.last_event_at(),
|
||||
Some(t(100) + Duration::from_millis(3))
|
||||
|
|
@ -896,7 +902,7 @@ mod tests {
|
|||
|
||||
let pane = supervisor.get("pane-reader").unwrap();
|
||||
assert_eq!(pane.state(), AgentState::Done);
|
||||
assert_eq!(pane.pi_session_id(), Some("pi-s"));
|
||||
assert_eq!(pane.session_id(), Some("pi-s"));
|
||||
assert_eq!(pane.last_event_at(), Some(t(13)));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ struct RuntimeInventory {
|
|||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pi: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
zot: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
npm_prefix: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
package_manager: Option<String>,
|
||||
|
|
@ -156,6 +158,23 @@ fn detect_pi_version() -> Option<String> {
|
|||
.find_map(|candidate| pi_package_version_from_bin(&candidate))
|
||||
}
|
||||
|
||||
/// Detect the installed zot agent version. zot is a single Go binary (not an
|
||||
/// npm package), so this is a plain `--version` probe across `$ZOT_BIN`, PATH,
|
||||
/// and the usual candidate locations.
|
||||
fn detect_zot_version() -> Option<String> {
|
||||
if let Ok(zot_bin) = env::var("ZOT_BIN") {
|
||||
if let Some(version) = command_output(&zot_bin, &["--version"]) {
|
||||
return Some(version);
|
||||
}
|
||||
}
|
||||
if let Some(version) = command_output("zot", &["--version"]) {
|
||||
return Some(version);
|
||||
}
|
||||
command_candidates("zot")
|
||||
.into_iter()
|
||||
.find_map(|candidate| command_output(&candidate, &["--version"]))
|
||||
}
|
||||
|
||||
fn detect_package_manager() -> Option<String> {
|
||||
if command_output("pkg", &["--version"]).is_some() {
|
||||
return Some("pkg".to_string());
|
||||
|
|
@ -180,6 +199,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
node: command_output("node", &["--version"]),
|
||||
npm: command_output("npm", &["--version"]),
|
||||
pi: detect_pi_version(),
|
||||
zot: detect_zot_version(),
|
||||
npm_prefix: command_output("npm", &["config", "get", "prefix"]),
|
||||
package_manager: detect_package_manager(),
|
||||
iso_npm_globals_pin: BTreeMap::new(),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue