Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 5 additions & 21 deletions app/src/components/settings/SettingsHome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,20 +134,20 @@ const SettingsHome = () => {
onClick: () => navigateToSettings('appearance'),
},
{
id: 'agent-access',
title: t('settings.agentAccess.title'),
description: t('settings.agentAccess.menuDesc'),
id: 'agents-settings',
title: t('settings.agentsSection.title'),
description: t('settings.agentsSection.menuDesc'),
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 7h10a2 2 0 012 2v6a2 2 0 01-2 2H7a2 2 0 01-2-2V9a2 2 0 012-2zm2 4h.01M15 11h.01M9.5 15h5"
/>
</svg>
),
onClick: () => navigateToSettings('agent-access'),
onClick: () => navigateToSettings('agents-settings'),
},
{
id: 'mascot',
Expand All @@ -165,22 +165,6 @@ const SettingsHome = () => {
),
onClick: () => navigateToSettings('mascot'),
},
{
id: 'persona',
title: t('settings.persona.menuTitle'),
description: t('settings.persona.menuDesc'),
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
/>
</svg>
),
onClick: () => navigateToSettings('persona'),
},
],
},
// Features tile (Screen Awareness / Messaging Channels / Notifications /
Expand Down
7 changes: 4 additions & 3 deletions app/src/components/settings/__tests__/SettingsHome.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,13 @@ describe('SettingsHome', () => {
expect(mockNavigateToSettings).toHaveBeenCalledWith('notifications');
});

it('navigates to persona settings when Persona is clicked', async () => {
it('navigates to the Agents section when Agents is clicked', async () => {
const user = userEvent.setup();
renderSettingsHome();

await user.click(screen.getByText('Persona').closest('button')!);
expect(mockNavigateToSettings).toHaveBeenCalledWith('persona');
// Persona, Agent OS access, etc. now live under the Agents section page.
await user.click(screen.getByText('Agents').closest('button')!);
expect(mockNavigateToSettings).toHaveBeenCalledWith('agents-settings');
});

it('navigates to /notifications inbox when Alerts is clicked', async () => {
Expand Down
24 changes: 19 additions & 5 deletions app/src/components/settings/hooks/useSettingsNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { useLocation, useNavigate } from 'react-router-dom';
export type SettingsRoute =
| 'home'
| 'agents'
| 'agents-settings'
| 'agent-access'
| 'account'
| 'features'
| 'messaging'
Expand Down Expand Up @@ -122,6 +124,10 @@ export const useSettingsNavigation = (): SettingsNavigationHook => {
if (path.includes('/settings/mascot')) return 'mascot';
if (path.includes('/settings/persona')) return 'persona';
if (path.includes('/settings/appearance')) return 'appearance';
// `agents-settings` (the Agents section page) must be checked before the
// shorter `agents` (the manage-agents registry panel) so it isn't swallowed.
if (path.includes('/settings/agents-settings')) return 'agents-settings';
if (path.includes('/settings/agent-access')) return 'agent-access';
if (path.includes('/settings/agents')) return 'agents';
if (path.includes('/settings/mcp-server')) return 'mcp-server';
if (path.includes('/settings/dev-workflow')) return 'dev-workflow';
Expand Down Expand Up @@ -181,14 +187,27 @@ export const useSettingsNavigation = (): SettingsNavigationHook => {
onClick: () => navigate('/settings/developer-options'),
};

const agentsCrumb: BreadcrumbItem = {
label: 'Agents',
onClick: () => navigate('/settings/agents-settings'),
};

const getBreadcrumbs = (): BreadcrumbItem[] => {
switch (currentRoute) {
// Section pages
case 'account':
case 'features':
case 'ai':
case 'agents-settings':
return [settingsCrumb];

// Leaf panels under the Agents section
case 'agents':
case 'agent-access':
case 'autonomy':
case 'persona':
return [settingsCrumb, agentsCrumb];

// Leaf panels under account
case 'recovery-phrase':
case 'wallet-balances':
Expand Down Expand Up @@ -233,7 +252,6 @@ export const useSettingsNavigation = (): SettingsNavigationHook => {
case 'notification-routing':
case 'mcp-server':
case 'dev-workflow':
case 'autonomy':
return [settingsCrumb, developerCrumb];

// Developer options section page
Expand All @@ -251,10 +269,6 @@ export const useSettingsNavigation = (): SettingsNavigationHook => {
case 'mascot':
return [settingsCrumb];

// Persona panel sits at the top level of Settings.
case 'persona':
return [settingsCrumb];

// Appearance (theme) panel sits at the top level of Settings.
case 'appearance':
return [settingsCrumb];
Expand Down
92 changes: 59 additions & 33 deletions app/src/components/settings/panels/AgentAccessPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,15 +197,19 @@ const AgentAccessPanel = () => {

<div className="p-4 space-y-6">
{!isTauri() && (
<p className="text-sm text-coral">{t('settings.agentAccess.desktopOnly')}</p>
<p className="text-sm text-coral-600 dark:text-coral-300">
{t('settings.agentAccess.desktopOnly')}
</p>
)}

{isLoading ? (
<p className="text-sm text-ink-soft">{t('settings.agentAccess.loading')}</p>
<p className="text-sm text-stone-600 dark:text-neutral-400">
{t('settings.agentAccess.loading')}
</p>
) : (
<>
<section className="space-y-2">
<h2 className="text-sm font-semibold text-ink">
<h2 className="text-sm font-semibold text-stone-900 dark:text-neutral-100">
{t('settings.agentAccess.accessMode')}
</h2>
<div className="grid gap-2">
Expand All @@ -216,27 +220,33 @@ const AgentAccessPanel = () => {
onClick={() => selectTier(p.id)}
className={`text-left rounded-lg border p-3 transition ${
level === p.id
? 'border-primary-500 bg-primary-50'
: 'border-line hover:border-primary-300'
? 'border-primary-500 bg-primary-50 dark:bg-primary-500/10'
: 'border-stone-200 dark:border-neutral-800 hover:border-primary-300 dark:hover:border-primary-500'
}`}>
<div className="flex items-center gap-2">
<span
className={`inline-block w-3 h-3 rounded-full border ${
level === p.id ? 'bg-primary-500 border-primary-500' : 'border-line'
level === p.id
? 'bg-primary-500 border-primary-500'
: 'border-stone-300 dark:border-neutral-700'
}`}
/>
<span className="font-medium text-ink">{p.title}</span>
<span className="font-medium text-stone-900 dark:text-neutral-100">
{p.title}
</span>
{p.id === 'supervised' && (
<span className="text-xs text-ink-soft">
<span className="text-xs text-stone-600 dark:text-neutral-400">
{t('settings.agentAccess.defaultTag')}
</span>
)}
</div>
<p className="mt-1 text-xs text-ink-soft">{p.description}</p>
<p className="mt-1 text-xs text-stone-600 dark:text-neutral-400">
{p.description}
</p>
</button>
))}
{level === 'full' && (
<p className="rounded border border-coral/40 bg-coral/5 p-2 text-xs text-coral">
<p className="rounded border border-coral/40 bg-coral/5 dark:bg-coral/10 p-2 text-xs text-coral-600 dark:text-coral-300">
{t('settings.agentAccess.fullWarning')}
</p>
)}
Expand All @@ -253,10 +263,10 @@ const AgentAccessPanel = () => {
onChange={e => toggleWorkspaceOnly(e.target.checked)}
/>
<span>
<span className="text-sm font-medium text-ink">
<span className="text-sm font-medium text-stone-900 dark:text-neutral-100">
{t('settings.agentAccess.confine.label')}
</span>
<span className="block text-xs text-ink-soft">
<span className="block text-xs text-stone-600 dark:text-neutral-400">
{t('settings.agentAccess.confine.desc')}
</span>
</span>
Expand All @@ -272,10 +282,10 @@ const AgentAccessPanel = () => {
onChange={e => toggleTaskPlanApproval(e.target.checked)}
/>
<span>
<span className="text-sm font-medium text-ink">
<span className="text-sm font-medium text-stone-900 dark:text-neutral-100">
{t('settings.agentAccess.requireTaskPlanApproval.label')}
</span>
<span className="block text-xs text-ink-soft">
<span className="block text-xs text-stone-600 dark:text-neutral-400">
{t('settings.agentAccess.requireTaskPlanApproval.desc')}
</span>
</span>
Expand All @@ -284,29 +294,35 @@ const AgentAccessPanel = () => {

{/* Granted folders (trusted roots) — extra read/write reach. */}
<section className="space-y-2">
<h2 className="text-sm font-semibold text-ink">
<h2 className="text-sm font-semibold text-stone-900 dark:text-neutral-100">
{t('settings.agentAccess.grantedFolders')}
</h2>
<p className="text-xs text-ink-soft">{t('settings.agentAccess.grantedDesc')}</p>
<p className="text-xs text-stone-600 dark:text-neutral-400">
{t('settings.agentAccess.grantedDesc')}
</p>
{trustedRoots.length === 0 ? (
<p className="text-xs text-ink-soft">{t('settings.agentAccess.noneGranted')}</p>
<p className="text-xs text-stone-600 dark:text-neutral-400">
{t('settings.agentAccess.noneGranted')}
</p>
) : (
<ul className="space-y-1">
{trustedRoots.map(r => (
<li
key={r.path}
className="flex items-center justify-between rounded border border-line px-2 py-1">
<span className="font-mono text-xs text-ink truncate">{r.path}</span>
className="flex items-center justify-between rounded border border-stone-200 dark:border-neutral-800 px-2 py-1">
<span className="font-mono text-xs text-stone-900 dark:text-neutral-100 truncate">
{r.path}
</span>
<span className="flex items-center gap-2">
<span className="text-xs text-ink-soft">
<span className="text-xs text-stone-600 dark:text-neutral-400">
{r.access === 'readwrite'
? t('settings.agentAccess.readWrite')
: t('settings.agentAccess.readOnly')}
</span>
<button
type="button"
onClick={() => removeRoot(r.path)}
className="text-xs text-coral hover:underline">
className="text-xs text-coral-600 dark:text-coral-300 hover:underline">
{t('settings.agentAccess.remove')}
</button>
</span>
Expand All @@ -321,13 +337,13 @@ const AgentAccessPanel = () => {
onChange={e => setNewRootPath(e.target.value)}
placeholder={t('settings.agentAccess.pathPlaceholder')}
aria-label={t('settings.agentAccess.pathPlaceholder')}
className="flex-1 rounded border border-line px-2 py-1 text-xs font-mono"
className="flex-1 rounded border border-stone-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 text-stone-900 dark:text-neutral-100 px-2 py-1 text-xs font-mono"
/>
<select
value={newRootAccess}
onChange={e => setNewRootAccess(e.target.value as TrustedAccess)}
aria-label={t('settings.agentAccess.accessLevelLabel')}
className="rounded border border-line px-2 py-1 text-xs">
className="rounded border border-stone-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 text-stone-900 dark:text-neutral-100 px-2 py-1 text-xs">
<option value="read">{t('settings.agentAccess.readOnly')}</option>
<option value="readwrite">{t('settings.agentAccess.readWrite')}</option>
</select>
Expand All @@ -344,23 +360,29 @@ const AgentAccessPanel = () => {
prompted for, via the in-chat approval card. Read-only here with
a Remove action to re-enable prompting for a tool. */}
<section className="space-y-2">
<h2 className="text-sm font-semibold text-ink">
<h2 className="text-sm font-semibold text-stone-900 dark:text-neutral-100">
{t('settings.agentAccess.alwaysAllow')}
</h2>
<p className="text-xs text-ink-soft">{t('settings.agentAccess.alwaysAllowDesc')}</p>
<p className="text-xs text-stone-600 dark:text-neutral-400">
{t('settings.agentAccess.alwaysAllowDesc')}
</p>
{autoApprove.length === 0 ? (
<p className="text-xs text-ink-soft">{t('settings.agentAccess.alwaysAllowNone')}</p>
<p className="text-xs text-stone-600 dark:text-neutral-400">
{t('settings.agentAccess.alwaysAllowNone')}
</p>
) : (
<ul className="space-y-1">
{autoApprove.map(tool => (
<li
key={tool}
className="flex items-center justify-between rounded border border-line px-2 py-1">
<span className="font-mono text-xs text-ink truncate">{tool}</span>
className="flex items-center justify-between rounded border border-stone-200 dark:border-neutral-800 px-2 py-1">
<span className="font-mono text-xs text-stone-900 dark:text-neutral-100 truncate">
{tool}
</span>
<button
type="button"
onClick={() => removeAutoApprove(tool)}
className="text-xs text-coral hover:underline">
className="text-xs text-coral-600 dark:text-coral-300 hover:underline">
{t('settings.agentAccess.remove')}
</button>
</li>
Expand All @@ -372,13 +394,17 @@ const AgentAccessPanel = () => {
{/* Auto-save status — changes persist on selection; no manual save. */}
<div className="min-h-[1.25rem] text-sm" aria-live="polite">
{error ? (
<span className="text-coral">{error}</span>
<span className="text-coral-600 dark:text-coral-300">{error}</span>
) : isSaving ? (
<span className="text-ink-soft">{t('settings.agentAccess.saving')}</span>
<span className="text-stone-600 dark:text-neutral-400">
{t('settings.agentAccess.saving')}
</span>
) : savedNote ? (
<span className="text-sage">✓ {savedNote}</span>
<span className="text-sage-700 dark:text-sage-300">✓ {savedNote}</span>
) : (
<span className="text-ink-soft">{t('settings.agentAccess.changesApply')}</span>
<span className="text-stone-600 dark:text-neutral-400">
{t('settings.agentAccess.changesApply')}
</span>
)}
</div>
</>
Expand Down
Loading
Loading