agent surface: create_repo through hive-c0re (#1787)
Closes the #1787 loop — the sanctioned create path now that agents can't create repos directly. Adds: - wire: Request::CreateRepo{repo} + Response::RepoCreated{full_name, clone_url} (hive-sh4re). - agent_server: dispatch_shared arm + handle_create_repo — validates the repo name, then forge::create_agent_repo (org-owned repo, agent=write collaborator, operator-team branch protection). Returns the full name + clone url so the agent can git clone immediately. - MCP: create_repo tool + CreateRepoArgs in the harness. - a new opt-in ToolGroup::Forge (=[create_repo]) so the operator controls which agents can spin up repos (least privilege). Workspace clippy -D warnings, cargo test, nix fmt all green.
This commit is contained in:
parent
867be7bb98
commit
f1d54ce12c
4 changed files with 115 additions and 3 deletions
|
|
@ -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 (#1787).
|
||||
/// 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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue