feat: surface ecc2 session handoff lineage

This commit is contained in:
Affaan Mustafa
2026-04-07 12:21:29 -07:00
parent 8fbd89b215
commit 0eb31212e9
3 changed files with 165 additions and 4 deletions

View File

@@ -30,7 +30,12 @@ pub fn list_sessions(db: &StateStore) -> Result<Vec<Session>> {
pub fn get_status(db: &StateStore, id: &str) -> Result<SessionStatus> {
let session = resolve_session(db, id)?;
Ok(SessionStatus(session))
let session_id = session.id.clone();
Ok(SessionStatus {
session,
parent_session: db.latest_task_handoff_source(&session_id)?,
delegated_children: db.delegated_children(&session_id, 5)?,
})
}
pub async fn stop_session(db: &StateStore, id: &str) -> Result<()> {
@@ -449,15 +454,22 @@ async fn kill_process(pid: u32) -> Result<()> {
}
}
pub struct SessionStatus(Session);
pub struct SessionStatus {
session: Session,
parent_session: Option<String>,
delegated_children: Vec<String>,
}
impl fmt::Display for SessionStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = &self.0;
let s = &self.session;
writeln!(f, "Session: {}", s.id)?;
writeln!(f, "Task: {}", s.task)?;
writeln!(f, "Agent: {}", s.agent_type)?;
writeln!(f, "State: {}", s.state)?;
if let Some(parent) = self.parent_session.as_ref() {
writeln!(f, "Parent: {}", parent)?;
}
if let Some(pid) = s.pid {
writeln!(f, "PID: {}", pid)?;
}
@@ -469,6 +481,9 @@ impl fmt::Display for SessionStatus {
writeln!(f, "Tools: {}", s.metrics.tool_calls)?;
writeln!(f, "Files: {}", s.metrics.files_changed)?;
writeln!(f, "Cost: ${:.4}", s.metrics.cost_usd)?;
if !self.delegated_children.is_empty() {
writeln!(f, "Children: {}", self.delegated_children.join(", "))?;
}
writeln!(f, "Created: {}", s.created_at)?;
write!(f, "Updated: {}", s.updated_at)
}
@@ -848,7 +863,44 @@ mod tests {
db.insert_session(&build_session("newer", SessionState::Idle, newer))?;
let status = get_status(&db, "latest")?;
assert_eq!(status.0.id, "newer");
assert_eq!(status.session.id, "newer");
Ok(())
}
#[test]
fn get_status_surfaces_handoff_lineage() -> Result<()> {
let tempdir = TestDir::new("manager-status-lineage")?;
let cfg = build_config(tempdir.path());
let db = StateStore::open(&cfg.db_path)?;
let now = Utc::now();
db.insert_session(&build_session("parent", SessionState::Running, now - Duration::minutes(2)))?;
db.insert_session(&build_session("child", SessionState::Pending, now - Duration::minutes(1)))?;
db.insert_session(&build_session("sibling", SessionState::Idle, now))?;
db.send_message(
"parent",
"child",
"{\"task\":\"Review auth flow\",\"context\":\"Delegated from parent\"}",
"task_handoff",
)?;
db.send_message(
"parent",
"sibling",
"{\"task\":\"Check billing\",\"context\":\"Delegated from parent\"}",
"task_handoff",
)?;
let status = get_status(&db, "parent")?;
let rendered = status.to_string();
assert!(rendered.contains("Children:"));
assert!(rendered.contains("child"));
assert!(rendered.contains("sibling"));
let child_status = get_status(&db, "child")?;
assert_eq!(child_status.parent_session.as_deref(), Some("parent"));
Ok(())
}

View File

@@ -411,6 +411,40 @@ impl StateStore {
Ok(updated)
}
pub fn latest_task_handoff_source(&self, session_id: &str) -> Result<Option<String>> {
self.conn
.query_row(
"SELECT from_session
FROM messages
WHERE to_session = ?1 AND msg_type = 'task_handoff'
ORDER BY id DESC
LIMIT 1",
rusqlite::params![session_id],
|row| row.get::<_, String>(0),
)
.optional()
.map_err(Into::into)
}
pub fn delegated_children(&self, session_id: &str, limit: usize) -> Result<Vec<String>> {
let mut stmt = self.conn.prepare(
"SELECT to_session
FROM messages
WHERE from_session = ?1 AND msg_type = 'task_handoff'
GROUP BY to_session
ORDER BY MAX(id) DESC
LIMIT ?2",
)?;
let children = stmt
.query_map(rusqlite::params![session_id, limit as i64], |row| {
row.get::<_, String>(0)
})?
.collect::<Result<Vec<_>, _>>()?;
Ok(children)
}
pub fn append_output_line(
&self,
session_id: &str,
@@ -725,6 +759,31 @@ mod tests {
assert_eq!(unread_after.get("worker"), None);
assert_eq!(unread_after.get("planner"), Some(&1));
db.send_message(
"planner",
"worker-2",
"{\"task\":\"Review auth flow\",\"context\":\"Delegated from planner\"}",
"task_handoff",
)?;
db.send_message(
"planner",
"worker-3",
"{\"task\":\"Check billing\",\"context\":\"Delegated from planner\"}",
"task_handoff",
)?;
assert_eq!(
db.latest_task_handoff_source("worker-2")?,
Some("planner".to_string())
);
assert_eq!(
db.delegated_children("planner", 10)?,
vec![
"worker-3".to_string(),
"worker-2".to_string(),
]
);
Ok(())
}
}