diff --git a/crates/altius-cli/src/cli.rs b/crates/altius-cli/src/cli.rs index 2a7b72e..c747237 100644 --- a/crates/altius-cli/src/cli.rs +++ b/crates/altius-cli/src/cli.rs @@ -171,11 +171,7 @@ pub struct GitHubMcpArgs { /// Name of the environment variable containing the GitHub bearer token. /// The token value is never accepted as a CLI argument or sent to the LLM. - #[arg( - long, - env = "ALTIUS_GITHUB_TOKEN_ENV", - default_value = "GITHUB_TOKEN" - )] + #[arg(long, env = "ALTIUS_GITHUB_TOKEN_ENV", default_value = "GITHUB_TOKEN")] pub github_token_env: String, /// GitHub MCP capability exposed to the specialist. diff --git a/crates/altius-cli/src/github_connector.rs b/crates/altius-cli/src/github_connector.rs index 52f9b53..3689b45 100644 --- a/crates/altius-cli/src/github_connector.rs +++ b/crates/altius-cli/src/github_connector.rs @@ -53,4 +53,3 @@ pub async fn attach( dispatcher: Arc::new(tools), })) } - diff --git a/crates/altius-cli/src/serve_command.rs b/crates/altius-cli/src/serve_command.rs index f3ba218..f6987f9 100644 --- a/crates/altius-cli/src/serve_command.rs +++ b/crates/altius-cli/src/serve_command.rs @@ -22,8 +22,8 @@ use altius_protocol::anp::{ AgentDescription, AgentRegistry, AnpState, InMemoryRegistry, InterfaceDescription, }; use altius_protocol::beeacp::{ - require_bearer, BearerAuth, BeeAcpState, Message, MessagePart, Run, RunApproval, RunExecutor, - RunOutcome, SqliteRunStore, openapi_router, + openapi_router, require_bearer, BearerAuth, BeeAcpState, Message, MessagePart, Run, + RunApproval, RunExecutor, RunOutcome, SqliteRunStore, }; use altius_protocol::Result as ProtocolResult; use async_trait::async_trait; @@ -294,9 +294,11 @@ impl FleetRunExecutor { .await { Ok(ExecutionOutcome::Finished { state, .. }) => Ok(Some(completed_outcome(state))), - Ok(ExecutionOutcome::Interrupted { reason, node, .. }) => Ok(Some(RunOutcome::Awaiting { - approval: RunApproval::generic(reason, Some(node)), - })), + Ok(ExecutionOutcome::Interrupted { reason, node, .. }) => { + Ok(Some(RunOutcome::Awaiting { + approval: RunApproval::generic(reason, Some(node)), + })) + } Err(error) => Ok(Some(RunOutcome::Failed(error.to_string()))), } } @@ -384,6 +386,7 @@ fn options_for_run(template: &SupervisorOptions, agent_name: &str) -> Supervisor SupervisorOptions { agent_name: Some(agent_name.to_owned()), browser: template.browser.clone(), + github: template.github.clone(), hooks: template.hooks.clone(), } } @@ -554,10 +557,20 @@ async fn build_supervisor_options( } } let browser_enabled = browser_tooling.is_some(); + + let github_tooling = match crate::github_connector::attach(&args.github, &attachments).await { + Ok(tooling) => tooling, + Err(error) => { + eprintln!("altius: warning: GitHub MCP attach failed: {error}"); + None + } + }; + Ok(( SupervisorOptions { agent_name: None, browser: browser_tooling, + github: github_tooling, hooks: Vec::new(), }, browser_enabled, @@ -612,6 +625,7 @@ fn serve_protocols(args: &FleetServeArgs, include_beeacp: bool) -> Result<(), Cl token: args.token.clone(), run_db: args.run_db.clone(), plugin: args.plugin.clone(), + github: args.github.clone(), }; rt.block_on(async move { diff --git a/crates/altius-mcp/src/agent_lsp.rs b/crates/altius-mcp/src/agent_lsp.rs index 5eea67f..9814a38 100644 --- a/crates/altius-mcp/src/agent_lsp.rs +++ b/crates/altius-mcp/src/agent_lsp.rs @@ -87,6 +87,9 @@ fn map_error(error: McpClientError) -> AgentLspError { McpClientError::Request(_, message) => AgentLspError::Request(message), McpClientError::NotFound(name) => AgentLspError::Request(format!("not found: {name}")), McpClientError::Shutdown(_, message) => AgentLspError::Shutdown(message), + McpClientError::MissingCredential(env_var) => AgentLspError::InvalidConfig(format!( + "missing credential environment variable `{env_var}`" + )), } } diff --git a/crates/altius-mcp/src/mcp_client.rs b/crates/altius-mcp/src/mcp_client.rs index d35475c..c4cc0ed 100644 --- a/crates/altius-mcp/src/mcp_client.rs +++ b/crates/altius-mcp/src/mcp_client.rs @@ -225,9 +225,7 @@ pub async fn attach_mcp(config: McpAttachConfig) -> Result Result { +pub async fn attach_remote_mcp(config: McpRemoteConfig) -> Result { validate_remote_config(&config)?; spawn_remote(config).await } @@ -398,11 +396,7 @@ fn validate_config(config: &McpAttachConfig) -> Result<(), McpClientError> { "too many env_extras entries".into(), )); } - if config - .env_extras - .iter() - .any(|key| !valid_env_key(key)) - { + if config.env_extras.iter().any(|key| !valid_env_key(key)) { return Err(McpClientError::InvalidConfig( "env_extras keys must be ASCII environment names and bounded".into(), )); diff --git a/crates/altius-protocol/src/beeacp/mod.rs b/crates/altius-protocol/src/beeacp/mod.rs index f84f92f..8a8ff24 100644 --- a/crates/altius-protocol/src/beeacp/mod.rs +++ b/crates/altius-protocol/src/beeacp/mod.rs @@ -13,8 +13,8 @@ mod auth; mod model; -mod routes; mod openapi; +mod routes; mod sqlite_store; mod store; diff --git a/crates/altius-protocol/src/beeacp/routes.rs b/crates/altius-protocol/src/beeacp/routes.rs index f262444..115c598 100644 --- a/crates/altius-protocol/src/beeacp/routes.rs +++ b/crates/altius-protocol/src/beeacp/routes.rs @@ -28,9 +28,7 @@ pub enum RunOutcome { /// The run finished with the given output messages. Completed(Vec), /// The run is paused waiting for external input (`POST /runs/{id}`). - Awaiting { - approval: RunApproval, - }, + Awaiting { approval: RunApproval }, /// The run failed with a human-readable reason. Failed(String), } @@ -156,13 +154,7 @@ async fn apply_outcome(state: &BeeAcpState, run_id: RunId, outcome: RunOutcome) RunOutcome::Awaiting { approval } => { state .store - .transition( - run_id, - RunStatus::Awaiting, - None, - None, - Some(approval), - ) + .transition(run_id, RunStatus::Awaiting, None, None, Some(approval)) .await } RunOutcome::Failed(reason) => { @@ -257,7 +249,10 @@ pub(crate) async fn list_runs(State(state): State) -> Result, Path(id): Path) -> Result> { +pub(crate) async fn get_run( + State(state): State, + Path(id): Path, +) -> Result> { let run = state.store.get(parse_run_id(&id)?).await?; Ok(Json(run)) } @@ -274,7 +269,10 @@ pub(crate) async fn get_run(State(state): State, Path(id): Path, Path(id): Path) -> Result> { +pub(crate) async fn cancel_run( + State(state): State, + Path(id): Path, +) -> Result> { let run = state .store .transition(parse_run_id(&id)?, RunStatus::Cancelled, None, None, None) @@ -663,7 +661,9 @@ mod tests { // so keep-alive pings keep the stream open indefinitely. let (_, run) = send( &app, - Request::get(format!("/runs/{id}")).body(Body::empty()).unwrap(), + Request::get(format!("/runs/{id}")) + .body(Body::empty()) + .unwrap(), ) .await; assert_eq!(run["status"], "awaiting"); diff --git a/crates/altius-protocol/src/beeacp/sqlite_store.rs b/crates/altius-protocol/src/beeacp/sqlite_store.rs index 994e49d..ea09842 100644 --- a/crates/altius-protocol/src/beeacp/sqlite_store.rs +++ b/crates/altius-protocol/src/beeacp/sqlite_store.rs @@ -162,12 +162,13 @@ fn decode_run( finished_at, ): StoredRunRow, ) -> Result { - let approval = match approval_json { - Some(raw) => Some(serde_json::from_str(&raw).map_err(|e| { - ProtocolError::Internal(format!("corrupt approval json in db: {e}")) - })?), - None => None, - }; + let approval = + match approval_json { + Some(raw) => Some(serde_json::from_str(&raw).map_err(|e| { + ProtocolError::Internal(format!("corrupt approval json in db: {e}")) + })?), + None => None, + }; Ok(Run { run_id: run_id .parse()