feat: add set_status MCP tool and status field to whoami/dashboard (closes #325)
This commit is contained in:
parent
6f3b56ad84
commit
fe2933b213
8 changed files with 170 additions and 16 deletions
|
|
@ -10,7 +10,8 @@ Tools (hyperhive surface):
|
||||||
- `mcp__hyperhive__get_loose_ends()` — list your loose ends: unanswered questions where you're asker (waiting on someone) or target (owing a reply), plus reminders you've scheduled that haven't fired. No args, cheap server-side sweep. Useful at turn start to remember what's outstanding without scanning inbox archaeology.
|
- `mcp__hyperhive__get_loose_ends()` — list your loose ends: unanswered questions where you're asker (waiting on someone) or target (owing a reply), plus reminders you've scheduled that haven't fired. No args, cheap server-side sweep. Useful at turn start to remember what's outstanding without scanning inbox archaeology.
|
||||||
- `mcp__hyperhive__cancel_loose_end(kind, id)` — cancel one of your own open threads. `kind` is `"question"` (the asker — you, in this case — gets a `[cancelled by <you>]` answer so the waiter unblocks) or `"reminder"` (hard-deleted before it fires). `id` from the matching `get_loose_ends` row or the original submission reply.
|
- `mcp__hyperhive__cancel_loose_end(kind, id)` — cancel one of your own open threads. `kind` is `"question"` (the asker — you, in this case — gets a `[cancelled by <you>]` answer so the waiter unblocks) or `"reminder"` (hard-deleted before it fires). `id` from the matching `get_loose_ends` row or the original submission reply.
|
||||||
- `mcp__hyperhive__remind(message, delay_seconds? | at_unix_timestamp?, file_path?)` — schedule a message to land in your *own* inbox at a future time (sender shows as `reminder`). Set exactly one of `delay_seconds` (relative) or `at_unix_timestamp` (absolute). Use for self-paced follow-ups instead of blocking a whole turn on a long `recv` wait. A large `message` auto-spills to a file under `/agents/{label}/state/reminders/`; pass `file_path` to point at one yourself. Each agent's pending-reminder count is capped (default 50) — the tool will error if the cap is already reached.
|
- `mcp__hyperhive__remind(message, delay_seconds? | at_unix_timestamp?, file_path?)` — schedule a message to land in your *own* inbox at a future time (sender shows as `reminder`). Set exactly one of `delay_seconds` (relative) or `at_unix_timestamp` (absolute). Use for self-paced follow-ups instead of blocking a whole turn on a long `recv` wait. A large `message` auto-spills to a file under `/agents/{label}/state/reminders/`; pass `file_path` to point at one yourself. Each agent's pending-reminder count is capped (default 50) — the tool will error if the cap is already reached.
|
||||||
- `mcp__hyperhive__whoami()` — self-introspection: returns your canonical agent name (from socket identity, not the prompt-substituted label), role, and current hyperhive rev. No args. Use it when you want a trustworthy identity stamp for state files, commit messages, or cross-agent attribution that won't drift across renames.
|
- `mcp__hyperhive__whoami()` — self-introspection: returns your canonical agent name (from socket identity, not the prompt-substituted label), role, and current hyperhive rev. No args. Use it when you want a trustworthy identity stamp for state files, commit messages, or cross-agent attribution that won't drift across renames or session-continue boundaries where the system-prompt label could be stale.
|
||||||
|
- `mcp__hyperhive__set_status(text)` — set a free-text status visible on the operator dashboard. **Call this at the start of every task** to say what you're working on (e.g. `"reviewing PR #42"`, `"fixing #319 model priority"`, `"idle"`). Pass an empty string to clear. Persists across harness restarts.
|
||||||
- `mcp__hyperhive__request_next_turn()` — ask the harness to start another turn immediately after this one ends, even if the inbox is empty. Use for multi-turn tasks (long builds, sequential steps) where you want to continue without waiting for an external message. The next turn starts with `from: "self"` and `body: "continue"`. No-op if new inbox messages arrive before this turn ends (the harness already loops immediately on pending messages). No args.
|
- `mcp__hyperhive__request_next_turn()` — ask the harness to start another turn immediately after this one ends, even if the inbox is empty. Use for multi-turn tasks (long builds, sequential steps) where you want to continue without waiting for an external message. The next turn starts with `from: "self"` and `body: "continue"`. No-op if new inbox messages arrive before this turn ends (the harness already loops immediately on pending messages). No args.
|
||||||
|
|
||||||
Need new packages, env vars, or other NixOS config for yourself? You can't edit your own config directly — message the manager (recipient `manager`) describing what you need + why. The manager evaluates the request (it doesn't rubber-stamp), edits `/agents/{label}/config/agent.nix` on your behalf, commits, and submits an approval that the operator can accept on the dashboard; on approve hive-c0re rebuilds your container with the new config.
|
Need new packages, env vars, or other NixOS config for yourself? You can't edit your own config directly — message the manager (recipient `manager`) describing what you need + why. The manager evaluates the request (it doesn't rubber-stamp), edits `/agents/{label}/config/agent.nix` on your behalf, commits, and submits an approval that the operator can accept on the dashboard; on approve hive-c0re rebuilds your container with the new config.
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ pub enum SocketReply {
|
||||||
name: String,
|
name: String,
|
||||||
role: String,
|
role: String,
|
||||||
hyperhive_rev: Option<String>,
|
hyperhive_rev: Option<String>,
|
||||||
|
status_text: Option<String>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,10 +75,12 @@ impl From<hive_sh4re::AgentResponse> for SocketReply {
|
||||||
name,
|
name,
|
||||||
role,
|
role,
|
||||||
hyperhive_rev,
|
hyperhive_rev,
|
||||||
|
status_text,
|
||||||
} => Self::Whoami {
|
} => Self::Whoami {
|
||||||
name,
|
name,
|
||||||
role,
|
role,
|
||||||
hyperhive_rev,
|
hyperhive_rev,
|
||||||
|
status_text,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -102,10 +105,12 @@ impl From<hive_sh4re::ManagerResponse> for SocketReply {
|
||||||
name,
|
name,
|
||||||
role,
|
role,
|
||||||
hyperhive_rev,
|
hyperhive_rev,
|
||||||
|
status_text,
|
||||||
} => Self::Whoami {
|
} => Self::Whoami {
|
||||||
name,
|
name,
|
||||||
role,
|
role,
|
||||||
hyperhive_rev,
|
hyperhive_rev,
|
||||||
|
status_text,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -269,9 +274,14 @@ pub fn format_whoami(resp: Result<SocketReply, anyhow::Error>) -> String {
|
||||||
name,
|
name,
|
||||||
role,
|
role,
|
||||||
hyperhive_rev,
|
hyperhive_rev,
|
||||||
|
status_text,
|
||||||
}) => {
|
}) => {
|
||||||
let rev = hyperhive_rev.as_deref().unwrap_or("<unknown>");
|
let rev = hyperhive_rev.as_deref().unwrap_or("<unknown>");
|
||||||
format!("name: {name}\nrole: {role}\nhyperhive_rev: {rev}")
|
let mut out = format!("name: {name}\nrole: {role}\nhyperhive_rev: {rev}");
|
||||||
|
if let Some(s) = status_text {
|
||||||
|
out.push_str(&format!("\nstatus: {s}"));
|
||||||
|
}
|
||||||
|
out
|
||||||
}
|
}
|
||||||
Ok(SocketReply::Err(m)) => format!("whoami failed: {m}"),
|
Ok(SocketReply::Err(m)) => format!("whoami failed: {m}"),
|
||||||
Ok(other) => format!("whoami unexpected response: {other:?}"),
|
Ok(other) => format!("whoami unexpected response: {other:?}"),
|
||||||
|
|
@ -551,8 +561,9 @@ impl AgentServer {
|
||||||
#[tool(
|
#[tool(
|
||||||
description = "Self-introspection: returns your own canonical agent name (the \
|
description = "Self-introspection: returns your own canonical agent name (the \
|
||||||
socket-identity name, NOT the prompt-substituted label), role (`agent`), and \
|
socket-identity name, NOT the prompt-substituted label), role (`agent`), and \
|
||||||
the current hyperhive rev hive-c0re is running against. No args. Useful when \
|
the current hyperhive rev hive-c0re is running against. Also returns the \
|
||||||
you want a trustworthy identity stamp for state files / commit messages / \
|
current `status` text if one has been set via `set_status`. No args. Useful \
|
||||||
|
when you want a trustworthy identity stamp for state files / commit messages / \
|
||||||
cross-agent attribution that won't drift across renames or session-continue \
|
cross-agent attribution that won't drift across renames or session-continue \
|
||||||
boundaries where the system-prompt label could be stale."
|
boundaries where the system-prompt label could be stale."
|
||||||
)]
|
)]
|
||||||
|
|
@ -564,6 +575,22 @@ impl AgentServer {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tool(
|
||||||
|
description = "Set a free-text status string visible on the operator dashboard. \
|
||||||
|
Call this at the START of every task to describe what you're working on (e.g. \
|
||||||
|
`\"reviewing PR #42\"`, `\"fixing bitburner crash\"`, `\"idle\"`). Pass an empty \
|
||||||
|
string to clear. The status is shown on your dashboard card and persists across \
|
||||||
|
harness restarts."
|
||||||
|
)]
|
||||||
|
async fn set_status(&self, Parameters(args): Parameters<SetStatusArgs>) -> String {
|
||||||
|
run_tool_envelope("set_status", args.text.clone(), async move {
|
||||||
|
let (resp, retries) =
|
||||||
|
self.dispatch(hive_sh4re::AgentRequest::SetStatus { text: args.text }).await;
|
||||||
|
annotate_retries(format_ack(resp, "set_status", "status updated".to_owned()), retries)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
#[tool(
|
#[tool(
|
||||||
description = "Cancel an open thread you own — a `question` you asked (the \
|
description = "Cancel an open thread you own — a `question` you asked (the \
|
||||||
asker gets `[cancelled by <you>]` as the answer and unblocks) or a `reminder` \
|
asker gets `[cancelled by <you>]` as the answer and unblocks) or a `reminder` \
|
||||||
|
|
@ -724,6 +751,12 @@ pub struct KillArgs {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
||||||
|
pub struct SetStatusArgs {
|
||||||
|
/// Status text to display on the dashboard card. Pass an empty string to clear.
|
||||||
|
pub text: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
||||||
pub struct StartArgs {
|
pub struct StartArgs {
|
||||||
/// Sub-agent name (without the `h-` container prefix).
|
/// Sub-agent name (without the `h-` container prefix).
|
||||||
|
|
@ -1246,7 +1279,8 @@ impl ManagerServer {
|
||||||
|
|
||||||
#[tool(
|
#[tool(
|
||||||
description = "Self-introspection for the manager: returns canonical name \
|
description = "Self-introspection for the manager: returns canonical name \
|
||||||
(`manager`), role (`manager`), and the current hyperhive rev. Same shape as \
|
(`manager`), role (`manager`), and the current hyperhive rev. Also returns \
|
||||||
|
the current `status` text if one has been set via `set_status`. Same shape as \
|
||||||
the agent flavour; useful for cross-agent attribution / boot announcements / \
|
the agent flavour; useful for cross-agent attribution / boot announcements / \
|
||||||
state-file headers without trusting prompt substitution."
|
state-file headers without trusting prompt substitution."
|
||||||
)]
|
)]
|
||||||
|
|
@ -1258,6 +1292,20 @@ impl ManagerServer {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tool(
|
||||||
|
description = "Set a free-text status string visible on the operator dashboard. \
|
||||||
|
Call this at the START of every task to describe what you're working on. \
|
||||||
|
Pass an empty string to clear. Persists across harness restarts."
|
||||||
|
)]
|
||||||
|
async fn set_status(&self, Parameters(args): Parameters<SetStatusArgs>) -> String {
|
||||||
|
run_tool_envelope("set_status", args.text.clone(), async move {
|
||||||
|
let (resp, retries) =
|
||||||
|
self.dispatch(hive_sh4re::ManagerRequest::SetStatus { text: args.text }).await;
|
||||||
|
annotate_retries(format_ack(resp, "set_status", "status updated".to_owned()), retries)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
#[tool(
|
#[tool(
|
||||||
description = "Cancel any open thread in the swarm — a `question` (cancels \
|
description = "Cancel any open thread in the swarm — a `question` (cancels \
|
||||||
with the operator-override sentinel so the asker unblocks) or a `reminder` \
|
with the operator-override sentinel so the asker unblocks) or a `reminder` \
|
||||||
|
|
@ -1377,6 +1425,7 @@ pub fn allowed_mcp_tools(flavor: Flavor) -> Vec<String> {
|
||||||
"remind",
|
"remind",
|
||||||
"get_loose_ends",
|
"get_loose_ends",
|
||||||
"whoami",
|
"whoami",
|
||||||
|
"set_status",
|
||||||
"cancel_loose_end",
|
"cancel_loose_end",
|
||||||
],
|
],
|
||||||
Flavor::Manager => &[
|
Flavor::Manager => &[
|
||||||
|
|
@ -1395,6 +1444,7 @@ pub fn allowed_mcp_tools(flavor: Flavor) -> Vec<String> {
|
||||||
"get_loose_ends",
|
"get_loose_ends",
|
||||||
"remind",
|
"remind",
|
||||||
"whoami",
|
"whoami",
|
||||||
|
"set_status",
|
||||||
"cancel_loose_end",
|
"cancel_loose_end",
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -727,6 +727,14 @@
|
||||||
}
|
}
|
||||||
body.append(head);
|
body.append(head);
|
||||||
|
|
||||||
|
// ── agent status text ─────────────────────────────────────────
|
||||||
|
if (c.status_text) {
|
||||||
|
body.append(el('div', { class: 'agent-status', title: 'agent self-reported status' },
|
||||||
|
el('span', { class: 'status-icon' }, '◈ '),
|
||||||
|
c.status_text,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
// ── action buttons ───────────────────────────────────────────
|
// ── action buttons ───────────────────────────────────────────
|
||||||
const actions = el('div', { class: 'actions' });
|
const actions = el('div', { class: 'actions' });
|
||||||
if (c.running) {
|
if (c.running) {
|
||||||
|
|
|
||||||
|
|
@ -207,6 +207,16 @@ a:hover {
|
||||||
color: var(--red); border-color: var(--red);
|
color: var(--red); border-color: var(--red);
|
||||||
text-shadow: 0 0 6px rgba(243, 139, 168, 0.5);
|
text-shadow: 0 0 6px rgba(243, 139, 168, 0.5);
|
||||||
}
|
}
|
||||||
|
.agent-status {
|
||||||
|
font-size: 0.82em;
|
||||||
|
color: var(--subtext0, #a6adc8);
|
||||||
|
padding: 0.1em 0.3em 0.25em;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.agent-status .status-icon { opacity: 0.65; }
|
||||||
|
|
||||||
.container-row.tombstone {
|
.container-row.tombstone {
|
||||||
border-style: dashed;
|
border-style: dashed;
|
||||||
background: rgba(24, 24, 37, 0.35);
|
background: rgba(24, 24, 37, 0.35);
|
||||||
|
|
|
||||||
|
|
@ -220,11 +220,39 @@ async fn dispatch(req: &AgentRequest, agent: &str, coord: &Arc<Coordinator>) ->
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AgentRequest::Whoami => AgentResponse::Whoami {
|
AgentRequest::Whoami => {
|
||||||
|
let status_text = crate::container_view::read_agent_status_text(agent);
|
||||||
|
AgentResponse::Whoami {
|
||||||
name: agent.to_owned(),
|
name: agent.to_owned(),
|
||||||
role: "agent".to_owned(),
|
role: "agent".to_owned(),
|
||||||
hyperhive_rev: crate::auto_update::current_flake_rev(&coord.hyperhive_flake),
|
hyperhive_rev: crate::auto_update::current_flake_rev(&coord.hyperhive_flake),
|
||||||
},
|
status_text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AgentRequest::SetStatus { text } => {
|
||||||
|
let path = crate::coordinator::Coordinator::agent_notes_dir(agent)
|
||||||
|
.join("hyperhive-status");
|
||||||
|
let result = if text.trim().is_empty() {
|
||||||
|
// Empty = clear: remove the file (ignore missing).
|
||||||
|
std::fs::remove_file(&path)
|
||||||
|
.or_else(|e| if e.kind() == std::io::ErrorKind::NotFound {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(e)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
std::fs::write(&path, format!("{}\n", text.trim()))
|
||||||
|
};
|
||||||
|
match result {
|
||||||
|
Ok(()) => {
|
||||||
|
// Kick a container rescan so the dashboard updates live.
|
||||||
|
let coord2 = Arc::clone(coord);
|
||||||
|
tokio::spawn(async move { coord2.rescan_containers_and_emit().await });
|
||||||
|
AgentResponse::Ok
|
||||||
|
}
|
||||||
|
Err(e) => AgentResponse::Err { message: format!("set_status write failed: {e}") },
|
||||||
|
}
|
||||||
|
}
|
||||||
AgentRequest::CancelLooseEnd { kind, id } => crate::questions::handle_cancel_loose_end(
|
AgentRequest::CancelLooseEnd { kind, id } => crate::questions::handle_cancel_loose_end(
|
||||||
coord, agent, *kind, *id,
|
coord, agent, *kind, *id,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,11 @@ pub struct ContainerView {
|
||||||
/// the file is absent or the agent declares no links.
|
/// the file is absent or the agent declares no links.
|
||||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
pub extra_links: Vec<DashboardLink>,
|
pub extra_links: Vec<DashboardLink>,
|
||||||
|
/// Free-text status set by the agent via `mcp__hyperhive__set_status`.
|
||||||
|
/// Persisted to `{state_dir}/hyperhive-status`. `None` when the file
|
||||||
|
/// is absent or empty — the agent hasn't set one yet.
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub status_text: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build the full container list. Wraps `lifecycle::list()` and
|
/// Build the full container list. Wraps `lifecycle::list()` and
|
||||||
|
|
@ -119,6 +124,7 @@ pub async fn build_all(coord: &Coordinator) -> Vec<ContainerView> {
|
||||||
.and_then(|(_, model)| resolve_ctx_window(model, &coord.context_window_tokens));
|
.and_then(|(_, model)| resolve_ctx_window(model, &coord.context_window_tokens));
|
||||||
let rate_limited = is_rate_limited(&logical);
|
let rate_limited = is_rate_limited(&logical);
|
||||||
let extra_links = read_dashboard_links(&logical);
|
let extra_links = read_dashboard_links(&logical);
|
||||||
|
let status_text = read_status_text(&logical);
|
||||||
out.push(ContainerView {
|
out.push(ContainerView {
|
||||||
port: lifecycle::agent_web_port(&logical),
|
port: lifecycle::agent_web_port(&logical),
|
||||||
running: lifecycle::is_running(&logical).await,
|
running: lifecycle::is_running(&logical).await,
|
||||||
|
|
@ -133,6 +139,7 @@ pub async fn build_all(coord: &Coordinator) -> Vec<ContainerView> {
|
||||||
context_window_tokens,
|
context_window_tokens,
|
||||||
rate_limited,
|
rate_limited,
|
||||||
extra_links,
|
extra_links,
|
||||||
|
status_text,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
out
|
out
|
||||||
|
|
@ -172,6 +179,20 @@ fn is_rate_limited(name: &str) -> bool {
|
||||||
.exists()
|
.exists()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read the agent's free-text status set via `mcp__hyperhive__set_status`.
|
||||||
|
/// Returns `None` when the file is absent or empty — best-effort, never panics.
|
||||||
|
/// `pub` so `agent_server` and `manager_server` can include it in `Whoami` responses.
|
||||||
|
pub fn read_agent_status_text(name: &str) -> Option<String> {
|
||||||
|
let path = Coordinator::agent_notes_dir(name).join("hyperhive-status");
|
||||||
|
let s = std::fs::read_to_string(path).ok()?;
|
||||||
|
let trimmed = s.trim();
|
||||||
|
if trimmed.is_empty() { None } else { Some(trimmed.to_owned()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_status_text(name: &str) -> Option<String> {
|
||||||
|
read_agent_status_text(name)
|
||||||
|
}
|
||||||
|
|
||||||
/// Read the agent's most recent completed turn from its turn-stats
|
/// Read the agent's most recent completed turn from its turn-stats
|
||||||
/// `SQLite`: the context-window size (prompt tokens) and the model name.
|
/// `SQLite`: the context-window size (prompt tokens) and the model name.
|
||||||
/// Returns `None` when the file is absent or has no rows. Best-effort
|
/// Returns `None` when the file is absent or has no rows. Best-effort
|
||||||
|
|
|
||||||
|
|
@ -480,11 +480,36 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc<Coordinator>) -> ManagerResp
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ManagerRequest::Whoami => ManagerResponse::Whoami {
|
ManagerRequest::Whoami => {
|
||||||
|
let status_text = crate::container_view::read_agent_status_text(MANAGER_AGENT);
|
||||||
|
ManagerResponse::Whoami {
|
||||||
name: MANAGER_AGENT.to_owned(),
|
name: MANAGER_AGENT.to_owned(),
|
||||||
role: "manager".to_owned(),
|
role: "manager".to_owned(),
|
||||||
hyperhive_rev: crate::auto_update::current_flake_rev(&coord.hyperhive_flake),
|
hyperhive_rev: crate::auto_update::current_flake_rev(&coord.hyperhive_flake),
|
||||||
},
|
status_text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ManagerRequest::SetStatus { text } => {
|
||||||
|
let path = Coordinator::agent_notes_dir(MANAGER_AGENT).join("hyperhive-status");
|
||||||
|
let result = if text.trim().is_empty() {
|
||||||
|
std::fs::remove_file(&path)
|
||||||
|
.or_else(|e| if e.kind() == std::io::ErrorKind::NotFound {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(e)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
std::fs::write(&path, format!("{}\n", text.trim()))
|
||||||
|
};
|
||||||
|
match result {
|
||||||
|
Ok(()) => {
|
||||||
|
let coord2 = Arc::clone(coord);
|
||||||
|
tokio::spawn(async move { coord2.rescan_containers_and_emit().await });
|
||||||
|
ManagerResponse::Ok
|
||||||
|
}
|
||||||
|
Err(e) => ManagerResponse::Err { message: format!("set_status write failed: {e}") },
|
||||||
|
}
|
||||||
|
}
|
||||||
ManagerRequest::CancelLooseEnd { kind, id } => crate::questions::handle_cancel_loose_end(
|
ManagerRequest::CancelLooseEnd { kind, id } => crate::questions::handle_cancel_loose_end(
|
||||||
coord,
|
coord,
|
||||||
MANAGER_AGENT,
|
MANAGER_AGENT,
|
||||||
|
|
|
||||||
|
|
@ -443,6 +443,10 @@ pub enum AgentRequest {
|
||||||
/// identity after a rename or session-continue boundary where the
|
/// identity after a rename or session-continue boundary where the
|
||||||
/// system-prompt-substituted label is no longer reliable.
|
/// system-prompt-substituted label is no longer reliable.
|
||||||
Whoami,
|
Whoami,
|
||||||
|
/// Set a free-text status string visible on the dashboard. Persisted
|
||||||
|
/// to `{state_dir}/hyperhive-status` so it survives harness restarts.
|
||||||
|
/// Pass an empty string to clear the status.
|
||||||
|
SetStatus { text: String },
|
||||||
/// Cancel an open thread the agent owns: a `Question` they asked
|
/// Cancel an open thread the agent owns: a `Question` they asked
|
||||||
/// (returns `[cancelled by <self>]` as the answer to the asker)
|
/// (returns `[cancelled by <self>]` as the answer to the asker)
|
||||||
/// or a `Reminder` they scheduled (hard-deletes the row).
|
/// or a `Reminder` they scheduled (hard-deletes the row).
|
||||||
|
|
@ -507,12 +511,15 @@ pub enum AgentResponse {
|
||||||
/// hive-c0re is running against. `role` is `"agent"` for
|
/// hive-c0re is running against. `role` is `"agent"` for
|
||||||
/// sub-agents (the only path that reaches this variant of the
|
/// sub-agents (the only path that reaches this variant of the
|
||||||
/// response). `hyperhive_rev` is `None` only when the configured
|
/// response). `hyperhive_rev` is `None` only when the configured
|
||||||
/// flake URL has no canonical path.
|
/// flake URL has no canonical path. `status_text` is the last
|
||||||
|
/// value written via `SetStatus`, or `None` if none has been set.
|
||||||
Whoami {
|
Whoami {
|
||||||
name: String,
|
name: String,
|
||||||
role: String,
|
role: String,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
hyperhive_rev: Option<String>,
|
hyperhive_rev: Option<String>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
status_text: Option<String>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -843,6 +850,8 @@ pub enum ManagerRequest {
|
||||||
/// Manager-flavour self-introspection. Same wire shape as
|
/// Manager-flavour self-introspection. Same wire shape as
|
||||||
/// `AgentRequest::Whoami`, but `role` is always `"manager"`.
|
/// `AgentRequest::Whoami`, but `role` is always `"manager"`.
|
||||||
Whoami,
|
Whoami,
|
||||||
|
/// Mirror of `AgentRequest::SetStatus` on the manager surface.
|
||||||
|
SetStatus { text: String },
|
||||||
/// Cancel an open thread (question or reminder). Manager surface
|
/// Cancel an open thread (question or reminder). Manager surface
|
||||||
/// can cancel any row (no owner check) — same dispatch as
|
/// can cancel any row (no owner check) — same dispatch as
|
||||||
/// `AgentRequest::CancelLooseEnd` but with privileged auth.
|
/// `AgentRequest::CancelLooseEnd` but with privileged auth.
|
||||||
|
|
@ -925,5 +934,7 @@ pub enum ManagerResponse {
|
||||||
role: String,
|
role: String,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
hyperhive_rev: Option<String>,
|
hyperhive_rev: Option<String>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
status_text: Option<String>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue