From cc755100f06fd2527cd60110f5617cfc879df649 Mon Sep 17 00:00:00 2001 From: Yingjie He Date: Wed, 23 Apr 2025 08:54:04 -0700 Subject: [PATCH] feat: use the same permission flow for enable extensions (#2302) --- crates/goose-cli/src/commands/configure.rs | 6 +- crates/goose-cli/src/session/mod.rs | 24 ---- crates/goose/src/agents/agent.rs | 75 ++++++------ crates/goose/src/agents/tool_execution.rs | 35 ++---- crates/goose/src/message.rs | 42 ------- .../goose/src/permission/permission_judge.rs | 78 +++---------- .../goose/src/providers/formats/anthropic.rs | 3 - crates/goose/src/providers/formats/bedrock.rs | 3 - .../goose/src/providers/formats/databricks.rs | 3 - crates/goose/src/providers/formats/openai.rs | 3 - ui/desktop/src/components/ChatView.tsx | 39 +------ .../src/components/ExtensionConfirmation.tsx | 109 ------------------ ui/desktop/src/components/GooseMessage.tsx | 26 ----- .../permission/PermissionModal.tsx | 4 +- .../permission/PermissionSetting.tsx | 5 + ui/desktop/src/types/message.ts | 42 +------ 16 files changed, 77 insertions(+), 420 deletions(-) delete mode 100644 ui/desktop/src/components/ExtensionConfirmation.tsx diff --git a/crates/goose-cli/src/commands/configure.rs b/crates/goose-cli/src/commands/configure.rs index ff47ee1711..687d018cf3 100644 --- a/crates/goose-cli/src/commands/configure.rs +++ b/crates/goose-cli/src/commands/configure.rs @@ -3,8 +3,7 @@ use console::style; use goose::agents::extension::ToolInfo; use goose::agents::extension_manager::get_parameter_names; use goose::agents::platform_tools::{ - PLATFORM_ENABLE_EXTENSION_TOOL_NAME, PLATFORM_LIST_RESOURCES_TOOL_NAME, - PLATFORM_READ_RESOURCE_TOOL_NAME, + PLATFORM_LIST_RESOURCES_TOOL_NAME, PLATFORM_READ_RESOURCE_TOOL_NAME, }; use goose::agents::Agent; use goose::agents::{extension::Envs, ExtensionConfig}; @@ -1015,8 +1014,7 @@ pub async fn configure_tool_permissions_dialog() -> Result<(), Box> { .await .into_iter() .filter(|tool| { - tool.name != PLATFORM_ENABLE_EXTENSION_TOOL_NAME - && tool.name != PLATFORM_LIST_RESOURCES_TOOL_NAME + tool.name != PLATFORM_LIST_RESOURCES_TOOL_NAME && tool.name != PLATFORM_READ_RESOURCE_TOOL_NAME }) .map(|tool| { diff --git a/crates/goose-cli/src/session/mod.rs b/crates/goose-cli/src/session/mod.rs index 8c88598e38..5b03367425 100644 --- a/crates/goose-cli/src/session/mod.rs +++ b/crates/goose-cli/src/session/mod.rs @@ -17,7 +17,6 @@ use completion::GooseCompleter; use etcetera::choose_app_strategy; use etcetera::AppStrategy; use goose::agents::extension::{Envs, ExtensionConfig}; -use goose::agents::platform_tools::PLATFORM_ENABLE_EXTENSION_TOOL_NAME; use goose::agents::{Agent, SessionConfig}; use goose::config::Config; use goose::message::{Message, MessageContent}; @@ -623,29 +622,6 @@ impl Session { principal_type: PrincipalType::Tool, permission, },).await; - } else if let Some(MessageContent::ExtensionRequest(enable_extension_request)) = message.content.first() { - output::hide_thinking(); - - let extension_action = if enable_extension_request.tool_name == PLATFORM_ENABLE_EXTENSION_TOOL_NAME { - "enable" - } else { - "disable" - }; - - let prompt = format!("Goose would like to {} the following extension, do you approve?", extension_action); - let confirmed = cliclack::select(prompt) - .item(true, "Yes, for this session", format!("{} the extension for this session", extension_action)) - .item(false, "No", format!("Do not {} the extension", extension_action)) - .interact()?; - let permission = if confirmed { - Permission::AllowOnce - } else { - Permission::DenyOnce - }; - self.agent.handle_confirmation(enable_extension_request.id.clone(), PermissionConfirmation { - principal_type: PrincipalType::Extension, - permission, - },).await; } // otherwise we have a model/tool to render else { diff --git a/crates/goose/src/agents/agent.rs b/crates/goose/src/agents/agent.rs index f6a4d08852..7601313cf3 100644 --- a/crates/goose/src/agents/agent.rs +++ b/crates/goose/src/agents/agent.rs @@ -22,8 +22,8 @@ use tracing::{debug, error, instrument, warn}; use crate::agents::extension::{ExtensionConfig, ExtensionResult, ToolInfo}; use crate::agents::extension_manager::{get_parameter_names, ExtensionManager}; use crate::agents::platform_tools::{ - PLATFORM_LIST_RESOURCES_TOOL_NAME, PLATFORM_READ_RESOURCE_TOOL_NAME, - PLATFORM_SEARCH_AVAILABLE_EXTENSIONS_TOOL_NAME, + PLATFORM_ENABLE_EXTENSION_TOOL_NAME, PLATFORM_LIST_RESOURCES_TOOL_NAME, + PLATFORM_READ_RESOURCE_TOOL_NAME, PLATFORM_SEARCH_AVAILABLE_EXTENSIONS_TOOL_NAME, }; use crate::agents::prompt_manager::PromptManager; use crate::agents::types::SessionConfig; @@ -33,9 +33,7 @@ use mcp_core::{ }; use super::platform_tools; -use super::tool_execution::{ - ExtensionInstallResult, ToolFuture, CHAT_MODE_TOOL_SKIPPED_RESPONSE, DECLINED_RESPONSE, -}; +use super::tool_execution::{ToolFuture, CHAT_MODE_TOOL_SKIPPED_RESPONSE, DECLINED_RESPONSE}; const MAX_TRUNCATION_ATTEMPTS: usize = 3; const ESTIMATE_FACTOR_DECAY: f32 = 0.9; @@ -114,6 +112,16 @@ impl Agent { tool_call: mcp_core::tool::ToolCall, request_id: String, ) -> (String, Result, ToolError>) { + if tool_call.name == PLATFORM_ENABLE_EXTENSION_TOOL_NAME { + let extension_name = tool_call + .arguments + .get("extension_name") + .and_then(|v| v.as_str()) + .unwrap_or("") + .to_string(); + return self.enable_extension(extension_name, request_id).await; + } + let extension_manager = self.extension_manager.lock().await; let result = if tool_call.name == PLATFORM_READ_RESOURCE_TOOL_NAME { // Check if the tool is read_resource and handle it separately @@ -418,26 +426,26 @@ impl Agent { // What remains is handling the remaining tool requests (enable extension, // regular tool calls) in goose_mode == ["auto", "approve" or "smart_approve"] let mut permission_manager = PermissionManager::default(); - let permission_check_result = check_tool_permissions(&remaining_requests, - &mode, - tools_with_readonly_annotation.clone(), - tools_without_annotation.clone(), - &mut permission_manager, - self.provider()).await; - + let (permission_check_result, enable_extension_request_ids) = check_tool_permissions( + &remaining_requests, + &mode, + tools_with_readonly_annotation.clone(), + tools_without_annotation.clone(), + &mut permission_manager, + self.provider(), + ).await; // Handle pre-approved and read-only tools in parallel let mut tool_futures: Vec = Vec::new(); - let mut install_results: Vec = Vec::new(); - let install_results_arc = Arc::new(Mutex::new(install_results)); // Skip the confirmation for approved tools for request in &permission_check_result.approved { if let Ok(tool_call) = request.tool_call.clone() { let tool_future = self.dispatch_tool_call(tool_call, request.id.clone()); - tool_futures.push(Box::pin(tool_future)); + tool_futures.push(Box::pin(tool_future)); } } + for request in &permission_check_result.denied { let mut response = message_tool_response.lock().await; *response = response.clone().with_tool_response( @@ -446,51 +454,42 @@ impl Agent { ); } - // we need interior mutability in handle_approval_tool_requests + // We need interior mutability in handle_approval_tool_requests let tool_futures_arc = Arc::new(Mutex::new(tool_futures)); + // Process tools requiring approval (enable extension, regular tool calls) let mut tool_approval_stream = self.handle_approval_tool_requests( &permission_check_result.needs_approval, - install_results_arc.clone(), tool_futures_arc.clone(), &mut permission_manager, - message_tool_response.clone() + message_tool_response.clone(), ); - // we have a stream of tool_approval_requests to handle - // execution is yeield back to this reply loop, and is of the same Message - // type, so we can yield the Message back up to be handled and grab and + // We have a stream of tool_approval_requests to handle + // Execution is yielded back to this reply loop, and is of the same Message + // type, so we can yield the Message back up to be handled and grab any // confirmations or denials while let Some(msg) = tool_approval_stream.try_next().await? { yield msg; } tool_futures = { - // Lock the mutex asynchronously. + // Lock the mutex asynchronously let mut futures_lock = tool_futures_arc.lock().await; - // Drain the vector and collect into a new Vec. + // Drain the vector and collect into a new Vec futures_lock.drain(..).collect::>() }; - install_results = { - // Lock the mutex asynchronously. - let mut results_lock = install_results_arc.lock().await; - // Drain the vector and collect into a new Vec. - results_lock.drain(..).collect::>() - }; - - // Wait for all tool calls to complete let results = futures::future::join_all(tool_futures).await; + let mut all_install_successful = true; - // Check if any install results had errors before processing them - let all_install_successful = !install_results.iter().any(|(_, result)| result.is_err()); - for (request_id, output) in results.into_iter().chain(install_results.into_iter()) { + for (request_id, output) in results.into_iter() { + if enable_extension_request_ids.contains(&request_id) && output.is_err(){ + all_install_successful = false; + } let mut response = message_tool_response.lock().await; - *response = response.clone().with_tool_response( - request_id, - output - ); + *response = response.clone().with_tool_response(request_id, output); } // Update system prompt and tools if installations were successful diff --git a/crates/goose/src/agents/tool_execution.rs b/crates/goose/src/agents/tool_execution.rs index e76f54c778..ae718a36d8 100644 --- a/crates/goose/src/agents/tool_execution.rs +++ b/crates/goose/src/agents/tool_execution.rs @@ -10,8 +10,6 @@ use tokio::sync::Mutex; use crate::config::permission::PermissionLevel; use crate::config::PermissionManager; use crate::message::{Message, ToolRequest}; -use crate::permission::permission_confirmation::PrincipalType; -use crate::permission::permission_judge::get_confirmation_message; use crate::permission::Permission; use mcp_core::{Content, ToolError}; @@ -19,9 +17,6 @@ use mcp_core::{Content, ToolError}; pub(crate) type ToolFuture<'a> = Pin, ToolError>)> + Send + 'a>>; pub(crate) type ToolFuturesVec<'a> = Arc>>>; -// Type alias for extension installation results -pub(crate) type ExtensionInstallResult = (String, Result, ToolError>); -pub(crate) type ExtensionInstallResults = Arc>>; use crate::agents::Agent; @@ -42,7 +37,6 @@ impl Agent { pub(crate) fn handle_approval_tool_requests<'a>( &'a self, tool_requests: &'a [ToolRequest], - install_results: ExtensionInstallResults, tool_futures: ToolFuturesVec<'a>, permission_manager: &'a mut PermissionManager, message_tool_response: Arc>, @@ -50,31 +44,24 @@ impl Agent { try_stream! { for request in tool_requests { if let Ok(tool_call) = request.tool_call.clone() { - let (principal_type, confirmation) = get_confirmation_message(&request.id.clone(), tool_call.clone()); + let confirmation = Message::user().with_tool_confirmation_request( + request.id.clone(), + tool_call.name.clone(), + tool_call.arguments.clone(), + Some("Goose would like to call the above tool. Allow? (y/n):".to_string()), + ); yield confirmation; let mut rx = self.confirmation_rx.lock().await; while let Some((req_id, confirmation)) = rx.recv().await { if req_id == request.id { if confirmation.permission == Permission::AllowOnce || confirmation.permission == Permission::AlwaysAllow { - if principal_type == PrincipalType::Extension { - let extension_name = tool_call.arguments.get("extension_name") - .and_then(|v| v.as_str()) - .unwrap_or("") - .to_string(); + let tool_future = self.dispatch_tool_call(tool_call.clone(), request.id.clone()); + let mut futures = tool_futures.lock().await; + futures.push(Box::pin(tool_future)); - let mut results = install_results.lock().await; - let install_result = self.enable_extension(extension_name, request.id.clone()).await; - results.push(install_result); - } else { - // Add this tool call to the futures collection - let tool_future = self.dispatch_tool_call(tool_call.clone(), request.id.clone()); - let mut futures = tool_futures.lock().await; - futures.push(Box::pin(tool_future)); - - if confirmation.permission == Permission::AlwaysAllow { - permission_manager.update_user_permission(&tool_call.name, PermissionLevel::AlwaysAllow); - } + if confirmation.permission == Permission::AlwaysAllow { + permission_manager.update_user_permission(&tool_call.name, PermissionLevel::AlwaysAllow); } } else { // User declined - add declined response diff --git a/crates/goose/src/message.rs b/crates/goose/src/message.rs index 9d5b2356a5..ecdf41574b 100644 --- a/crates/goose/src/message.rs +++ b/crates/goose/src/message.rs @@ -59,14 +59,6 @@ pub struct ToolConfirmationRequest { pub prompt: Option, } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ExtensionRequest { - pub id: String, - pub extension_name: String, - pub tool_name: String, -} - #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct ThinkingContent { pub thinking: String, @@ -95,7 +87,6 @@ pub enum MessageContent { ToolRequest(ToolRequest), ToolResponse(ToolResponse), ToolConfirmationRequest(ToolConfirmationRequest), - ExtensionRequest(ExtensionRequest), FrontendToolRequest(FrontendToolRequest), Thinking(ThinkingContent), RedactedThinking(RedactedThinkingContent), @@ -145,18 +136,6 @@ impl MessageContent { }) } - pub fn extension_request>( - id: S, - extension_name: String, - tool_name: String, - ) -> Self { - MessageContent::ExtensionRequest(ExtensionRequest { - id: id.into(), - extension_name, - tool_name, - }) - } - pub fn thinking, S2: Into>(thinking: S1, signature: S2) -> Self { MessageContent::Thinking(ThinkingContent { thinking: thinking.into(), @@ -198,14 +177,6 @@ impl MessageContent { } } - pub fn as_extension_request(&self) -> Option<&ExtensionRequest> { - if let MessageContent::ExtensionRequest(ref extension_request) = self { - Some(extension_request) - } else { - None - } - } - pub fn as_tool_response_text(&self) -> Option { if let Some(tool_response) = self.as_tool_response() { if let Ok(contents) = &tool_response.tool_result { @@ -365,19 +336,6 @@ impl Message { )) } - pub fn with_extension_request>( - self, - id: S, - extension_name: String, - tool_name: String, - ) -> Self { - self.with_content(MessageContent::extension_request( - id, - extension_name, - tool_name, - )) - } - pub fn with_frontend_tool_request>( self, id: S, diff --git a/crates/goose/src/permission/permission_judge.rs b/crates/goose/src/permission/permission_judge.rs index cb831f1c07..b44f64ac05 100644 --- a/crates/goose/src/permission/permission_judge.rs +++ b/crates/goose/src/permission/permission_judge.rs @@ -6,14 +6,11 @@ use crate::providers::base::Provider; use chrono::Utc; use indoc::indoc; use mcp_core::tool::ToolAnnotations; -use mcp_core::ToolCall; use mcp_core::{tool::Tool, TextContent}; use serde_json::{json, Value}; use std::collections::HashSet; use std::sync::Arc; -use super::permission_confirmation::PrincipalType; - /// Creates the tool definition for checking read-only permissions. fn create_read_only_tool() -> Tool { Tool::new( @@ -154,36 +151,6 @@ pub async fn detect_read_only_tools( } } -/// Gets the boolean value whether the message is enable extension related and -/// the cconfirmation message based on the tool call -pub fn get_confirmation_message(request_id: &str, tool_call: ToolCall) -> (PrincipalType, Message) { - if tool_call.name == PLATFORM_ENABLE_EXTENSION_TOOL_NAME { - ( - PrincipalType::Extension, - Message::user().with_extension_request( - request_id, - tool_call - .arguments - .get("extension_name") - .and_then(|v| v.as_str()) - .unwrap_or("") - .to_string(), - tool_call.name.clone(), - ), - ) - } else { - ( - PrincipalType::Tool, - Message::user().with_tool_confirmation_request( - request_id, - tool_call.name.clone(), - tool_call.arguments.clone(), - Some("Goose would like to call the above tool. Allow? (y/n):".to_string()), - ), - ) - } -} - // Define return structure pub struct PermissionCheckResult { pub approved: Vec, @@ -198,26 +165,24 @@ pub async fn check_tool_permissions( tools_without_annotation: HashSet, permission_manager: &mut PermissionManager, provider: Arc, -) -> PermissionCheckResult { +) -> (PermissionCheckResult, Vec) { let mut approved = vec![]; let mut needs_approval = vec![]; let mut denied = vec![]; let mut llm_detect_candidates = vec![]; + let mut enable_extension_request_ids = vec![]; for request in candidate_requests { if let Ok(tool_call) = request.tool_call.clone() { - // Always ask approval for enable extension tool. - if tool_call.name == PLATFORM_ENABLE_EXTENSION_TOOL_NAME { - // Insert at the front of the list so that enable extension can be run before other tools. - needs_approval.insert(0, request.clone()); - continue; - } - if mode == "chat" { continue; } else if mode == "auto" { approved.push(request.clone()); } else { + if tool_call.name == PLATFORM_ENABLE_EXTENSION_TOOL_NAME { + enable_extension_request_ids.push(request.id.clone()); + } + // 1. Check user-defined permission if let Some(level) = permission_manager.get_user_permission(&tool_call.name) { match level { @@ -284,11 +249,14 @@ pub async fn check_tool_permissions( } } - PermissionCheckResult { - approved, - needs_approval, - denied, - } + ( + PermissionCheckResult { + approved, + needs_approval, + denied, + }, + enable_extension_request_ids, + ) } #[cfg(test)] @@ -471,7 +439,7 @@ mod tests { vec![tool_request_1, tool_request_2, enable_extension]; // Call the function under test - let result = check_tool_permissions( + let (result, enable_extension_request_ids) = check_tool_permissions( &candidate_requests, "smart_approve", tools_with_readonly_annotation, @@ -485,21 +453,13 @@ mod tests { assert_eq!(result.approved.len(), 1); // file_reader should be approved assert_eq!(result.needs_approval.len(), 2); // data_fetcher should need approval assert_eq!(result.denied.len(), 0); // No tool should be denied in this test + assert_eq!(enable_extension_request_ids.len(), 1); // Ensure the right tools are in the approved and needs_approval lists assert!(result.approved.iter().any(|req| req.id == "tool_1")); assert!(result.needs_approval.iter().any(|req| req.id == "tool_2")); - - let tool_0 = result.needs_approval.get(0); - assert!( - tool_0.is_some(), - "Expected at least one tool in needs_approval" - ); - assert_eq!( - tool_0.unwrap().id, - "tool_3", - "PLATFORM_ENABLE_EXTENSION_TOOL_NAME should be the first in needs_approval" - ); + assert!(result.needs_approval.iter().any(|req| req.id == "tool_3")); + assert!(enable_extension_request_ids.iter().any(|id| id == "tool_3")); } #[tokio::test] @@ -538,7 +498,7 @@ mod tests { let candidate_requests: Vec = vec![tool_request_1, tool_request_2]; // Call the function under test - let result = check_tool_permissions( + let (result, _) = check_tool_permissions( &candidate_requests, "auto", tools_with_readonly_annotation, diff --git a/crates/goose/src/providers/formats/anthropic.rs b/crates/goose/src/providers/formats/anthropic.rs index 171102c61d..b0f05fae20 100644 --- a/crates/goose/src/providers/formats/anthropic.rs +++ b/crates/goose/src/providers/formats/anthropic.rs @@ -60,9 +60,6 @@ pub fn format_messages(messages: &[Message]) -> Vec { MessageContent::ToolConfirmationRequest(_tool_confirmation_request) => { // Skip tool confirmation requests } - MessageContent::ExtensionRequest(_extension_request) => { - // Skip extension requests - } MessageContent::Thinking(thinking) => { content.push(json!({ "type": "thinking", diff --git a/crates/goose/src/providers/formats/bedrock.rs b/crates/goose/src/providers/formats/bedrock.rs index 1a7b59c7d8..da2bb332e0 100644 --- a/crates/goose/src/providers/formats/bedrock.rs +++ b/crates/goose/src/providers/formats/bedrock.rs @@ -31,9 +31,6 @@ pub fn to_bedrock_message_content(content: &MessageContent) -> Result { bedrock::ContentBlock::Text("".to_string()) } - MessageContent::ExtensionRequest(_extension_request) => { - bedrock::ContentBlock::Text("".to_string()) - } MessageContent::Image(_) => { bail!("Image content is not supported by Bedrock provider yet") } diff --git a/crates/goose/src/providers/formats/databricks.rs b/crates/goose/src/providers/formats/databricks.rs index 072da7c425..fce49f9325 100644 --- a/crates/goose/src/providers/formats/databricks.rs +++ b/crates/goose/src/providers/formats/databricks.rs @@ -179,9 +179,6 @@ pub fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec< MessageContent::ToolConfirmationRequest(_) => { // Skip tool confirmation requests } - MessageContent::ExtensionRequest(_) => { - // Skip enable extension requests - } MessageContent::Image(image) => { // Handle direct image content content_array.push(json!({ diff --git a/crates/goose/src/providers/formats/openai.rs b/crates/goose/src/providers/formats/openai.rs index 5fc55c86a1..e06f80b815 100644 --- a/crates/goose/src/providers/formats/openai.rs +++ b/crates/goose/src/providers/formats/openai.rs @@ -147,9 +147,6 @@ pub fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec< MessageContent::ToolConfirmationRequest(_) => { // Skip tool confirmation requests } - MessageContent::ExtensionRequest(_) => { - // Skip enable extension requests - } MessageContent::Image(image) => { // Handle direct image content converted["content"] = json!([convert_image(image, image_format)]); diff --git a/ui/desktop/src/components/ChatView.tsx b/ui/desktop/src/components/ChatView.tsx index 2c139ba83b..33af8278c1 100644 --- a/ui/desktop/src/components/ChatView.tsx +++ b/ui/desktop/src/components/ChatView.tsx @@ -28,7 +28,6 @@ import { ToolRequestMessageContent, ToolResponseMessageContent, ToolConfirmationRequestMessageContent, - ExtensionRequestMessageContent, } from '../types/message'; export interface ChatType { @@ -48,9 +47,6 @@ const isUserMessage = (message: Message): boolean => { if (message.content.every((c) => c.type === 'toolConfirmationRequest')) { return false; } - if (message.content.every((c) => c.type === 'extensionRequest')) { - return false; - } return true; }; @@ -260,14 +256,6 @@ export default function ChatView({ return [content.id, toolCall]; } }); - const extensionRequests = lastMessage.content - .filter( - (content): content is ExtensionRequestMessageContent => - content.type === 'extensionRequest' - ) - .map((content) => { - return [content.id, content.extensionCall]; - }); if (toolRequests.length !== 0) { // This means we were interrupted during a tool request @@ -297,30 +285,6 @@ export default function ChatView({ // Use an immutable update to add the response message to the messages array setMessages([...messages, responseMessage]); } - - // do the same for enable extension requests - // leverages toolResponse to send the error notification - if (extensionRequests.length !== 0) { - let responseMessage: Message = { - role: 'user', - created: Date.now(), - content: [], - }; - const notification = 'Interrupted by the user to make a correction'; - // generate a response saying it was interrupted for each extension request - for (const [reqId, _] of extensionRequests) { - const toolResponse: ToolResponseMessageContent = { - type: 'toolResponse', - id: reqId, - toolResult: { - status: 'error', - error: notification, - }, - }; - responseMessage.content.push(toolResponse); - } - setMessages([...messages, responseMessage]); - } } }; @@ -338,9 +302,8 @@ export default function ChatView({ (c) => c.type === 'toolConfirmationRequest' ); - const hasExtensionRequest = message.content.every((c) => c.type === 'extensionRequest'); // Keep the message if it has text content or tool confirmation or is not just tool responses - return hasTextContent || !hasOnlyToolResponses || hasToolConfirmation || hasExtensionRequest; + return hasTextContent || !hasOnlyToolResponses || hasToolConfirmation; } return true; diff --git a/ui/desktop/src/components/ExtensionConfirmation.tsx b/ui/desktop/src/components/ExtensionConfirmation.tsx deleted file mode 100644 index 114c5f136c..0000000000 --- a/ui/desktop/src/components/ExtensionConfirmation.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import React, { useState } from 'react'; -import { snakeToTitleCase } from '../utils'; -import { confirmPermission } from '../api'; - -interface ExtensionConfirmationProps { - isCancelledMessage: boolean; - isClicked: boolean; - extensionConfirmationId: string; - extensionName: string; - toolName: string; -} -export default function ExtensionConfirmation({ - isCancelledMessage, - isClicked, - extensionConfirmationId, - extensionName, - toolName, -}: ExtensionConfirmationProps) { - const [clicked, setClicked] = useState(isClicked); - const [status, setStatus] = useState('unknown'); - - const extensionAction = toolName.toLowerCase().includes('enable') ? 'enable' : 'disable'; - - const handleButtonClick = async (confirmed: boolean) => { - setClicked(true); - setStatus(confirmed ? 'approved' : 'denied'); - try { - const response = await confirmPermission({ - body: { - id: extensionConfirmationId, - action: confirmed ? 'allow_once' : 'deny', - principal_type: 'Extension', - }, - }); - if (response.error) { - console.error('Failed to confirm permission: ', response.error); - } - } catch (err) { - console.error('Error fetching tools:', err); - } - }; - - return isCancelledMessage ? ( -
- Extension {extensionAction} is cancelled. -
- ) : ( - <> -
- Goose would like to {extensionAction} the following extension. Allow? -
- {clicked ? ( -
-
- {status === 'approved' && ( - - - - )} - {status === 'denied' && ( - - - - )} - - {isClicked - ? 'Extension enablement is not available' - : `${snakeToTitleCase(extensionName.includes('__') ? extensionName.split('__').pop() || extensionName : extensionName)} is ${status}`}{' '} - -
-
- ) : ( -
- - -
- )} - - ); -} diff --git a/ui/desktop/src/components/GooseMessage.tsx b/ui/desktop/src/components/GooseMessage.tsx index 4d3358f050..a4fc0cb9f5 100644 --- a/ui/desktop/src/components/GooseMessage.tsx +++ b/ui/desktop/src/components/GooseMessage.tsx @@ -12,11 +12,9 @@ import { getToolResponses, getToolConfirmationContent, createToolErrorResponseMessage, - getExtensionContent, } from '../types/message'; import ToolCallConfirmation from './ToolCallConfirmation'; import MessageCopyLink from './MessageCopyLink'; -import ExtensionConfirmation from './ExtensionConfirmation'; interface GooseMessageProps { messageHistoryIndex: number; @@ -58,9 +56,6 @@ export default function GooseMessage({ const toolConfirmationContent = getToolConfirmationContent(message); const hasToolConfirmation = toolConfirmationContent !== undefined; - const extensionContent = getExtensionContent(message); - const hasExtensionRequest = extensionContent !== undefined; - // Find tool responses that correspond to the tool requests in this message const toolResponsesMap = useMemo(() => { const responseMap = new Map(); @@ -95,23 +90,12 @@ export default function GooseMessage({ createToolErrorResponseMessage(toolConfirmationContent.id, 'The tool call is cancelled.') ); } - if (messageIndex == messageHistoryIndex - 1 && hasExtensionRequest) { - appendMessage( - createToolErrorResponseMessage( - extensionContent.id, - 'The extension enablement is cancelled.' - ) - ); - } }, [ messageIndex, messageHistoryIndex, hasToolConfirmation, toolConfirmationContent, appendMessage, - hasExtensionRequest, - // Only include enableExtensionContent if it exists - extensionContent?.id, ]); return ( @@ -172,16 +156,6 @@ export default function GooseMessage({ toolName={toolConfirmationContent.toolName} /> )} - - {hasExtensionRequest && ( - - )} {/* TODO(alexhancock): Re-enable link previews once styled well again */} diff --git a/ui/desktop/src/components/settings_v2/permission/PermissionModal.tsx b/ui/desktop/src/components/settings_v2/permission/PermissionModal.tsx index d9091c6f5e..aea1316259 100644 --- a/ui/desktop/src/components/settings_v2/permission/PermissionModal.tsx +++ b/ui/desktop/src/components/settings_v2/permission/PermissionModal.tsx @@ -34,9 +34,7 @@ export default function PermissionModal({ extensionName, onClose }: PermissionMo } else { const filteredTools = (response.data || []).filter( (tool) => - tool.name !== 'platform__enable_extension' && - tool.name !== 'platform__read_resource' && - tool.name !== 'platform__list_resources' + tool.name !== 'platform__read_resource' && tool.name !== 'platform__list_resources' ); setTools(filteredTools); } diff --git a/ui/desktop/src/components/settings_v2/permission/PermissionSetting.tsx b/ui/desktop/src/components/settings_v2/permission/PermissionSetting.tsx index 29eb6dd1ed..8c936539fc 100644 --- a/ui/desktop/src/components/settings_v2/permission/PermissionSetting.tsx +++ b/ui/desktop/src/components/settings_v2/permission/PermissionSetting.tsx @@ -46,6 +46,11 @@ export default function PermissionSettingsView({ onClose }: { onClose: () => voi const extensionsList = await getExtensions(true); // Force refresh // Filter out disabled extensions const enabledExtensions = extensionsList.filter((extension) => extension.enabled); + enabledExtensions.push({ + name: 'platform', + type: 'builtin', + enabled: true, + }); // Sort extensions by name to maintain consistent order const sortedExtensions = [...enabledExtensions].sort((a, b) => { // First sort by builtin diff --git a/ui/desktop/src/types/message.ts b/ui/desktop/src/types/message.ts index b7a10c43e0..875cefe161 100644 --- a/ui/desktop/src/types/message.ts +++ b/ui/desktop/src/types/message.ts @@ -41,13 +41,6 @@ export interface ToolResponse { toolResult: ToolCallResult; } -export interface ToolConfirmationRequest { - id: string; - toolName: string; - arguments: Record; - prompt?: string; -} - export interface ToolRequestMessageContent { type: 'toolRequest'; id: string; @@ -80,33 +73,12 @@ export interface ExtensionCallResult { error?: string; } -export interface ExtensionRequest { - id: string; - extensionCall: ExtensionCallResult; -} - -export interface ExtensionConfirmationRequest { - id: string; - extensionName: string; - arguments: Record; - prompt?: string; -} - -export interface ExtensionRequestMessageContent { - type: 'extensionRequest'; - id: string; - extensionCall: ExtensionCallResult; - extensionName: string; - toolName: string; -} - export type MessageContent = | TextContent | ImageContent | ToolRequestMessageContent | ToolResponseMessageContent - | ToolConfirmationRequestMessageContent - | ExtensionRequestMessageContent; + | ToolConfirmationRequestMessageContent; export interface Message { id?: string; @@ -220,12 +192,6 @@ export function getToolResponses(message: Message): ToolResponseMessageContent[] ); } -export function getExtensionRequests(message: Message): ExtensionRequestMessageContent[] { - return message.content.filter( - (content): content is ExtensionRequestMessageContent => content.type === 'extensionRequest' - ); -} - export function getToolConfirmationContent( message: Message ): ToolConfirmationRequestMessageContent { @@ -235,12 +201,6 @@ export function getToolConfirmationContent( ); } -export function getExtensionContent(message: Message): ExtensionRequestMessageContent { - return message.content.find( - (content): content is ExtensionRequestMessageContent => content.type === 'extensionRequest' - ); -} - export function hasCompletedToolCalls(message: Message): boolean { const toolRequests = getToolRequests(message); if (toolRequests.length === 0) return false;