mirror of
https://github.com/aljazceru/goose.git
synced 2025-12-18 22:54:24 +01:00
feat: enable smart approve for user by default (#1599)
This commit is contained in:
@@ -15,7 +15,6 @@ use super::Agent;
|
|||||||
use crate::agents::capabilities::Capabilities;
|
use crate::agents::capabilities::Capabilities;
|
||||||
use crate::agents::extension::{ExtensionConfig, ExtensionResult};
|
use crate::agents::extension::{ExtensionConfig, ExtensionResult};
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::config::ExperimentManager;
|
|
||||||
use crate::memory_condense::condense_messages;
|
use crate::memory_condense::condense_messages;
|
||||||
use crate::message::{Message, ToolRequest};
|
use crate::message::{Message, ToolRequest};
|
||||||
use crate::providers::base::Provider;
|
use crate::providers::base::Provider;
|
||||||
@@ -283,11 +282,7 @@ impl Agent for SummarizeAgent {
|
|||||||
let mode = goose_mode.clone();
|
let mode = goose_mode.clone();
|
||||||
match mode.as_str() {
|
match mode.as_str() {
|
||||||
"approve" => {
|
"approve" => {
|
||||||
let mut read_only_tools = Vec::new();
|
let read_only_tools = detect_read_only_tools(&capabilities, tool_requests.clone()).await;
|
||||||
// Process each tool request sequentially with confirmation
|
|
||||||
if ExperimentManager::is_enabled("GOOSE_SMART_APPROVE")? {
|
|
||||||
read_only_tools = detect_read_only_tools(&capabilities, tool_requests.clone()).await;
|
|
||||||
}
|
|
||||||
for request in &tool_requests {
|
for request in &tool_requests {
|
||||||
if let Ok(tool_call) = request.tool_call.clone() {
|
if let Ok(tool_call) = request.tool_call.clone() {
|
||||||
// Skip confirmation if the tool_call.name is in the read_only_tools list
|
// Skip confirmation if the tool_call.name is in the read_only_tools list
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ use crate::agents::capabilities::Capabilities;
|
|||||||
use crate::agents::extension::{ExtensionConfig, ExtensionResult};
|
use crate::agents::extension::{ExtensionConfig, ExtensionResult};
|
||||||
use crate::agents::ToolPermissionStore;
|
use crate::agents::ToolPermissionStore;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::config::ExperimentManager;
|
|
||||||
use crate::message::{Message, ToolRequest};
|
use crate::message::{Message, ToolRequest};
|
||||||
use crate::providers::base::Provider;
|
use crate::providers::base::Provider;
|
||||||
use crate::providers::base::ProviderUsage;
|
use crate::providers::base::ProviderUsage;
|
||||||
@@ -299,7 +298,7 @@ impl Agent for TruncateAgent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only check read-only status for tools needing confirmation
|
// Only check read-only status for tools needing confirmation
|
||||||
if !needs_confirmation.is_empty() && ExperimentManager::is_enabled("GOOSE_SMART_APPROVE")? {
|
if !needs_confirmation.is_empty() {
|
||||||
read_only_tools = detect_read_only_tools(&capabilities, needs_confirmation.clone()).await;
|
read_only_tools = detect_read_only_tools(&capabilities, needs_confirmation.clone()).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use std::collections::HashMap;
|
|||||||
/// It is the ground truth for init experiments. The experiment names in users' experiment list but not
|
/// It is the ground truth for init experiments. The experiment names in users' experiment list but not
|
||||||
/// in the list will be remove from user list; The experiment names in the ground-truth list but not
|
/// in the list will be remove from user list; The experiment names in the ground-truth list but not
|
||||||
/// in users' experiment list will be added to user list with default value false;
|
/// in users' experiment list will be added to user list with default value false;
|
||||||
const ALL_EXPERIMENTS: &[(&str, bool)] = &[("GOOSE_SMART_APPROVE", true)];
|
const ALL_EXPERIMENTS: &[(&str, bool)] = &[];
|
||||||
|
|
||||||
/// Experiment configuration management
|
/// Experiment configuration management
|
||||||
pub struct ExperimentManager;
|
pub struct ExperimentManager;
|
||||||
|
|||||||
@@ -96,76 +96,8 @@ Here's how to configure:
|
|||||||
4. Under `Mode Selection`, choose the mode you'd like
|
4. Under `Mode Selection`, choose the mode you'd like
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
If you choose `Approve` mode, you will see "Allow" and "Deny" buttons in your session windows during tool calls with write operations.
|
If you choose `Approve` mode, you will see "Allow" and "Deny" buttons in your session windows during tool calls. Goose will only ask for permission before tool call for tools that it deems are 'write' tools, for example any 'text editor write', 'text editor edit', 'bash - rm, cp, mv' commands, as an example. Read write approval makes best effort attempt at classifying read or write tools- this is interpreted by your LLM provider.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
|
|
||||||
## Smart Approve
|
|
||||||
|
|
||||||
Goose introduces the **Smart Approve** feature when the Goose mode is set to `Approve`. With Smart Approve enabled, Goose evaluates the risk level of a tool call before execution.
|
|
||||||
|
|
||||||
- **If the tool call is deemed risky (e.g. tool requires Goose to write)**, Goose will prompt you for confirmation before proceeding.
|
|
||||||
- **If the tool call is considered safe**, Goose will execute it directly without any notification.
|
|
||||||
|
|
||||||
This feature is enabled by default. If you wish to disable Smart Approve, you can
|
|
||||||
|
|
||||||
1. Run the following command:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
goose configure
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Select `Goose Settings` from the menu and press Enter.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
┌ goose-configure
|
|
||||||
│
|
|
||||||
◆ What would you like to configure?
|
|
||||||
| ○ Configure Providers
|
|
||||||
| ○ Add Extension
|
|
||||||
| ○ Toggle Extensions
|
|
||||||
| ○ Remove Extension
|
|
||||||
// highlight-start
|
|
||||||
| ● Goose Settings (Set the Goose Mode, Tool Output, Experiment and more)
|
|
||||||
// highlight-end
|
|
||||||
└
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Choose `Toggle Experiment` from the menu and press Enter.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
┌ goose-configure
|
|
||||||
│
|
|
||||||
◇ What would you like to configure?
|
|
||||||
│ Goose Settings
|
|
||||||
│
|
|
||||||
◆ What setting would you like to configure?
|
|
||||||
│ ○ Goose Mode
|
|
||||||
│ ○ Tool Output
|
|
||||||
// highlight-start
|
|
||||||
│ ● Toggle Experiment (Enable or disable an experiment feature)
|
|
||||||
// highlight-end
|
|
||||||
└
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Toggle `GOOSE_SMART_APPROVE` and press Enter.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
┌ goose-configure
|
|
||||||
┌ goose-configure
|
|
||||||
│
|
|
||||||
◇ What would you like to configure?
|
|
||||||
│ Goose Settings
|
|
||||||
│
|
|
||||||
◇ What setting would you like to configure?
|
|
||||||
│ Toggle Experiment
|
|
||||||
│
|
|
||||||
◆ enable experiments: (use "space" to toggle and "enter" to submit)
|
|
||||||
// highlight-start
|
|
||||||
│ ◼ GOOSE_SMART_APPROVE
|
|
||||||
// highlight-end
|
|
||||||
└
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ import BackButton from '../ui/BackButton';
|
|||||||
import { RecentModelsRadio } from './models/RecentModels';
|
import { RecentModelsRadio } from './models/RecentModels';
|
||||||
import { ExtensionItem } from './extensions/ExtensionItem';
|
import { ExtensionItem } from './extensions/ExtensionItem';
|
||||||
import type { View } from '../../App';
|
import type { View } from '../../App';
|
||||||
import ModeSelection from './basic/ModeSelection';
|
import { ModeSelection } from './basic/ModeSelection';
|
||||||
import { getApiUrl, getSecretKey } from '../../config';
|
|
||||||
|
|
||||||
const EXTENSIONS_DESCRIPTION =
|
const EXTENSIONS_DESCRIPTION =
|
||||||
'The Model Context Protocol (MCP) is a system that allows AI models to securely connect with local or remote resources using standard server setups. It works like a client-server setup and expands AI capabilities using three main components: Prompts, Resources, and Tools.';
|
'The Model Context Protocol (MCP) is a system that allows AI models to securely connect with local or remote resources using standard server setups. It works like a client-server setup and expands AI capabilities using three main components: Prompts, Resources, and Tools.';
|
||||||
@@ -62,55 +61,6 @@ export default function SettingsView({
|
|||||||
setView: (view: View) => void;
|
setView: (view: View) => void;
|
||||||
viewOptions: SettingsViewOptions;
|
viewOptions: SettingsViewOptions;
|
||||||
}) {
|
}) {
|
||||||
const [mode, setMode] = useState('auto');
|
|
||||||
|
|
||||||
const handleModeChange = async (newMode: string) => {
|
|
||||||
const storeResponse = await fetch(getApiUrl('/configs/store'), {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-Secret-Key': getSecretKey(),
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
key: 'GOOSE_MODE',
|
|
||||||
value: newMode,
|
|
||||||
isSecret: false,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!storeResponse.ok) {
|
|
||||||
const errorText = await storeResponse.text();
|
|
||||||
console.error('Store response error:', errorText);
|
|
||||||
throw new Error(`Failed to store new goose mode: ${newMode}`);
|
|
||||||
}
|
|
||||||
setMode(newMode);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchCurrentMode = async () => {
|
|
||||||
try {
|
|
||||||
const response = await fetch(getApiUrl('/configs/get?key=GOOSE_MODE'), {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-Secret-Key': getSecretKey(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
const { value } = await response.json();
|
|
||||||
if (value) {
|
|
||||||
setMode(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching current mode:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchCurrentMode();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const [settings, setSettings] = React.useState<SettingsType>(() => {
|
const [settings, setSettings] = React.useState<SettingsType>(() => {
|
||||||
const saved = localStorage.getItem('user_settings');
|
const saved = localStorage.getItem('user_settings');
|
||||||
window.electron.logInfo('Settings: ' + saved);
|
window.electron.logInfo('Settings: ' + saved);
|
||||||
@@ -304,7 +254,7 @@ export default function SettingsView({
|
|||||||
Others setting like Goose Mode, Tool Output, Experiment and more
|
Others setting like Goose Mode, Tool Output, Experiment and more
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ModeSelection value={mode} onChange={handleModeChange} />
|
<ModeSelection />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import * as RadioGroup from '@radix-ui/react-radio-group';
|
import * as RadioGroup from '@radix-ui/react-radio-group';
|
||||||
import React from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { getApiUrl, getSecretKey } from '../../../config';
|
||||||
|
|
||||||
const ModeSelection = ({ value, onChange }) => {
|
export const ModeSelection = () => {
|
||||||
const modes = [
|
const modes = [
|
||||||
{
|
{
|
||||||
value: 'auto',
|
value: 'auto',
|
||||||
@@ -11,7 +12,8 @@ const ModeSelection = ({ value, onChange }) => {
|
|||||||
{
|
{
|
||||||
value: 'approve',
|
value: 'approve',
|
||||||
label: 'Approval needed',
|
label: 'Approval needed',
|
||||||
description: 'Editing, creating, and deleting files will require human approval.',
|
description:
|
||||||
|
'Classifies the tool as either a read-only tool or write tool. Write tools will ask for human approval.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'chat',
|
value: 'chat',
|
||||||
@@ -20,11 +22,64 @@ const ModeSelection = ({ value, onChange }) => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const [currentMode, setCurrentMode] = useState('auto');
|
||||||
|
|
||||||
|
const handleModeChange = async (newMode: string) => {
|
||||||
|
const storeResponse = await fetch(getApiUrl('/configs/store'), {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Secret-Key': getSecretKey(),
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
key: 'GOOSE_MODE',
|
||||||
|
value: newMode,
|
||||||
|
isSecret: false,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!storeResponse.ok) {
|
||||||
|
const errorText = await storeResponse.text();
|
||||||
|
console.error('Store response error:', errorText);
|
||||||
|
throw new Error(`Failed to store new goose mode: ${newMode}`);
|
||||||
|
}
|
||||||
|
setCurrentMode(newMode);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchCurrentMode = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(getApiUrl('/configs/get?key=GOOSE_MODE'), {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Secret-Key': getSecretKey(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const { value } = await response.json();
|
||||||
|
if (value) {
|
||||||
|
setCurrentMode(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching current mode:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchCurrentMode();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-medium mb-4 text-textStandard">Mode Selection</h4>
|
<h4 className="font-medium mb-4 text-textStandard">Mode Selection</h4>
|
||||||
|
|
||||||
<RadioGroup.Root className="flex flex-col space-y-2" value={value} onValueChange={onChange}>
|
<RadioGroup.Root
|
||||||
|
className="flex flex-col space-y-2"
|
||||||
|
value={currentMode}
|
||||||
|
onValueChange={handleModeChange}
|
||||||
|
>
|
||||||
{modes.map((mode) => (
|
{modes.map((mode) => (
|
||||||
<RadioGroup.Item
|
<RadioGroup.Item
|
||||||
key={mode.value}
|
key={mode.value}
|
||||||
@@ -41,7 +96,7 @@ const ModeSelection = ({ value, onChange }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<div className="w-4 h-4 flex items-center justify-center rounded-full border border-gray-500 dark:border-gray-400">
|
<div className="w-4 h-4 flex items-center justify-center rounded-full border border-gray-500 dark:border-gray-400">
|
||||||
{value === mode.value && (
|
{currentMode === mode.value && (
|
||||||
<div className="w-2 h-2 bg-black dark:bg-white rounded-full" />
|
<div className="w-2 h-2 bg-black dark:bg-white rounded-full" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -52,5 +107,3 @@ const ModeSelection = ({ value, onChange }) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ModeSelection;
|
|
||||||
|
|||||||
Reference in New Issue
Block a user