hive-ag3nt: add GET /api/bash-tasks endpoint for the agent page
Snapshot of the agent's in-flight bash tasks: reads the in-container bash-tasks/ dir (the co-located hive-bash-mcp daemon writes one TaskFile JSON per task), deserializes the canonical hive_sh4re::TaskFile, filters to Pending/Running, and returns them running-first then oldest-first. Skips unreadable/malformed files (and the daemon's .json.tmp scratch writes) so a stray file can't fail the list. Snapshot-only for v1; the page polls it like /api/loose-ends, SSE live-push is a possible follow-up.
This commit is contained in:
parent
b70209836e
commit
ceb852e5b4
1 changed files with 48 additions and 0 deletions
|
|
@ -123,6 +123,7 @@ pub async fn serve(
|
|||
.route("/api/new-session", post(post_new_session))
|
||||
.route("/api/logout", post(post_logout))
|
||||
.route("/api/loose-ends", get(api_loose_ends))
|
||||
.route("/api/bash-tasks", get(api_bash_tasks))
|
||||
.route("/api/stats", get(api_stats))
|
||||
.route("/screen/ws", get(screen_ws))
|
||||
.route("/icon", get(serve_icon))
|
||||
|
|
@ -525,6 +526,53 @@ async fn api_loose_ends(State(state): State<AppState>) -> Response {
|
|||
axum::Json(serde_json::json!({ "loose_ends": loose_ends })).into_response()
|
||||
}
|
||||
|
||||
/// `GET /api/bash-tasks` — snapshot of this agent's in-flight bash tasks.
|
||||
///
|
||||
/// The `hive-bash-mcp` daemon runs in this same container and writes one
|
||||
/// `<id>.json` ([`hive_sh4re::TaskFile`]) per task under the harness
|
||||
/// `bash-tasks/` dir. This reads that dir and returns the tasks still
|
||||
/// `Pending` or `Running`, so the agent page can show what's running without
|
||||
/// going through the broker. Snapshot only — the page polls/refreshes it like
|
||||
/// `/api/loose-ends`; there's no live SSE push for task state yet. Unreadable
|
||||
/// or malformed files (incl. the daemon's `.json.tmp` scratch writes, which
|
||||
/// don't match the `.json` extension) are skipped so one stray file can't
|
||||
/// fail the whole list.
|
||||
async fn api_bash_tasks() -> Response {
|
||||
let dir = crate::paths::harness_dir().join("bash-tasks");
|
||||
let mut tasks: Vec<hive_sh4re::TaskFile> = Vec::new();
|
||||
if let Ok(rd) = std::fs::read_dir(&dir) {
|
||||
for entry in rd.flatten() {
|
||||
let path = entry.path();
|
||||
if path.extension().and_then(|e| e.to_str()) != Some("json") {
|
||||
continue;
|
||||
}
|
||||
let Ok(text) = std::fs::read_to_string(&path) else {
|
||||
continue;
|
||||
};
|
||||
let Ok(task) = serde_json::from_str::<hive_sh4re::TaskFile>(&text) else {
|
||||
continue;
|
||||
};
|
||||
if matches!(
|
||||
task.status,
|
||||
hive_sh4re::TaskStatus::Pending | hive_sh4re::TaskStatus::Running
|
||||
) {
|
||||
tasks.push(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Running before Pending, then oldest-first so a long-runner sits on top.
|
||||
tasks.sort_by(|a, b| {
|
||||
let rank = |s: &hive_sh4re::TaskStatus| match s {
|
||||
hive_sh4re::TaskStatus::Running => 0,
|
||||
_ => 1,
|
||||
};
|
||||
rank(&a.status)
|
||||
.cmp(&rank(&b.status))
|
||||
.then(a.created_at.cmp(&b.created_at))
|
||||
});
|
||||
axum::Json(serde_json::json!({ "tasks": tasks })).into_response()
|
||||
}
|
||||
|
||||
async fn api_state(State(state): State<AppState>) -> axum::Json<StateSnapshot> {
|
||||
// Capture seq *before* any reads so the dedupe contract is
|
||||
// "events with seq > snapshot.seq are post-snapshot, never missed."
|
||||
|
|
|
|||
Loading…
Reference in a new issue