diff --git a/ui/desktop/src/App.tsx b/ui/desktop/src/App.tsx
index 0d73ed01..97f77158 100644
--- a/ui/desktop/src/App.tsx
+++ b/ui/desktop/src/App.tsx
@@ -35,6 +35,7 @@ import 'react-toastify/dist/ReactToastify.css';
import { useConfig, MalformedConfigError } from './components/ConfigContext';
import { addExtensionFromDeepLink as addExtensionFromDeepLinkV2 } from './components/settings_v2/extensions';
import { initConfig } from './api/sdk.gen';
+import PermissionSettingsView from './components/settings_v2/permission/PermissionSetting';
// Views and their options
export type View =
@@ -49,7 +50,8 @@ export type View =
| 'sessions'
| 'sharedSession'
| 'loading'
- | 'recipeEditor';
+ | 'recipeEditor'
+ | 'permission';
export type ViewOptions =
| SettingsViewOptions
@@ -779,6 +781,7 @@ export default function App() {
}}
/>
)}
+ {view === 'permission' && setView('settings')} />}
{isGoosehintsModalOpen && (
diff --git a/ui/desktop/src/components/settings_v2/SettingsView.tsx b/ui/desktop/src/components/settings_v2/SettingsView.tsx
index 62ba4f6e..b612ed88 100644
--- a/ui/desktop/src/components/settings_v2/SettingsView.tsx
+++ b/ui/desktop/src/components/settings_v2/SettingsView.tsx
@@ -43,7 +43,7 @@ export default function SettingsView({
showEnvVars={viewOptions.showEnvVars}
/>
{/* Goose Modes */}
-
+
{/*Session sharing*/}
diff --git a/ui/desktop/src/components/settings_v2/mode/ModeSection.tsx b/ui/desktop/src/components/settings_v2/mode/ModeSection.tsx
index 7d89bdff..3f66a09f 100644
--- a/ui/desktop/src/components/settings_v2/mode/ModeSection.tsx
+++ b/ui/desktop/src/components/settings_v2/mode/ModeSection.tsx
@@ -1,10 +1,14 @@
import React, { useEffect, useState } from 'react';
import { getApiUrl, getSecretKey } from '../../../config';
-import { all_goose_modes, filterGooseModes, ModeSelectionItem } from './ModeSelectionItem';
+import { all_goose_modes, ModeSelectionItem } from './ModeSelectionItem';
+import { View } from '../../../App';
-export const ModeSection = () => {
+interface ModeSectionProps {
+ setView: (view: View) => void;
+}
+
+export const ModeSection = ({ setView }: ModeSectionProps) => {
const [currentMode, setCurrentMode] = useState('auto');
- const [previousApproveModel, setPreviousApproveModel] = useState('');
const handleModeChange = async (newMode: string) => {
const storeResponse = await fetch(getApiUrl('/configs/store'), {
@@ -25,10 +29,6 @@ export const ModeSection = () => {
console.error('Store response error:', errorText);
throw new Error(`Failed to store new goose mode: ${newMode}`);
}
- // Only track the previous approve if current mode is approve related but new mode is not.
- if (currentMode.includes('approve') && !newMode.includes('approve')) {
- setPreviousApproveModel(currentMode);
- }
setCurrentMode(newMode);
};
@@ -67,13 +67,14 @@ export const ModeSection = () => {
Configure how Goose interacts with tools and extensions
- {filterGooseModes(currentMode, all_goose_modes, previousApproveModel).map((mode) => (
+ {all_goose_modes.map((mode) => (
))}
diff --git a/ui/desktop/src/components/settings_v2/mode/ModeSelectionItem.tsx b/ui/desktop/src/components/settings_v2/mode/ModeSelectionItem.tsx
index 27a1d1c0..17cc6f98 100644
--- a/ui/desktop/src/components/settings_v2/mode/ModeSelectionItem.tsx
+++ b/ui/desktop/src/components/settings_v2/mode/ModeSelectionItem.tsx
@@ -1,6 +1,7 @@
import React, { useEffect, useState } from 'react';
import { Gear } from '../../icons';
import { ConfigureApproveMode } from './ConfigureApproveMode';
+import { View } from '../../../App';
export interface GooseMode {
key: string;
@@ -71,6 +72,7 @@ interface ModeSelectionItemProps {
mode: GooseMode;
showDescription: boolean;
isApproveModeConfigure: boolean;
+ setView: (view: View) => void;
handleModeChange: (newMode: string) => void;
}
@@ -79,6 +81,7 @@ export function ModeSelectionItem({
mode,
showDescription,
isApproveModeConfigure,
+ setView,
handleModeChange,
}: ModeSelectionItemProps) {
const [checked, setChecked] = useState(currentMode == mode.key);
@@ -109,7 +112,7 @@ export function ModeSelectionItem({
{!isApproveModeConfigure && (mode.key == 'approve' || mode.key == 'smart_approve') && (
diff --git a/ui/desktop/src/components/settings_v2/permission/PermissionSetting.tsx b/ui/desktop/src/components/settings_v2/permission/PermissionSetting.tsx
new file mode 100644
index 00000000..5d1ef8c0
--- /dev/null
+++ b/ui/desktop/src/components/settings_v2/permission/PermissionSetting.tsx
@@ -0,0 +1,123 @@
+import React, { useCallback, useEffect, useState } from 'react';
+import { ScrollArea } from '../../ui/scroll-area';
+import BackButton from '../../ui/BackButton';
+import { FixedExtensionEntry, useConfig } from '../../ConfigContext';
+import { ChevronRight } from 'lucide-react';
+import PermissionModal from './PermissionModal';
+
+function RuleItem({ title, description }: { title: string; description: string }) {
+ const [isModalOpen, setIsModalOpen] = useState(false);
+
+ const handleModalClose = () => {
+ console.log('close modal');
+ setIsModalOpen(false);
+ };
+
+ return (
+
+
+
{title}
+
{description}
+
+
+
+
+ {/* Modal for updating tool permission */}
+ {isModalOpen &&
}
+
+ );
+}
+
+function RulesSection({ title, rules }: { title: string; rules: React.ReactNode }) {
+ return (
+
+
{title}
+ {rules}
+
+ );
+}
+
+export default function PermissionSettingsView({ onClose }: { onClose: () => void }) {
+ const { getExtensions } = useConfig();
+ const [extensions, setExtensions] = useState([]);
+
+ const fetchExtensions = useCallback(async () => {
+ const extensionsList = await getExtensions(true); // Force refresh
+ // Filter out disabled extensions
+ const enabledExtensions = extensionsList.filter((extension) => extension.enabled);
+ // Sort extensions by name to maintain consistent order
+ const sortedExtensions = [...enabledExtensions].sort((a, b) => {
+ // First sort by builtin
+ if (a.type === 'builtin' && b.type !== 'builtin') return -1;
+ if (a.type !== 'builtin' && b.type === 'builtin') return 1;
+
+ // Then sort by bundled (handle null/undefined cases)
+ const aBundled = a.bundled === true;
+ const bBundled = b.bundled === true;
+ if (aBundled && !bBundled) return -1;
+ if (!aBundled && bBundled) return 1;
+
+ // Finally sort alphabetically within each group
+ return a.name.localeCompare(b.name);
+ });
+ setExtensions(sortedExtensions);
+ }, [getExtensions]);
+
+ useEffect(() => {
+ fetchExtensions();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ return (
+
+
+
+
+
+
+
onClose()} className="mb-4" />
+
+ Permission Rules
+
+ Hidden instructions that will be passed to the provider to help direct and add context
+ to your responses.
+
+
+
+ {/* Content Area */}
+
+
+ {/* Extension Rules Section */}
+
+ {extensions.map((extension) => (
+
+ ))}
+ >
+ }
+ />
+
+
+
+
+
+ );
+}