huskies: merge 897
This commit is contained in:
@@ -22,6 +22,9 @@ const GATEWAY_TOOLS: &[&str] = &[
|
||||
"init_project",
|
||||
"aggregate_pipeline_status",
|
||||
"agents.list",
|
||||
// Handled at the gateway so the Matrix bot's perm_rx listener is used
|
||||
// rather than the container's (which has no interactive session attached).
|
||||
"prompt_permission",
|
||||
];
|
||||
|
||||
/// Gateway tool definitions.
|
||||
@@ -350,6 +353,7 @@ async fn handle_gateway_tool(
|
||||
"init_project" => handle_init_project_tool(params, state, id).await,
|
||||
"aggregate_pipeline_status" => handle_aggregate_pipeline_status_tool(state, id).await,
|
||||
"agents.list" => handle_agents_list_tool(id),
|
||||
"prompt_permission" => handle_prompt_permission_tool(params, state, id).await,
|
||||
_ => JsonRpcResponse::error(id, -32601, format!("Unknown gateway tool: {tool_name}")),
|
||||
}
|
||||
}
|
||||
@@ -531,6 +535,102 @@ async fn handle_aggregate_pipeline_status_tool(
|
||||
)
|
||||
}
|
||||
|
||||
/// Handle the `prompt_permission` tool at the gateway level.
|
||||
///
|
||||
/// Mirrors `tool_prompt_permission` in `http/mcp/diagnostics/permission.rs` but
|
||||
/// uses the gateway's `perm_tx`/`perm_rx` so requests reach the Matrix bot that
|
||||
/// is listening on the gateway, not the proxied container (which has no
|
||||
/// interactive session and would auto-deny immediately).
|
||||
async fn handle_prompt_permission_tool(
|
||||
params: &Value,
|
||||
state: &GatewayState,
|
||||
id: Option<Value>,
|
||||
) -> JsonRpcResponse {
|
||||
use crate::http::context::PermissionDecision;
|
||||
use crate::http::context::PermissionForward;
|
||||
|
||||
let args = params.get("arguments").unwrap_or(params);
|
||||
let tool_name = args
|
||||
.get("tool_name")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
let tool_input = args.get("input").cloned().unwrap_or(json!({}));
|
||||
|
||||
// Auto-approve huskies MCP tools — mirrors the standard server's allowlist.
|
||||
if tool_name.starts_with("mcp__huskies__") {
|
||||
crate::slog!(
|
||||
"[gateway/permission] Auto-approved '{tool_name}' (matches mcp__huskies__* allowlist)"
|
||||
);
|
||||
let text = json!({"behavior": "allow", "updatedInput": tool_input}).to_string();
|
||||
return JsonRpcResponse::success(id, json!({"content": [{"type": "text", "text": text}]}));
|
||||
}
|
||||
|
||||
// Auto-deny when no interactive session holds perm_rx (i.e. no Matrix bot
|
||||
// listener is running — try_lock succeeds when nobody else holds the lock).
|
||||
if state.perm_rx.try_lock().is_ok() {
|
||||
crate::slog!("[gateway/permission] Auto-denied '{tool_name}' (no interactive session)");
|
||||
let text = json!({
|
||||
"behavior": "deny",
|
||||
"message": format!("Permission denied for '{tool_name}'. No interactive session active.")
|
||||
})
|
||||
.to_string();
|
||||
return JsonRpcResponse::success(id, json!({"content": [{"type": "text", "text": text}]}));
|
||||
}
|
||||
|
||||
let request_id = uuid::Uuid::new_v4().to_string();
|
||||
let (response_tx, response_rx) = tokio::sync::oneshot::channel();
|
||||
|
||||
if state
|
||||
.perm_tx
|
||||
.send(PermissionForward {
|
||||
request_id,
|
||||
tool_name: tool_name.clone(),
|
||||
tool_input: tool_input.clone(),
|
||||
response_tx,
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
crate::slog!("[gateway/permission] Auto-denied '{tool_name}' (perm_tx send failed)");
|
||||
let text =
|
||||
json!({"behavior": "deny", "message": format!("Permission denied for '{tool_name}'.")})
|
||||
.to_string();
|
||||
return JsonRpcResponse::success(id, json!({"content": [{"type": "text", "text": text}]}));
|
||||
}
|
||||
|
||||
let decision =
|
||||
match tokio::time::timeout(std::time::Duration::from_secs(300), response_rx).await {
|
||||
Ok(Ok(d)) => d,
|
||||
Ok(Err(_)) => {
|
||||
return JsonRpcResponse::error(
|
||||
id,
|
||||
-32603,
|
||||
"Permission response channel closed unexpectedly".into(),
|
||||
);
|
||||
}
|
||||
Err(_) => {
|
||||
return JsonRpcResponse::error(
|
||||
id,
|
||||
-32603,
|
||||
format!("Permission request for '{tool_name}' timed out after 5 minutes"),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let text = if matches!(
|
||||
decision,
|
||||
PermissionDecision::Approve | PermissionDecision::AlwaysAllow
|
||||
) {
|
||||
json!({"behavior": "allow", "updatedInput": tool_input}).to_string()
|
||||
} else {
|
||||
crate::slog_warn!("[gateway/permission] User denied permission for '{tool_name}'");
|
||||
json!({"behavior": "deny", "message": format!("User denied permission for '{tool_name}'")})
|
||||
.to_string()
|
||||
};
|
||||
|
||||
JsonRpcResponse::success(id, json!({"content": [{"type": "text", "text": text}]}))
|
||||
}
|
||||
|
||||
/// Handle the `agents.list` gateway tool — returns all alive build agents from the CRDT.
|
||||
fn handle_agents_list_tool(id: Option<Value>) -> JsonRpcResponse {
|
||||
let agents = gateway::list_agents();
|
||||
|
||||
Reference in New Issue
Block a user