Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27ac0153c4 | ||
|
|
f1d54ce12c | ||
|
|
867be7bb98 |
5 changed files with 304 additions and 1 deletions
|
|
@ -59,6 +59,11 @@ pub enum SocketReply {
|
|||
hive_name: Option<String>,
|
||||
swarm_name: Option<String>,
|
||||
},
|
||||
/// `create_repo` result — the new repo's full name + clone URL.
|
||||
RepoCreated {
|
||||
full_name: String,
|
||||
clone_url: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<hive_sh4re::Response> for SocketReply {
|
||||
|
|
@ -100,6 +105,13 @@ impl From<hive_sh4re::Response> for SocketReply {
|
|||
hive_name,
|
||||
swarm_name,
|
||||
},
|
||||
hive_sh4re::Response::RepoCreated {
|
||||
full_name,
|
||||
clone_url,
|
||||
} => Self::RepoCreated {
|
||||
full_name,
|
||||
clone_url,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -910,6 +922,35 @@ impl AgentServer {
|
|||
.await
|
||||
}
|
||||
|
||||
#[tool(
|
||||
description = "Create a git repo through hive-c0re. You CANNOT create repos with your \
|
||||
own forge token (creation is disabled) — this is the only path. The repo is created in \
|
||||
the c0re-owned `agents` org, you're added as a write collaborator (not owner), and the \
|
||||
default branch gets branch protection so merges require an operator-team approval — you \
|
||||
cannot merge your own PRs. `repo` is a single name segment (letters, digits, `-`, `_`, \
|
||||
`.`). Returns the new repo's full name + clone URL; clone it over \
|
||||
`http://localhost:3000/agents/<repo>.git` and push/open PRs as normal."
|
||||
)]
|
||||
async fn create_repo(&self, Parameters(args): Parameters<CreateRepoArgs>) -> String {
|
||||
let log = format!("{args:?}");
|
||||
run_tool_envelope("create_repo", log, async move {
|
||||
let (resp, retries) = self
|
||||
.dispatch(hive_sh4re::Request::CreateRepo { repo: args.repo })
|
||||
.await;
|
||||
let s = match resp {
|
||||
Ok(SocketReply::RepoCreated {
|
||||
full_name,
|
||||
clone_url,
|
||||
}) => format!("created repo {full_name} — clone: {clone_url}"),
|
||||
Ok(SocketReply::Err(m)) => format!("create_repo failed: {m}"),
|
||||
Ok(other) => format!("create_repo unexpected response: {other:?}"),
|
||||
Err(e) => format!("create_repo transport error: {e:#}"),
|
||||
};
|
||||
annotate_retries(s, retries)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[tool(
|
||||
description = "Schedule a reminder that lands in this agent's own inbox at a future \
|
||||
time (sender will appear as `reminder`). Use for self-paced follow-ups: 'check task \
|
||||
|
|
@ -1505,6 +1546,13 @@ pub struct SetStatusArgs {
|
|||
pub text: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
||||
pub struct CreateRepoArgs {
|
||||
/// Repo name — a single segment of letters, digits, `-`, `_`, `.`
|
||||
/// (no leading `-`/`.`). The repo is created as `agents/<repo>`.
|
||||
pub repo: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
||||
pub struct GetAgentMetaArgs {
|
||||
/// Logical name of the agent to query (e.g. `"iris"`, `"manager"`).
|
||||
|
|
|
|||
|
|
@ -183,6 +183,7 @@ pub(crate) async fn dispatch_shared(
|
|||
|()| hive_sh4re::Response::Ok,
|
||||
)
|
||||
}
|
||||
hive_sh4re::Request::CreateRepo { repo } => handle_create_repo(agent, repo).await,
|
||||
hive_sh4re::Request::AckTurn => handle_ack_turn(coord, agent),
|
||||
hive_sh4re::Request::RequeueInflight => handle_requeue_inflight(coord, agent),
|
||||
hive_sh4re::Request::GracefulStopComplete => {
|
||||
|
|
@ -304,6 +305,46 @@ fn handle_set_status(coord: &Arc<Coordinator>, text: &str) -> hive_sh4re::Respon
|
|||
hive_sh4re::Response::Ok
|
||||
}
|
||||
|
||||
/// Validate an agent-supplied repo name: a single safe slug segment, no
|
||||
/// path traversal. Forgejo validates server-side too, but rejecting early
|
||||
/// gives a clear message and avoids building odd API paths.
|
||||
fn valid_repo_name(name: &str) -> bool {
|
||||
!name.is_empty()
|
||||
&& name.len() <= 100
|
||||
&& !name.starts_with(['-', '.'])
|
||||
&& name
|
||||
.chars()
|
||||
.all(|c| c.is_ascii_alphanumeric() || matches!(c, '-' | '_' | '.'))
|
||||
}
|
||||
|
||||
/// `CreateRepo` — create a repo for `agent` *through hive-c0re* in the
|
||||
/// c0re-owned `agents` org with operator-team branch protection.
|
||||
/// The sanctioned create path now that agents can't create repos directly.
|
||||
async fn handle_create_repo(agent: &str, repo: &str) -> hive_sh4re::Response {
|
||||
if !valid_repo_name(repo) {
|
||||
return hive_sh4re::Response::Err {
|
||||
message: format!(
|
||||
"invalid repo name {repo:?} — single segment of letters, digits, '-', '_', '.' \
|
||||
(no leading '-'/'.', max 100 chars)"
|
||||
),
|
||||
};
|
||||
}
|
||||
let Some(core_token) = crate::forge::core_token() else {
|
||||
return hive_sh4re::Response::Err {
|
||||
message: "forge unavailable (no core token) — cannot create repo".to_owned(),
|
||||
};
|
||||
};
|
||||
match crate::forge::create_agent_repo(agent, repo, &core_token).await {
|
||||
Ok(full_name) => hive_sh4re::Response::RepoCreated {
|
||||
clone_url: format!("{}/{full_name}.git", crate::forge::FORGE_HTTP),
|
||||
full_name,
|
||||
},
|
||||
Err(e) => hive_sh4re::Response::Err {
|
||||
message: format!("create repo {repo:?} failed: {e:#}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// `GetAgentMeta` — identity + live status for `name` (defaults to the
|
||||
/// caller). Reads the live container-view status and the hive/swarm
|
||||
/// display names.
|
||||
|
|
|
|||
|
|
@ -51,9 +51,30 @@ const SHARED_DOCS_REPO: &str = "docs";
|
|||
/// Bind-mounted read-only into every container at `/knowledge`.
|
||||
/// See `hive-c0re/src/knowledge.rs`.
|
||||
const KNOWLEDGE_REPO: &str = crate::knowledge::REPO;
|
||||
/// Forgejo org that owns agent-created repos. Agents can't create
|
||||
/// repos with their own token (`max_repo_creation = 0`); instead hive-c0re
|
||||
/// creates them here and adds the requesting agent as a **write** member
|
||||
/// (not owner/admin). Because the org — not the agent — owns the repo,
|
||||
/// perms stay c0re-managed and branch protection (referencing
|
||||
/// [`OPERATORS_TEAM`]) can block the author from merging their own PR. This
|
||||
/// is the "agents namespace" repos land in by default.
|
||||
const AGENTS_ORG: &str = "agents";
|
||||
/// Operator merge-gate team inside [`AGENTS_ORG`]. Provisioned **empty** by
|
||||
/// hive-c0re (so perms can be set before anyone joins); the operator adds
|
||||
/// herself via the forge UI / hivectl. Branch protection on agents-org repos
|
||||
/// references this team by name for the merge/approval whitelist, so the
|
||||
/// rule never hardcodes a specific reviewer agent (which may not exist).
|
||||
const OPERATORS_TEAM: &str = "operators";
|
||||
/// Hive-managed Forgejo namespaces that agent-initiated repo creation must
|
||||
/// never target. `internal` is operator-curated shared content;
|
||||
/// `agent-configs` + `core` are hive-c0re-internal mirror/meta namespaces.
|
||||
/// (`hyperhive` is NOT managed — it's just a repo that happens to be built
|
||||
/// by this hive.) hive-c0re's create path forces [`AGENTS_ORG`], so this is
|
||||
/// a defensive guard against any future caller passing an explicit owner.
|
||||
const HIVE_MANAGED_NAMESPACES: &[&str] = &[SHARED_ORG, CONFIG_ORG, "core"];
|
||||
/// Forgejo orgs hive-c0re ensures on startup. The meta repo lives at
|
||||
/// `core/meta` (the `core` user's own namespace — no org needed).
|
||||
const SEEDED_ORGS: &[&str] = &[CONFIG_ORG, SHARED_ORG];
|
||||
const SEEDED_ORGS: &[&str] = &[CONFIG_ORG, SHARED_ORG, AGENTS_ORG];
|
||||
/// Per-agent token scopes (broad-but-not-admin). See
|
||||
/// `docs/forge.md::Token scopes` for the per-scope rationale.
|
||||
const TOKEN_SCOPES: &str = "read:user,write:user,read:notification,write:notification,write:repository,write:issue,write:organization,write:misc";
|
||||
|
|
@ -249,6 +270,57 @@ async fn ensure_user_email(name: &str) {
|
|||
}
|
||||
}
|
||||
|
||||
/// Disable direct repo creation for agent `name` by setting
|
||||
/// `max_repo_creation = 0` on its Forgejo account. Agents must
|
||||
/// create repos *through hive-c0re* (which owns the perms), never with
|
||||
/// their own token — a write-scoped token can otherwise create + own
|
||||
/// repos and self-merge, bypassing the operator-only-merge policy.
|
||||
///
|
||||
/// `max_repo_creation = 0` means `CanCreateRepo()` is false for any
|
||||
/// count (Forgejo: `MaxRepoCreation >= 0 && NumRepos >= MaxRepoCreation`),
|
||||
/// so creation is refused while push / PR / clone stay intact. **Existing
|
||||
/// repos are untouched** — this only blocks *new* direct creation.
|
||||
///
|
||||
/// Marker-guarded like [`ensure_user_email`]: the PATCH runs once per
|
||||
/// agent (delete the marker to re-apply). Body carries `login_name` +
|
||||
/// `source_id` for the same reason `ensure_user_email` does — omitting
|
||||
/// `login_name` makes Forgejo's `EditUserOption` validator reset
|
||||
/// `use_custom_avatar`. Best-effort: failures warn, don't propagate.
|
||||
async fn ensure_repo_creation_disabled(name: &str) {
|
||||
let marker = crate::paths::forge_repo_creation_disabled_marker(name);
|
||||
if marker.exists() {
|
||||
return;
|
||||
}
|
||||
let Some(token) = core_token() else {
|
||||
tracing::debug!(%name, "forge: skipping ensure_repo_creation_disabled — no core token yet");
|
||||
return;
|
||||
};
|
||||
let body = format!(r#"{{"login_name":"{name}","source_id":0,"max_repo_creation":0}}"#);
|
||||
let url = format!("{FORGE_HTTP}/api/v1/admin/users/{name}");
|
||||
match forge_http(reqwest::Method::PATCH, &url, &token, &body).await {
|
||||
Ok(status) if status.is_success() => {
|
||||
if let Some(parent) = marker.parent() {
|
||||
std::fs::create_dir_all(parent).ok();
|
||||
}
|
||||
std::fs::write(&marker, "").ok();
|
||||
tracing::info!(%name, "forge: disabled direct repo creation (max_repo_creation=0)");
|
||||
}
|
||||
Ok(status) if status == reqwest::StatusCode::FORBIDDEN => {
|
||||
tracing::warn!(
|
||||
%name, %status,
|
||||
"forge: PATCH max_repo_creation forbidden — core token likely missing admin scope. \
|
||||
Delete {CORE_TOKEN_PATH} and restart hive-c0re to re-mint with the new scopes."
|
||||
);
|
||||
}
|
||||
Ok(status) => {
|
||||
tracing::warn!(%name, %status, "forge: PATCH max_repo_creation returned non-success");
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!(%name, error = %e, "forge: PATCH max_repo_creation transport error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Mint a fresh access token for `name`. Token name is suffixed with
|
||||
/// a monotonic clock so re-issuing doesn't collide with an existing
|
||||
/// token of the same name in the DB. `scopes` is the scope string
|
||||
|
|
@ -821,6 +893,105 @@ async fn ensure_org(name: &str, admin_token: &str) -> Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Whether `ns` is a hive-managed Forgejo namespace that agent-initiated
|
||||
/// repo creation must never target — `internal` (operator-curated
|
||||
/// shared content) + `agent-configs` / `core` (hive-c0re-internal). The
|
||||
/// create path forces [`AGENTS_ORG`], so this guards a future surface that
|
||||
/// might accept an explicit owner.
|
||||
#[must_use]
|
||||
pub fn is_hive_managed_namespace(ns: &str) -> bool {
|
||||
HIVE_MANAGED_NAMESPACES.contains(&ns)
|
||||
}
|
||||
|
||||
/// Provision the [`OPERATORS_TEAM`] inside [`AGENTS_ORG`] as an **empty**
|
||||
/// team. Branch protection on agents-org repos references it as the
|
||||
/// merge/approval whitelist; the operator adds herself as a member via the
|
||||
/// forge UI / hivectl. `includes_all_repositories` so the gate applies to
|
||||
/// every agent repo; `write` is enough to approve + merge. hive-c0re never
|
||||
/// manages membership. Idempotent (422/409 = already exists).
|
||||
async fn ensure_operators_team(token: &str) -> Result<()> {
|
||||
let url = format!("{FORGE_HTTP}/api/v1/orgs/{AGENTS_ORG}/teams");
|
||||
let body = format!(
|
||||
r#"{{"name":"{OPERATORS_TEAM}","description":"hyperhive operators — merge gate for agent repos","permission":"write","includes_all_repositories":true,"can_create_org_repo":false}}"#
|
||||
);
|
||||
let status = forge_http(reqwest::Method::POST, &url, token, &body).await?;
|
||||
match status.as_u16() {
|
||||
201 => {
|
||||
tracing::info!("forge: created {OPERATORS_TEAM} team in {AGENTS_ORG}");
|
||||
Ok(())
|
||||
}
|
||||
409 | 422 => {
|
||||
tracing::debug!("forge: {OPERATORS_TEAM} team already exists");
|
||||
Ok(())
|
||||
}
|
||||
other => {
|
||||
anyhow::bail!("POST /orgs/{AGENTS_ORG}/teams ({OPERATORS_TEAM}) returned HTTP {other}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Add `user` as a collaborator on `owner/repo` at `permission`
|
||||
/// (`read` / `write` / `admin`). Idempotent: 201 (added) and 204 (already a
|
||||
/// collaborator / permission updated) both count as success.
|
||||
async fn add_collaborator(
|
||||
owner: &str,
|
||||
repo: &str,
|
||||
user: &str,
|
||||
permission: &str,
|
||||
token: &str,
|
||||
) -> Result<()> {
|
||||
let url = format!("{FORGE_HTTP}/api/v1/repos/{owner}/{repo}/collaborators/{user}");
|
||||
let body = format!(r#"{{"permission":"{permission}"}}"#);
|
||||
let status = forge_http(reqwest::Method::PUT, &url, token, &body).await?;
|
||||
match status.as_u16() {
|
||||
201 | 204 => {
|
||||
tracing::debug!(%owner, %repo, %user, %permission, "forge: collaborator set");
|
||||
Ok(())
|
||||
}
|
||||
other => {
|
||||
anyhow::bail!("PUT {owner}/{repo}/collaborators/{user} returned HTTP {other}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply the operator merge-gate branch protection to `repo`'s default
|
||||
/// branch: only [`OPERATORS_TEAM`] members can merge, and an
|
||||
/// approving review from that team is required — so the author (a write-level
|
||||
/// agent, not in the team) cannot merge its own PR. Idempotent: an existing
|
||||
/// rule for the branch (200/409/422) is treated as success.
|
||||
async fn apply_operator_branch_protection(repo: &str, token: &str) -> Result<()> {
|
||||
let url = format!("{FORGE_HTTP}/api/v1/repos/{AGENTS_ORG}/{repo}/branch_protections");
|
||||
let body = format!(
|
||||
r#"{{"branch_name":"main","enable_merge_whitelist":true,"merge_whitelist_teams":["{OPERATORS_TEAM}"],"enable_approvals_whitelist":true,"approvals_whitelist_teams":["{OPERATORS_TEAM}"],"required_approvals":1,"block_on_official_review_requests":true}}"#
|
||||
);
|
||||
let status = forge_http(reqwest::Method::POST, &url, token, &body).await?;
|
||||
match status.as_u16() {
|
||||
201 => {
|
||||
tracing::info!(%repo, "forge: applied operator branch protection");
|
||||
Ok(())
|
||||
}
|
||||
200 | 409 | 422 => {
|
||||
tracing::debug!(%repo, "forge: branch protection already present");
|
||||
Ok(())
|
||||
}
|
||||
other => anyhow::bail!("POST {AGENTS_ORG}/{repo}/branch_protections returned HTTP {other}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a repo for `agent` in the c0re-owned [`AGENTS_ORG`] and wire the
|
||||
/// perms: the org owns it (perms stay c0re-managed), the agent is added
|
||||
/// as a **write** collaborator (not owner — can push + open PRs but can't
|
||||
/// bypass branch protection), and the default branch gets the operator
|
||||
/// merge gate. This is the sanctioned create path now that agents can't
|
||||
/// create repos directly (`max_repo_creation = 0`). Idempotent.
|
||||
pub async fn create_agent_repo(agent: &str, repo: &str, core_token: &str) -> Result<String> {
|
||||
ensure_org_repo(AGENTS_ORG, repo, core_token).await?;
|
||||
add_collaborator(AGENTS_ORG, repo, agent, "write", core_token).await?;
|
||||
apply_operator_branch_protection(repo, core_token).await?;
|
||||
tracing::info!(%agent, %repo, "forge: created agent repo in {AGENTS_ORG} with operator merge gate");
|
||||
Ok(format!("{AGENTS_ORG}/{repo}"))
|
||||
}
|
||||
|
||||
/// Per-agent forge sync: ensure the agent has a forgejo user + token,
|
||||
/// a mirrored config repo, read access to `core/meta`, and the `meta`
|
||||
/// remote in its proposed repo. All operations are idempotent; failures
|
||||
|
|
@ -840,6 +1011,10 @@ pub async fn sync_agent(name: &str, core_token: Option<&str>) {
|
|||
// so commits link to the agent's Forgejo profile. Best-effort;
|
||||
// also patches up agents created before this fix (old @hive.local).
|
||||
ensure_user_email(name).await;
|
||||
// Block direct agent-initiated repo creation: agents create
|
||||
// repos through hive-c0re, never with their own token. Idempotent +
|
||||
// marker-guarded; also covers agents provisioned before this landed.
|
||||
ensure_repo_creation_disabled(name).await;
|
||||
// Mirror the agent's applied config repo into agent-configs.
|
||||
// ensure_config_repo is idempotent; push_config catches any
|
||||
// drift since the last run — e.g. the startup migration just
|
||||
|
|
@ -896,6 +1071,12 @@ pub async fn ensure_all() {
|
|||
tracing::warn!(%org, error = ?e, "forge: ensure_org failed");
|
||||
}
|
||||
}
|
||||
// Provision the operator merge-gate team (empty) inside the agents
|
||||
// org so branch protection can reference it before anyone joins
|
||||
//. The operator adds herself as a member out-of-band.
|
||||
if let Err(e) = ensure_operators_team(token).await {
|
||||
tracing::warn!(error = ?e, "forge: ensure_operators_team failed");
|
||||
}
|
||||
// Meta repo lives at core/meta — pushed from git_commit in
|
||||
// meta.rs on every deploy/lock-update. Make sure it exists
|
||||
// before the first push hits a 404.
|
||||
|
|
|
|||
|
|
@ -60,6 +60,16 @@ pub fn forge_email_aligned_marker(name: &str) -> PathBuf {
|
|||
forge_dir().join(format!("email-aligned-{name}"))
|
||||
}
|
||||
|
||||
/// `forge/repo-creation-disabled-<name>` — marker: `<name>`'s forge user
|
||||
/// has had `max_repo_creation = 0` applied (blocks direct agent-initiated
|
||||
/// repo creation). One-shot guard so the PATCH runs once per
|
||||
/// agent (including agents provisioned before the change); delete to
|
||||
/// re-apply.
|
||||
#[must_use]
|
||||
pub fn forge_repo_creation_disabled_marker(name: &str) -> PathBuf {
|
||||
forge_dir().join(format!("repo-creation-disabled-{name}"))
|
||||
}
|
||||
|
||||
/// `matrix/` — host-side matrix provisioning state (admin token, hive
|
||||
/// Space room id, per-agent password creds). The shared registration
|
||||
/// token is bind-mounted into the tuwunel container via nix and stays
|
||||
|
|
|
|||
|
|
@ -572,6 +572,13 @@ pub enum Request {
|
|||
/// per-kind semantics in
|
||||
/// `docs/conventions.md::Loose-ends wire shape`.
|
||||
CancelLooseEnd { kind: CancelLooseEndKind, id: i64 },
|
||||
/// Create a git repo *through hive-c0re*. Agents can't create
|
||||
/// repos with their own forge token (`max_repo_creation = 0`); this is
|
||||
/// the sanctioned path. hive-c0re creates `repo` in the c0re-owned
|
||||
/// `agents` org, adds the calling agent as a write collaborator (not
|
||||
/// owner), and applies operator-team branch protection so the author
|
||||
/// can't merge its own PRs. Returns the new repo's full name.
|
||||
CreateRepo { repo: String },
|
||||
/// Mark every message popped since the last `AckTurn` as handled.
|
||||
/// Harness↔broker pairing fired after `TurnOutcome::Ok`. See
|
||||
/// `docs/conventions.md::Broker delivery + ack cycle`.
|
||||
|
|
@ -753,6 +760,12 @@ pub enum Response {
|
|||
/// `ListSchedules` result. Snapshot of every schedule.
|
||||
/// Returned on the manager socket only.
|
||||
Schedules { schedules: Vec<WireSchedule> },
|
||||
/// `CreateRepo` result: the new repo's full name (`agents/<repo>`)
|
||||
/// and clone URL, so the agent can immediately `git clone` it.
|
||||
RepoCreated {
|
||||
full_name: String,
|
||||
clone_url: String,
|
||||
},
|
||||
/// `ListDescendants` result: all descendant containers, with running
|
||||
/// status. Ordered by topology depth (parents before children), then
|
||||
/// alphabetically within each depth tier.
|
||||
|
|
@ -952,6 +965,10 @@ pub enum ToolGroup {
|
|||
Scheduling,
|
||||
/// `get_logs` - *(privileged)*
|
||||
Diagnostics,
|
||||
/// `create_repo` — create git repos through hive-c0re (the only path
|
||||
/// now that agents can't create them directly). Opt-in per
|
||||
/// agent so the operator controls who can spin up repos.
|
||||
Forge,
|
||||
/// `run`, `status` (via `mcp__bash__*`)
|
||||
Execution,
|
||||
/// Claude built-in web egress tools: `WebFetch` (retrieve a URL) and
|
||||
|
|
@ -991,6 +1008,7 @@ impl ToolGroup {
|
|||
"list_schedules",
|
||||
],
|
||||
Self::Diagnostics => &["get_logs"],
|
||||
Self::Forge => &["create_repo"],
|
||||
Self::Execution => &["run", "status"],
|
||||
Self::WebTools => &[],
|
||||
}
|
||||
|
|
@ -1044,6 +1062,7 @@ impl ToolGroup {
|
|||
Self::Approvals,
|
||||
Self::Scheduling,
|
||||
Self::Diagnostics,
|
||||
Self::Forge,
|
||||
Self::Execution,
|
||||
Self::WebTools,
|
||||
];
|
||||
|
|
@ -1060,6 +1079,7 @@ impl ToolGroup {
|
|||
Self::Approvals => "approvals",
|
||||
Self::Scheduling => "scheduling",
|
||||
Self::Diagnostics => "diagnostics",
|
||||
Self::Forge => "forge",
|
||||
Self::Execution => "execution",
|
||||
Self::WebTools => "web_tools",
|
||||
}
|
||||
|
|
@ -1088,6 +1108,9 @@ impl ToolGroup {
|
|||
Self::Diagnostics => {
|
||||
"get_logs — read a sub-agent container's systemd journal (privileged)"
|
||||
}
|
||||
Self::Forge => {
|
||||
"create_repo — create git repos through hive-c0re (operator-gated merge)"
|
||||
}
|
||||
Self::Execution => {
|
||||
"run, status — run shell commands via mcp__bash__run / mcp__bash__status"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue