remove the create_repo agent tool
mara ruled on #4849 (c88934): "remove create_repo tool". The tool ran in
hive-c0re with the hive's core token, so it only ever worked for agents
on the hive that runs the forge.
Removed:
- the create_repo MCP tool and CreateRepoArgs (hive-agent-mcp)
- wire variants Request::CreateRepo and Response::RepoCreated
(hive-core-agent-sock)
- hive-c0re's handle_create_repo, its valid_repo_name check and the
dispatch arm
- forge::create_agent_repo and apply_operator_branch_protection, which
had no other caller, plus AGENTS_ORG and OPERATORS_TEAM, whose only
users they were
- the tool's docs (docs/tools/forge.md repo management, docs/turn-loop/
mcp.md, the conventions tool-group table) and the doc comments that
named it (hive-sock-client's response timeout, ensure_repo_creation_
disabled, the security doc's merge-gate bullet)
ToolGroup::Forge is kept with no tools, the same way b88a5b24 kept
Lifecycle, so existing meta/capabilities.json grants still parse.
Forge state is untouched: existing agents/* repos keep their collaborators
and operators-team branch protection. The swarm-controller's own
create_repo (config-org repos) is a different path and is unchanged.
Closes #4849
This commit is contained in:
parent
bab15e2ba6
commit
7d217f8267
14 changed files with 43 additions and 230 deletions
|
|
@ -302,7 +302,7 @@ binary flavor.
|
||||||
| `lifecycle` | none — `list_containers` no longer exists, with no replacement; the variant survives only so existing grants parse. |
|
| `lifecycle` | none — `list_containers` no longer exists, with no replacement; the variant survives only so existing grants parse. |
|
||||||
| `approvals` | none — `request_update_meta_inputs` no longer exists, with no replacement. Still a live server-side gate: `cancel_loose_end`'s approval-cancel arm requires it. |
|
| `approvals` | none — `request_update_meta_inputs` no longer exists, with no replacement. Still a live server-side gate: `cancel_loose_end`'s approval-cancel arm requires it. |
|
||||||
| `scheduling` | `request_schedule_prompt`, `fire_schedule_now`, `cancel_schedule`, `edit_schedule`, `list_schedules` *(privileged)* |
|
| `scheduling` | `request_schedule_prompt`, `fire_schedule_now`, `cancel_schedule`, `edit_schedule`, `list_schedules` *(privileged)* |
|
||||||
| `forge` | `create_repo` — create git repos through hive-c0re (operator-gated merge) |
|
| `forge` | none — `create_repo` no longer exists, with no replacement; the variant survives only so existing grants parse. |
|
||||||
| `web_tools` | none (gates the Claude built-ins `WebFetch`/`WebSearch`, not an MCP tool) |
|
| `web_tools` | none (gates the Claude built-ins `WebFetch`/`WebSearch`, not an MCP tool) |
|
||||||
|
|
||||||
**Always-on tools** — `ToolGroup::ALWAYS_ON_TOOLS` exposes `set_status`,
|
**Always-on tools** — `ToolGroup::ALWAYS_ON_TOOLS` exposes `set_status`,
|
||||||
|
|
|
||||||
|
|
@ -212,18 +212,11 @@ plain comment show under `last comment`, not `reviews`.
|
||||||
|
|
||||||
Agents **can't create repos directly via forge token** — Forgejo
|
Agents **can't create repos directly via forge token** — Forgejo
|
||||||
disables push-to-create and the agent token doesn't have the Create
|
disables push-to-create and the agent token doesn't have the Create
|
||||||
scope. Two paths exist depending on where the repo should live:
|
scope.
|
||||||
|
|
||||||
**Agent repos (`agents/<name>`)** — Use the `mcp__hyperhive__create_repo`
|
The CLI verbs below (`repo-create` / `repo-add-collaborator`) use the
|
||||||
MCP tool (requires the `forge` tool group). hive-c0re creates the repo in
|
agent's own forge token, so a repo lands under the agent's user account
|
||||||
the c0re-owned `agents/` org, adds you as a write collaborator (not
|
or an org the agent belongs to.
|
||||||
owner), enables branch protection (operator-team merge approval
|
|
||||||
required — you can't self-merge), and returns the clone URL immediately.
|
|
||||||
This is the standard path for agents that need a working repo.
|
|
||||||
|
|
||||||
**Other repos** — Use the CLI verbs below (`repo-create` / `repo-add-collaborator`).
|
|
||||||
These use the agent's own forge token so the repo lands under the agent's
|
|
||||||
user account or an org the agent belongs to.
|
|
||||||
|
|
||||||
**`repo-create <name>`** — create a repo under the authenticated user
|
**`repo-create <name>`** — create a repo under the authenticated user
|
||||||
and print its URL. Key flags:
|
and print its URL. Key flags:
|
||||||
|
|
|
||||||
|
|
@ -125,10 +125,10 @@ checkpoints**, not about sandboxing the agent from its own tools:
|
||||||
**human (the operator) merges the PR**, keeping a person in the loop on the
|
**human (the operator) merges the PR**, keeping a person in the loop on the
|
||||||
highest-value action. On the **internal forge this is technically enforced,
|
highest-value action. On the **internal forge this is technically enforced,
|
||||||
not just convention**: agents can't create repos (`max_repo_creation = 0`),
|
not just convention**: agents can't create repos (`max_repo_creation = 0`),
|
||||||
so every repo is `core`-created with branch protection **on by default** —
|
and `main` on an `agent-configs/<name>` repo carries swarm-controller's
|
||||||
merges restricted to the operators team + a required operators-team approval
|
branch protection: merge allowlisted to the `operators` team, with one
|
||||||
(`apply_operator_branch_protection` / the config-repo equivalent) — and an
|
approval from it. Existing `agents/<repo>` repos carry the same merge gate.
|
||||||
agent (a write collaborator, not a repo admin) can neither change those
|
An agent (a write collaborator, not a repo admin) can neither change those
|
||||||
settings nor merge its own PR. It's **not** set up for external VCS (GitHub
|
settings nor merge its own PR. It's **not** set up for external VCS (GitHub
|
||||||
etc.), though — there, operator-merge is process + accepted risk, not a
|
etc.), though — there, operator-merge is process + accepted risk, not a
|
||||||
technical control.
|
technical control.
|
||||||
|
|
|
||||||
|
|
@ -137,13 +137,8 @@ hive_name?, swarm_name?, matrix_accounts? }`. `matrix_accounts` is a
|
||||||
[`docs/agent-lifecycle/approvals.md`](../agent-lifecycle/approvals.md).
|
[`docs/agent-lifecycle/approvals.md`](../agent-lifecycle/approvals.md).
|
||||||
- **Scheduling** (`scheduling`) — scheduled prompts. See
|
- **Scheduling** (`scheduling`) — scheduled prompts. See
|
||||||
[`docs/tools/scheduling.md`](../tools/scheduling.md).
|
[`docs/tools/scheduling.md`](../tools/scheduling.md).
|
||||||
- **Forge repos** (`forge`) — `create_repo` — the only agent path to
|
- **Forge repos** (`forge`) — carries no MCP tool: `create_repo` no
|
||||||
create a repo under the `agents/` org (direct forge token creation is
|
longer exists, with no replacement; `forge` gates nothing.
|
||||||
disabled for agents). The repo lands in the c0re-owned `agents`
|
|
||||||
org; the calling agent gets write collaborator access; the default
|
|
||||||
branch is branch-protected (operator-team must approve merges, so the
|
|
||||||
agent can't self-merge). Opt-in; not in any default preset.
|
|
||||||
See [`docs/tools/forge.md — Repo management`](../tools/forge.md).
|
|
||||||
- **Web egress** (`web_tools`) — enables Claude's built-in `WebFetch`
|
- **Web egress** (`web_tools`) — enables Claude's built-in `WebFetch`
|
||||||
and `WebSearch` tools (not MCP tools; added directly to the
|
and `WebSearch` tools (not MCP tools; added directly to the
|
||||||
`--allowedTools` list). Off by default; add the group in the
|
`--allowedTools` list). Off by default; add the group in the
|
||||||
|
|
|
||||||
|
|
@ -103,13 +103,6 @@ pub struct SetStatusArgs {
|
||||||
pub text: String,
|
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)]
|
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
||||||
pub struct GetAgentMetaArgs {
|
pub struct GetAgentMetaArgs {
|
||||||
/// Logical name of the agent to query (e.g. `"iris"`, `"manager"`).
|
/// Logical name of the agent to query (e.g. `"iris"`, `"manager"`).
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,9 @@ mod args;
|
||||||
mod render;
|
mod render;
|
||||||
|
|
||||||
pub use args::{
|
pub use args::{
|
||||||
AckUntilArgs, CancelLooseEndArgs, CancelScheduleArgs, CompactArgs, CreateRepoArgs,
|
AckUntilArgs, CancelLooseEndArgs, CancelScheduleArgs, CompactArgs, EditScheduleArgs,
|
||||||
EditScheduleArgs, FireScheduleNowArgs, GetAgentMetaArgs, MarkTodosDoneArgs, RecvArgs,
|
FireScheduleNowArgs, GetAgentMetaArgs, MarkTodosDoneArgs, RecvArgs, RemindArgs,
|
||||||
RemindArgs, RequestSchedulePromptArgs, SendArgs, SetStatusArgs,
|
RequestSchedulePromptArgs, SendArgs, SetStatusArgs,
|
||||||
};
|
};
|
||||||
pub use render::{annotate_retries, format_ack, format_agent_meta, format_recv};
|
pub use render::{annotate_retries, format_ack, format_agent_meta, format_recv};
|
||||||
|
|
||||||
|
|
@ -441,33 +441,6 @@ impl AgentServer {
|
||||||
.await
|
.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 \
|
|
||||||
`$HIVE_FORGE_URL/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_core_agent_sock::Request::CreateRepo { repo: args.repo })
|
|
||||||
.await;
|
|
||||||
let s = match resp {
|
|
||||||
Ok(hive_core_agent_sock::Response::RepoCreated {
|
|
||||||
full_name,
|
|
||||||
clone_url,
|
|
||||||
}) => format!("created repo {full_name} — clone: {clone_url}"),
|
|
||||||
other => reply_err(other, "create_repo"),
|
|
||||||
};
|
|
||||||
annotate_retries(s, retries)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tool(
|
#[tool(
|
||||||
description = "Schedule a reminder that lands in this agent's own inbox at a future \
|
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 \
|
time (sender will appear as `reminder`). Use for self-paced follow-ups: 'check task \
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,9 @@ pub use pr_merge::{
|
||||||
};
|
};
|
||||||
pub use reconcile::{reconcile_config_apply, reconcile_config_status};
|
pub use reconcile::{reconcile_config_apply, reconcile_config_status};
|
||||||
pub use repos::{
|
pub use repos::{
|
||||||
clone_config_into_proposed, create_agent_repo, ensure_config_repo, ensure_meta_remote,
|
clone_config_into_proposed, ensure_config_repo, ensure_meta_remote, ensure_repo,
|
||||||
ensure_repo, fast_forward_applied_main, fetch_config_main_into_applied, meta_read_access,
|
fast_forward_applied_main, fetch_config_main_into_applied, meta_read_access, push_config,
|
||||||
push_config, push_meta, shared_docs_access,
|
push_meta, shared_docs_access,
|
||||||
};
|
};
|
||||||
pub use users::core_token;
|
pub use users::core_token;
|
||||||
|
|
||||||
|
|
@ -112,21 +112,6 @@ const SHARED_ORG: &str = "internal";
|
||||||
/// The shared docs repo inside `SHARED_ORG`. Cloneable by every agent
|
/// The shared docs repo inside `SHARED_ORG`. Cloneable by every agent
|
||||||
/// at `{forge_http_base()}/internal/docs.git`.
|
/// at `{forge_http_base()}/internal/docs.git`.
|
||||||
const SHARED_DOCS_REPO: &str = "docs";
|
const SHARED_DOCS_REPO: &str = "docs";
|
||||||
/// 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. The swarm-controller
|
|
||||||
/// ensures the org itself.
|
|
||||||
const AGENTS_ORG: &str = "agents";
|
|
||||||
/// Operator merge-gate team inside [`AGENTS_ORG`]. Provisioned **empty** by
|
|
||||||
/// the swarm-controller (so perms can be set before anyone joins); the
|
|
||||||
/// operator adds herself via the forge UI. 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";
|
|
||||||
|
|
||||||
/// Leak `s` to get a `&'static str` warning `kind` for the small, bounded
|
/// Leak `s` to get a `&'static str` warning `kind` for the small, bounded
|
||||||
/// set of boot warnings in [`ensure_all`] keyed by a runtime name (at
|
/// set of boot warnings in [`ensure_all`] keyed by a runtime name (at
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ use reqwest::StatusCode;
|
||||||
use crate::coordinator::Coordinator;
|
use crate::coordinator::Coordinator;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
AGENTS_ORG, CONFIG_ORG, OPERATORS_TEAM, SHARED_DOCS_REPO, SHARED_ORG, api, core_auth_header,
|
CONFIG_ORG, SHARED_DOCS_REPO, SHARED_ORG, api, core_auth_header, core_token, forge_git_url,
|
||||||
core_token, forge_git_url, forge_http_base, is_present,
|
forge_http_base, is_present,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Creation options for an empty repo defaulting to `main`.
|
/// Creation options for an empty repo defaulting to `main`.
|
||||||
|
|
@ -659,52 +659,6 @@ fn main_branch_protection_option() -> CreateBranchProtectionOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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, but **verify-don't-trust**: a create failure is ambiguous —
|
|
||||||
/// "rule already exists" (success) OR a silent rejection that created NO rule
|
|
||||||
/// (e.g. a 422 where `OPERATORS_TEAM` doesn't exist in `AGENTS_ORG`). The old
|
|
||||||
/// code folded 200/409/422 into `Ok` and left the repo unprotected with no
|
|
||||||
/// error — a fail-open merge gate. So on any create error, GET the `main` rule
|
|
||||||
/// and only treat it as success if the rule is actually present (the exact fix
|
|
||||||
/// already applied to [`apply_config_repo_branch_protection`]).
|
|
||||||
async fn apply_operator_branch_protection(repo: &str, token: &str) -> Result<()> {
|
|
||||||
let client = api(token)?;
|
|
||||||
let mut rule = main_branch_protection_option();
|
|
||||||
rule.enable_merge_whitelist = Some(true);
|
|
||||||
rule.merge_whitelist_teams = Some(vec![OPERATORS_TEAM.to_owned()]);
|
|
||||||
rule.enable_approvals_whitelist = Some(true);
|
|
||||||
rule.approvals_whitelist_teams = Some(vec![OPERATORS_TEAM.to_owned()]);
|
|
||||||
rule.required_approvals = Some(1);
|
|
||||||
rule.block_on_official_review_requests = Some(true);
|
|
||||||
let Err(create_err) = client
|
|
||||||
.repo_create_branch_protection(AGENTS_ORG, repo, rule)
|
|
||||||
.await
|
|
||||||
else {
|
|
||||||
tracing::info!(%repo, "forge: applied operator branch protection");
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
match client
|
|
||||||
.repo_get_branch_protection(AGENTS_ORG, repo, "main")
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(_) => {
|
|
||||||
tracing::debug!(
|
|
||||||
%repo, create_error = %create_err,
|
|
||||||
"forge: operator branch protection already present"
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Err(check_err) => anyhow::bail!(
|
|
||||||
"branch protection for {AGENTS_ORG}/{repo} not applied: create failed \
|
|
||||||
({create_err}); GET main rule failed ({check_err}), no `main` rule present"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Apply branch protection to an `agent-configs/<name>` repo's `main` so it
|
/// Apply branch protection to an `agent-configs/<name>` repo's `main` so it
|
||||||
/// can serve as the agent-editable, PR-merge config surface:
|
/// can serve as the agent-editable, PR-merge config surface:
|
||||||
/// - **`main` is never directly pushable** — no push is enabled on the
|
/// - **`main` is never directly pushable** — no push is enabled on the
|
||||||
|
|
@ -809,24 +763,3 @@ fn config_repo_protection_edit() -> EditBranchProtectionOption {
|
||||||
unprotected_file_patterns: None,
|
unprotected_file_patterns: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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,
|
|
||||||
AddCollaboratorOptionPermission::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}"))
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -208,10 +208,9 @@ pub(super) async fn ensure_user_email(name: &str) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Disable direct repo creation for agent `name` by setting
|
/// Disable direct repo creation for agent `name` by setting
|
||||||
/// `max_repo_creation = 0` on its Forgejo account. Agents must
|
/// `max_repo_creation = 0` on its Forgejo account. A write-scoped token
|
||||||
/// create repos *through hive-c0re* (which owns the perms), never with
|
/// can otherwise create + own repos and self-merge, bypassing the
|
||||||
/// their own token — a write-scoped token can otherwise create + own
|
/// operator-only-merge policy.
|
||||||
/// repos and self-merge, bypassing the operator-only-merge policy.
|
|
||||||
///
|
///
|
||||||
/// `max_repo_creation = 0` means `CanCreateRepo()` is false for any
|
/// `max_repo_creation = 0` means `CanCreateRepo()` is false for any
|
||||||
/// count (Forgejo: `MaxRepoCreation >= 0 && NumRepos >= MaxRepoCreation`),
|
/// count (Forgejo: `MaxRepoCreation >= 0 && NumRepos >= MaxRepoCreation`),
|
||||||
|
|
|
||||||
|
|
@ -316,7 +316,6 @@ pub(crate) async fn dispatch_shared(
|
||||||
|()| hive_core_agent_sock::Response::Ok,
|
|()| hive_core_agent_sock::Response::Ok,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
hive_core_agent_sock::Request::CreateRepo { repo } => handle_create_repo(agent, repo).await,
|
|
||||||
hive_core_agent_sock::Request::AckTurn => handle_ack_turn(coord, agent),
|
hive_core_agent_sock::Request::AckTurn => handle_ack_turn(coord, agent),
|
||||||
hive_core_agent_sock::Request::AckUntil { up_to } => handle_ack_until(coord, agent, *up_to),
|
hive_core_agent_sock::Request::AckUntil { up_to } => handle_ack_until(coord, agent, *up_to),
|
||||||
hive_core_agent_sock::Request::RequeueInflight => handle_requeue_inflight(coord, agent),
|
hive_core_agent_sock::Request::RequeueInflight => handle_requeue_inflight(coord, agent),
|
||||||
|
|
@ -423,46 +422,6 @@ fn handle_set_status(coord: &Arc<Coordinator>, text: &str) -> hive_core_agent_so
|
||||||
hive_core_agent_sock::Response::Ok
|
hive_core_agent_sock::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_core_agent_sock::Response {
|
|
||||||
if !valid_repo_name(repo) {
|
|
||||||
return hive_core_agent_sock::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_core_agent_sock::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_core_agent_sock::Response::RepoCreated {
|
|
||||||
clone_url: format!("{}/{full_name}.git", crate::forge::forge_http_base()),
|
|
||||||
full_name,
|
|
||||||
},
|
|
||||||
Err(e) => hive_core_agent_sock::Response::Err {
|
|
||||||
message: format!("create repo {repo:?} failed: {e:#}"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `GetAgentMeta` — identity + live status for `name` (defaults to the
|
/// `GetAgentMeta` — identity + live status for `name` (defaults to the
|
||||||
/// caller). Reads the live container-view status and the hive/swarm
|
/// caller). Reads the live container-view status and the hive/swarm
|
||||||
/// display names.
|
/// display names.
|
||||||
|
|
|
||||||
|
|
@ -83,13 +83,6 @@ pub enum Request {
|
||||||
/// per-kind semantics in
|
/// per-kind semantics in
|
||||||
/// `docs/process/conventions.md::Loose-ends wire shape`.
|
/// `docs/process/conventions.md::Loose-ends wire shape`.
|
||||||
CancelLooseEnd { kind: CancelLooseEndKind, id: i64 },
|
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.
|
/// Mark every message popped since the last `AckTurn` as handled.
|
||||||
/// Harness↔broker pairing fired after `TurnOutcome::Ok`. See
|
/// Harness↔broker pairing fired after `TurnOutcome::Ok`. See
|
||||||
/// `docs/process/conventions.md::Broker delivery + ack cycle`.
|
/// `docs/process/conventions.md::Broker delivery + ack cycle`.
|
||||||
|
|
@ -222,12 +215,6 @@ pub enum Response {
|
||||||
/// authorized to see — see `ListSchedules`'s own doc comment.
|
/// authorized to see — see `ListSchedules`'s own doc comment.
|
||||||
/// Returned on the manager socket only.
|
/// Returned on the manager socket only.
|
||||||
Schedules { schedules: Vec<WireSchedule> },
|
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,
|
|
||||||
},
|
|
||||||
/// `Recv` result when a graceful stop is pending for this agent
|
/// `Recv` result when a graceful stop is pending for this agent
|
||||||
/// (set by hive-c0re's `GracefulStop` orchestration). Returned in
|
/// (set by hive-c0re's `GracefulStop` orchestration). Returned in
|
||||||
/// place of `Messages` — it doubles as the inbound fence: the harness
|
/// place of `Messages` — it doubles as the inbound fence: the harness
|
||||||
|
|
|
||||||
|
|
@ -165,9 +165,9 @@ pub enum ToolGroup {
|
||||||
/// `request_schedule_prompt`, `fire_schedule_now`, `cancel_schedule`,
|
/// `request_schedule_prompt`, `fire_schedule_now`, `cancel_schedule`,
|
||||||
/// `edit_schedule`, `list_schedules` - *(privileged)*
|
/// `edit_schedule`, `list_schedules` - *(privileged)*
|
||||||
Scheduling,
|
Scheduling,
|
||||||
/// `create_repo` — create git repos through hive-c0re (the only path
|
/// Gates no tool today — `create_repo`, its only member, was removed
|
||||||
/// now that agents can't create them directly). Opt-in per
|
/// with no replacement. Kept so existing `meta/capabilities.json`
|
||||||
/// agent so the operator controls who can spin up repos.
|
/// grants still parse; `tools()` returns `&[]`.
|
||||||
Forge,
|
Forge,
|
||||||
/// Gates whether the `bash` MCP server (`mcp__bash__run`/`status`/
|
/// Gates whether the `bash` MCP server (`mcp__bash__run`/`status`/
|
||||||
/// `kill`) is rendered into the agent's config at all — see
|
/// `kill`) is rendered into the agent's config at all — see
|
||||||
|
|
@ -222,8 +222,8 @@ impl ToolGroup {
|
||||||
|
|
||||||
/// The MCP tool names (without the `mcp__hyperhive__` prefix) in this group.
|
/// The MCP tool names (without the `mcp__hyperhive__` prefix) in this group.
|
||||||
/// Returns `&[]` for `WebTools` (it enables Claude built-in tools, not MCP
|
/// Returns `&[]` for `WebTools` (it enables Claude built-in tools, not MCP
|
||||||
/// tools — see `builtin_tools()`) and for `Lifecycle` / `Approvals`
|
/// tools — see `builtin_tools()`) and for `Lifecycle` / `Approvals` /
|
||||||
/// (their tools were removed); see each variant's doc comment.
|
/// `Forge` (their tools were removed); see each variant's doc comment.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn tools(self) -> &'static [&'static str] {
|
pub fn tools(self) -> &'static [&'static str] {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -237,8 +237,7 @@ impl ToolGroup {
|
||||||
"edit_schedule",
|
"edit_schedule",
|
||||||
"list_schedules",
|
"list_schedules",
|
||||||
],
|
],
|
||||||
Self::Forge => &["create_repo"],
|
// All five empty, for different reasons — see each
|
||||||
// All four empty, for four different reasons — see each
|
|
||||||
// variant's own doc comment above. `Execution` grants the
|
// variant's own doc comment above. `Execution` grants the
|
||||||
// out-of-process `bash` MCP server
|
// out-of-process `bash` MCP server
|
||||||
// (`mcp__bash__run`/`status`/`kill`), gated at config-render
|
// (`mcp__bash__run`/`status`/`kill`), gated at config-render
|
||||||
|
|
@ -247,13 +246,15 @@ impl ToolGroup {
|
||||||
// out-of-process server has no later enforcement point, so
|
// out-of-process server has no later enforcement point, so
|
||||||
// that gate is the actual security boundary. `WebTools`
|
// that gate is the actual security boundary. `WebTools`
|
||||||
// grants Claude built-in tools, not MCP ones; see
|
// grants Claude built-in tools, not MCP ones; see
|
||||||
// `builtin_tools()`. `Lifecycle` and `Approvals` each listed
|
// `builtin_tools()`. `Lifecycle`, `Approvals` and `Forge` each
|
||||||
// exactly one tool — `list_containers` and
|
// listed exactly one tool — `list_containers`,
|
||||||
// `request_update_meta_inputs` respectively — and both tools
|
// `request_update_meta_inputs` and `create_repo` respectively
|
||||||
// were removed outright; the variants stay so existing
|
// — and all three tools were removed outright; the variants
|
||||||
// grants parse, and `Approvals` still gates
|
// stay so existing grants parse, and `Approvals` still gates
|
||||||
// `cancel_loose_end`'s approval-cancel arm server-side.
|
// `cancel_loose_end`'s approval-cancel arm server-side.
|
||||||
Self::Lifecycle | Self::Approvals | Self::Execution | Self::WebTools => &[],
|
Self::Lifecycle | Self::Approvals | Self::Forge | Self::Execution | Self::WebTools => {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -342,9 +343,7 @@ impl ToolGroup {
|
||||||
Self::Scheduling => {
|
Self::Scheduling => {
|
||||||
"request_schedule_prompt and related — operator-visible scheduled prompts (privileged)"
|
"request_schedule_prompt and related — operator-visible scheduled prompts (privileged)"
|
||||||
}
|
}
|
||||||
Self::Forge => {
|
Self::Forge => "no tools — vestigial since create_repo was removed",
|
||||||
"create_repo — create git repos through hive-c0re (operator-gated merge)"
|
|
||||||
}
|
|
||||||
Self::Execution => {
|
Self::Execution => {
|
||||||
"run, status — run shell commands via mcp__bash__run / mcp__bash__status"
|
"run, status — run shell commands via mcp__bash__run / mcp__bash__status"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,9 +51,7 @@ const CONNECT_TIMEOUT: Duration = Duration::from_secs(5);
|
||||||
/// that has not drained one JSON line in this long has stopped reading.
|
/// that has not drained one JSON line in this long has stopped reading.
|
||||||
const WRITE_TIMEOUT: Duration = Duration::from_secs(10);
|
const WRITE_TIMEOUT: Duration = Duration::from_secs(10);
|
||||||
|
|
||||||
/// Response deadline for every call that does not pass its own. Sized for
|
/// Response deadline for every call that does not pass its own.
|
||||||
/// the slowest non-polling verb: `CreateRepo`, which makes several
|
|
||||||
/// sequential forge API calls in hive-c0re before it answers.
|
|
||||||
const DEFAULT_RESPONSE_TIMEOUT: Duration = Duration::from_mins(1);
|
const DEFAULT_RESPONSE_TIMEOUT: Duration = Duration::from_mins(1);
|
||||||
|
|
||||||
/// What to do when a connect or I/O attempt fails.
|
/// What to do when a connect or I/O attempt fails.
|
||||||
|
|
|
||||||
|
|
@ -267,12 +267,11 @@ impl Client {
|
||||||
/// default branch — only [`OPERATORS_TEAM`] members can merge, and
|
/// default branch — only [`OPERATORS_TEAM`] members can merge, and
|
||||||
/// an approving review from that team is required, so the agent (a
|
/// an approving review from that team is required, so the agent (a
|
||||||
/// write-level collaborator, not in the team) cannot merge its own
|
/// write-level collaborator, not in the team) cannot merge its own
|
||||||
/// PR. Mirrors `hive-c0re::forge::repos::apply_operator_branch_protection`
|
/// PR. A create failure is ambiguous (already-exists vs. a silent
|
||||||
/// exactly (same policy, same verify-don't-trust shape): a create
|
/// reject that created no rule), so on any error this GETs the
|
||||||
/// failure is ambiguous (already-exists vs. a silent reject that
|
/// `main` rule and only treats it as success if the rule is
|
||||||
/// created no rule), so on any error this GETs the `main` rule and
|
/// actually present — a fail-open merge gate is a security bug,
|
||||||
/// only treats it as success if the rule is actually present — a
|
/// not a shrug.
|
||||||
/// fail-open merge gate is a security bug, not a shrug.
|
|
||||||
///
|
///
|
||||||
/// Also push-whitelists [`SWARM_CONTROLLER_FORGE_USER`] alone —
|
/// Also push-whitelists [`SWARM_CONTROLLER_FORGE_USER`] alone —
|
||||||
/// [`Client::seed_agent_config`]'s initial commit is a direct push to
|
/// [`Client::seed_agent_config`]'s initial commit is a direct push to
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue