update_meta_inputs: require operator approval, rename to request_update_meta_inputs
This commit is contained in:
parent
597e4ba03a
commit
3e098c56ff
7 changed files with 94 additions and 28 deletions
|
|
@ -10,7 +10,7 @@ Tools (hyperhive surface):
|
||||||
- `mcp__hyperhive__start(name)` — start a stopped sub-agent. No approval required.
|
- `mcp__hyperhive__start(name)` — start a stopped sub-agent. No approval required.
|
||||||
- `mcp__hyperhive__restart(name)` — stop + start a sub-agent. No approval required.
|
- `mcp__hyperhive__restart(name)` — stop + start a sub-agent. No approval required.
|
||||||
- `mcp__hyperhive__update(name)` — rebuild a sub-agent (re-applies the current hyperhive flake + agent.nix, restarts the container). No approval required — idempotent. Use when you receive a `needs_update` system event.
|
- `mcp__hyperhive__update(name)` — rebuild a sub-agent (re-applies the current hyperhive flake + agent.nix, restarts the container). No approval required — idempotent. Use when you receive a `needs_update` system event.
|
||||||
- `mcp__hyperhive__update_meta_inputs(inputs?)` — run `nix flake update [inputs...]` on the meta flake and commit the lock changes. Pass specific input names (e.g. `["bitburner-agent"]`) to update just those, or omit / pass `[]` to update everything. Blocks until complete. Does NOT trigger rebuilds — call `update(name)` on affected agents afterward.
|
- `mcp__hyperhive__request_update_meta_inputs(inputs?, description?)` — queue an approval for the operator to run `nix flake update [inputs...]` on the meta flake. Pass specific input names (e.g. `["bitburner-agent"]`) or omit / pass `[]` for all inputs. Returns immediately; lock update runs on operator approval. Does NOT trigger rebuilds — call `update(name)` on affected agents after approval resolves.
|
||||||
- `mcp__hyperhive__get_logs(agent, lines?)` — fetch recent journal lines for a sub-agent container. Use to diagnose MCP-server registration failures, startup crashes, or harness issues you can't see from inside. Pass the plain logical agent name; `lines` defaults to 50 (capped at 500).
|
- `mcp__hyperhive__get_logs(agent, lines?)` — fetch recent journal lines for a sub-agent container. Use to diagnose MCP-server registration failures, startup crashes, or harness issues you can't see from inside. Pass the plain logical agent name; `lines` defaults to 50 (capped at 500).
|
||||||
- `mcp__hyperhive__request_apply_commit(agent, commit_ref, description?)` — submit a config change for any agent (`hm1nd` for self) for operator approval. Pass an optional `description` and it appears on the dashboard approval card so the operator knows what changed without opening the diff. At submit time hive-c0re fetches your commit into the agent's applied repo and pins it as `proposal/<id>`; from that moment your proposed-side commit can be amended or force-pushed freely without changing what the operator will build.
|
- `mcp__hyperhive__request_apply_commit(agent, commit_ref, description?)` — submit a config change for any agent (`hm1nd` for self) for operator approval. Pass an optional `description` and it appears on the dashboard approval card so the operator knows what changed without opening the diff. At submit time hive-c0re fetches your commit into the agent's applied repo and pins it as `proposal/<id>`; from that moment your proposed-side commit can be amended or force-pushed freely without changing what the operator will build.
|
||||||
- `mcp__hyperhive__ask(question, options?, multi?, ttl_seconds?, to?)` — surface a structured question to the operator (default, or `to: "operator"`) OR a sub-agent (`to: "<agent-name>"`). Returns immediately with a question id; the answer arrives later as a system `question_answered { id, question, answer, answerer }` event in your inbox. Options are advisory: the dashboard always lets the operator type a free-text answer in addition. Set `multi: true` to render options as checkboxes (operator can pick multiple); the answer comes back as `, `-separated. Set `ttl_seconds` to auto-cancel after a deadline (capped at 6h server-side) — on expiry the answer is `[expired]` and `answerer` is `"ttl-watchdog"`. Do not poll inside the same turn — finish the current work and react when the event lands.
|
- `mcp__hyperhive__ask(question, options?, multi?, ttl_seconds?, to?)` — surface a structured question to the operator (default, or `to: "operator"`) OR a sub-agent (`to: "<agent-name>"`). Returns immediately with a question id; the answer arrives later as a system `question_answered { id, question, answer, answerer }` event in your inbox. Options are advisory: the dashboard always lets the operator type a free-text answer in addition. Set `multi: true` to render options as checkboxes (operator can pick multiple); the answer comes back as `, `-separated. Set `ttl_seconds` to auto-cancel after a deadline (capped at 6h server-side) — on expiry the answer is `[expired]` and `answerer` is `"ttl-watchdog"`. Do not poll inside the same turn — finish the current work and react when the event lands.
|
||||||
|
|
|
||||||
|
|
@ -813,6 +813,9 @@ pub struct UpdateMetaInputsArgs {
|
||||||
/// Pass an empty list to update ALL inputs.
|
/// Pass an empty list to update ALL inputs.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub inputs: Vec<String>,
|
pub inputs: Vec<String>,
|
||||||
|
/// Optional description shown on the dashboard approval card.
|
||||||
|
#[serde(default)]
|
||||||
|
pub description: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
||||||
|
|
@ -1031,31 +1034,36 @@ impl ManagerServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tool(
|
#[tool(
|
||||||
description = "Run `nix flake update` on the meta flake and commit the resulting \
|
description = "Queue an approval for the operator to run `nix flake update` on the \
|
||||||
`flake.lock` changes. Pass specific input names to update only those inputs \
|
meta flake and commit the resulting lock changes. Pass specific input names to update \
|
||||||
(e.g. `[\"bitburner-agent\"]`), or pass an empty list to update ALL inputs. \
|
only those inputs (e.g. `[\"bitburner-agent\"]`), or pass an empty list to update ALL \
|
||||||
Blocks until the lock step completes (~30-120s depending on download size). \
|
inputs. Returns immediately — the lock update runs when the operator approves. \
|
||||||
Does NOT trigger container rebuilds — call `update` on each affected agent \
|
Does NOT trigger container rebuilds — call `update` on each affected agent \
|
||||||
separately after the lock settles."
|
separately after the approval resolves."
|
||||||
)]
|
)]
|
||||||
async fn update_meta_inputs(
|
async fn request_update_meta_inputs(
|
||||||
&self,
|
&self,
|
||||||
Parameters(args): Parameters<UpdateMetaInputsArgs>,
|
Parameters(args): Parameters<UpdateMetaInputsArgs>,
|
||||||
) -> String {
|
) -> String {
|
||||||
let log = format!("{args:?}");
|
let log = format!("{args:?}");
|
||||||
run_tool_envelope("update_meta_inputs", log, async move {
|
run_tool_envelope("request_update_meta_inputs", log, async move {
|
||||||
let label = if args.inputs.is_empty() {
|
let label = if args.inputs.is_empty() {
|
||||||
"all inputs".to_string()
|
"all inputs".to_string()
|
||||||
} else {
|
} else {
|
||||||
args.inputs.join(", ")
|
args.inputs.join(", ")
|
||||||
};
|
};
|
||||||
let (resp, retries) = self
|
let (resp, retries) = self
|
||||||
.dispatch(hive_sh4re::ManagerRequest::UpdateMetaInputs {
|
.dispatch(hive_sh4re::ManagerRequest::RequestUpdateMetaInputs {
|
||||||
inputs: args.inputs,
|
inputs: args.inputs,
|
||||||
|
description: args.description,
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
annotate_retries(
|
annotate_retries(
|
||||||
format_ack(resp, "update_meta_inputs", format!("lock updated: {label}")),
|
format_ack(
|
||||||
|
resp,
|
||||||
|
"request_update_meta_inputs",
|
||||||
|
format!("approval queued: {label}"),
|
||||||
|
),
|
||||||
retries,
|
retries,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,14 @@ pub async fn approve(coord: Arc<Coordinator>, id: i64) -> Result<()> {
|
||||||
.await;
|
.await;
|
||||||
finish_approval(&coord, &approval, result, None)
|
finish_approval(&coord, &approval, result, None)
|
||||||
}
|
}
|
||||||
|
ApprovalKind::UpdateMetaInputs => {
|
||||||
|
// Decode the inputs from the commit_ref field (stored as JSON
|
||||||
|
// by submit_apply_commit's counterpart in manager_server.rs).
|
||||||
|
let inputs: Vec<String> =
|
||||||
|
serde_json::from_str(&approval.commit_ref).unwrap_or_default();
|
||||||
|
let result = crate::meta::lock_update(&inputs).await;
|
||||||
|
finish_approval(&coord, &approval, result, None)
|
||||||
|
}
|
||||||
ApprovalKind::Spawn => {
|
ApprovalKind::Spawn => {
|
||||||
// Run the spawn in the background so the approve POST returns
|
// Run the spawn in the background so the approve POST returns
|
||||||
// immediately. The dashboard reads `transient` to render a spinner.
|
// immediately. The dashboard reads `transient` to render a spinner.
|
||||||
|
|
@ -158,6 +166,7 @@ fn finish_approval(
|
||||||
ApprovalKind::Spawn => "spawn",
|
ApprovalKind::Spawn => "spawn",
|
||||||
ApprovalKind::ApplyCommit => "apply_commit",
|
ApprovalKind::ApplyCommit => "apply_commit",
|
||||||
ApprovalKind::InitConfig => "init_config",
|
ApprovalKind::InitConfig => "init_config",
|
||||||
|
ApprovalKind::UpdateMetaInputs => "update_meta_inputs",
|
||||||
};
|
};
|
||||||
let sha_short = approval
|
let sha_short = approval
|
||||||
.fetched_sha
|
.fetched_sha
|
||||||
|
|
@ -199,6 +208,9 @@ fn finish_approval(
|
||||||
sha: approval.fetched_sha.clone(),
|
sha: approval.fetched_sha.clone(),
|
||||||
tag: terminal_tag,
|
tag: terminal_tag,
|
||||||
}),
|
}),
|
||||||
|
// UpdateMetaInputs: ApprovalResolved already carries the result.
|
||||||
|
// No separate lifecycle event needed.
|
||||||
|
ApprovalKind::UpdateMetaInputs => {}
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
@ -462,6 +474,7 @@ pub async fn deny(coord: &Coordinator, id: i64, note: Option<&str>) -> Result<()
|
||||||
ApprovalKind::Spawn => "spawn",
|
ApprovalKind::Spawn => "spawn",
|
||||||
ApprovalKind::ApplyCommit => "apply_commit",
|
ApprovalKind::ApplyCommit => "apply_commit",
|
||||||
ApprovalKind::InitConfig => "init_config",
|
ApprovalKind::InitConfig => "init_config",
|
||||||
|
ApprovalKind::UpdateMetaInputs => "update_meta_inputs",
|
||||||
};
|
};
|
||||||
let sha_short = sha.as_deref().map(|s| s[..s.len().min(12)].to_owned());
|
let sha_short = sha.as_deref().map(|s| s[..s.len().min(12)].to_owned());
|
||||||
let description = a.description.clone();
|
let description = a.description.clone();
|
||||||
|
|
|
||||||
|
|
@ -285,6 +285,7 @@ fn row_to_approval(row: &rusqlite::Row<'_>) -> rusqlite::Result<Approval> {
|
||||||
"apply_commit" => ApprovalKind::ApplyCommit,
|
"apply_commit" => ApprovalKind::ApplyCommit,
|
||||||
"spawn" => ApprovalKind::Spawn,
|
"spawn" => ApprovalKind::Spawn,
|
||||||
"init_config" => ApprovalKind::InitConfig,
|
"init_config" => ApprovalKind::InitConfig,
|
||||||
|
"update_meta_inputs" => ApprovalKind::UpdateMetaInputs,
|
||||||
other => {
|
other => {
|
||||||
return Err(rusqlite::Error::FromSqlConversionFailure(
|
return Err(rusqlite::Error::FromSqlConversionFailure(
|
||||||
2,
|
2,
|
||||||
|
|
@ -326,6 +327,7 @@ fn kind_to_str(kind: ApprovalKind) -> &'static str {
|
||||||
ApprovalKind::ApplyCommit => "apply_commit",
|
ApprovalKind::ApplyCommit => "apply_commit",
|
||||||
ApprovalKind::Spawn => "spawn",
|
ApprovalKind::Spawn => "spawn",
|
||||||
ApprovalKind::InitConfig => "init_config",
|
ApprovalKind::InitConfig => "init_config",
|
||||||
|
ApprovalKind::UpdateMetaInputs => "update_meta_inputs",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -334,6 +336,7 @@ fn kind_from_str(s: &str) -> Result<ApprovalKind> {
|
||||||
"apply_commit" => ApprovalKind::ApplyCommit,
|
"apply_commit" => ApprovalKind::ApplyCommit,
|
||||||
"spawn" => ApprovalKind::Spawn,
|
"spawn" => ApprovalKind::Spawn,
|
||||||
"init_config" => ApprovalKind::InitConfig,
|
"init_config" => ApprovalKind::InitConfig,
|
||||||
|
"update_meta_inputs" => ApprovalKind::UpdateMetaInputs,
|
||||||
other => bail!("unknown approval kind '{other}'"),
|
other => bail!("unknown approval kind '{other}'"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -575,6 +575,7 @@ fn history_view(a: Approval) -> ApprovalHistoryView {
|
||||||
hive_sh4re::ApprovalKind::ApplyCommit => "apply_commit",
|
hive_sh4re::ApprovalKind::ApplyCommit => "apply_commit",
|
||||||
hive_sh4re::ApprovalKind::Spawn => "spawn",
|
hive_sh4re::ApprovalKind::Spawn => "spawn",
|
||||||
hive_sh4re::ApprovalKind::InitConfig => "init_config",
|
hive_sh4re::ApprovalKind::InitConfig => "init_config",
|
||||||
|
hive_sh4re::ApprovalKind::UpdateMetaInputs => "update_meta_inputs",
|
||||||
};
|
};
|
||||||
ApprovalHistoryView {
|
ApprovalHistoryView {
|
||||||
id: a.id,
|
id: a.id,
|
||||||
|
|
@ -623,6 +624,14 @@ async fn build_approval_views(approvals: Vec<Approval>) -> Vec<ApprovalView> {
|
||||||
diff: None,
|
diff: None,
|
||||||
description: a.description,
|
description: a.description,
|
||||||
},
|
},
|
||||||
|
hive_sh4re::ApprovalKind::UpdateMetaInputs => ApprovalView {
|
||||||
|
id: a.id,
|
||||||
|
agent: a.agent,
|
||||||
|
kind: "update_meta_inputs",
|
||||||
|
sha_short: None,
|
||||||
|
diff: None,
|
||||||
|
description: a.description,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
out
|
out
|
||||||
|
|
|
||||||
|
|
@ -311,22 +311,48 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc<Coordinator>) -> ManagerResp
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ManagerRequest::UpdateMetaInputs { inputs } => {
|
ManagerRequest::RequestUpdateMetaInputs {
|
||||||
|
inputs,
|
||||||
|
description,
|
||||||
|
} => {
|
||||||
let label = if inputs.is_empty() {
|
let label = if inputs.is_empty() {
|
||||||
"all inputs".to_string()
|
"all inputs".to_string()
|
||||||
} else {
|
} else {
|
||||||
inputs.join(", ")
|
inputs.join(", ")
|
||||||
};
|
};
|
||||||
tracing::info!(%label, "manager: update_meta_inputs");
|
tracing::info!(%label, "manager: request_update_meta_inputs");
|
||||||
// Treat empty list as "update all" by passing the full list
|
// Encode the inputs list as JSON and store it in commit_ref
|
||||||
// to lock_update; it calls bare `nix flake update` when empty.
|
// (there's no git commit involved; the field carries the
|
||||||
match crate::meta::lock_update(inputs).await {
|
// payload for the approval handler to decode at run time).
|
||||||
Ok(()) => ManagerResponse::Ok,
|
let commit_ref = serde_json::to_string(inputs).unwrap_or_default();
|
||||||
Err(e) => ManagerResponse::Err {
|
let id = match coord
|
||||||
message: format!("update_meta_inputs({label}): {e:#}"),
|
.approvals
|
||||||
},
|
.submit_kind(
|
||||||
|
hive_sh4re::MANAGER_AGENT,
|
||||||
|
hive_sh4re::ApprovalKind::UpdateMetaInputs,
|
||||||
|
&commit_ref,
|
||||||
|
description.as_deref(),
|
||||||
|
)
|
||||||
|
.map_err(|e| anyhow::anyhow!("{e:#}"))
|
||||||
|
{
|
||||||
|
Ok(id) => id,
|
||||||
|
Err(e) => {
|
||||||
|
return ManagerResponse::Err {
|
||||||
|
message: format!("queue update_meta_inputs approval: {e:#}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
tracing::info!(%id, %label, "update_meta_inputs approval queued");
|
||||||
|
coord.emit_approval_added(
|
||||||
|
id,
|
||||||
|
hive_sh4re::MANAGER_AGENT,
|
||||||
|
"update_meta_inputs",
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
description.clone(),
|
||||||
|
);
|
||||||
|
ManagerResponse::Ok
|
||||||
|
}
|
||||||
ManagerRequest::Ask {
|
ManagerRequest::Ask {
|
||||||
question,
|
question,
|
||||||
options,
|
options,
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,11 @@ pub enum ApprovalKind {
|
||||||
/// template but does NOT create the container - that requires a
|
/// template but does NOT create the container - that requires a
|
||||||
/// subsequent `RequestSpawn` approval.
|
/// subsequent `RequestSpawn` approval.
|
||||||
InitConfig,
|
InitConfig,
|
||||||
|
/// Run `nix flake update [inputs...]` on the meta flake and commit
|
||||||
|
/// the resulting lock changes. The `commit_ref` field stores the
|
||||||
|
/// JSON-encoded inputs array (`"[]"` = all inputs). Agent field is
|
||||||
|
/// set to `hm1nd` (the requesting manager).
|
||||||
|
UpdateMetaInputs,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
|
@ -842,16 +847,18 @@ pub enum ManagerRequest {
|
||||||
/// message into the manager's own broker inbox. `from` is caller-
|
/// message into the manager's own broker inbox. `from` is caller-
|
||||||
/// chosen; `body` becomes the wake prompt body.
|
/// chosen; `body` becomes the wake prompt body.
|
||||||
Wake { from: String, body: String },
|
Wake { from: String, body: String },
|
||||||
/// Run `nix flake update [inputs...]` on the meta flake and commit
|
/// Queue an approval to run `nix flake update [inputs...]` on the
|
||||||
/// the resulting `flake.lock` changes. `inputs` is the list of
|
/// meta flake. `inputs` is the list of named inputs to update
|
||||||
/// named inputs to update (e.g. `["bitburner-agent", "nixpkgs"]`).
|
/// (e.g. `["bitburner-agent", "nixpkgs"]`). Pass an empty list to
|
||||||
/// Pass an empty list to update ALL inputs (equivalent to bare
|
/// update ALL inputs. On operator approval hive-c0re runs the lock
|
||||||
/// `nix flake update`). Blocks until the lock step completes;
|
/// update and commits the result. The `UpdateMetaInputs` approval
|
||||||
/// does NOT trigger a rebuild — call `update` on each affected
|
/// resolves with `ApprovalResolved` in the manager inbox.
|
||||||
/// agent separately after the lock settles.
|
RequestUpdateMetaInputs {
|
||||||
UpdateMetaInputs {
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
inputs: Vec<String>,
|
inputs: Vec<String>,
|
||||||
|
/// Optional description shown on the dashboard approval card.
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
description: Option<String>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue