diff --git a/frontend/public/onboarding-videos/v2/02.mp4 b/frontend/public/onboarding-videos/v2/02.mp4
deleted file mode 100644
index 36a79e31..00000000
Binary files a/frontend/public/onboarding-videos/v2/02.mp4 and /dev/null differ
diff --git a/frontend/public/onboarding-videos/v2/07.mp4 b/frontend/public/onboarding-videos/v2/07.mp4
deleted file mode 100644
index fdadb924..00000000
Binary files a/frontend/public/onboarding-videos/v2/07.mp4 and /dev/null differ
diff --git a/frontend/src/app/Main.tsx b/frontend/src/app/Main.tsx
index 2016bac8..77f273be 100644
--- a/frontend/src/app/Main.tsx
+++ b/frontend/src/app/Main.tsx
@@ -27,10 +27,6 @@ import DashboardSelection from './pages/DashboardSelection/DashboardSelection';
import ErrorBoundary from './components/feedback/ErrorBoundary';
import { setPanelMode, disableOnboardingAfterCrash } from '@/shared/state/onboardingProgressSlice';
-const Skills = React.lazy(() => import('./pages/Skills/Skills'));
-const Tools = React.lazy(() => import('./pages/Tools/Tools'));
-const Modes = React.lazy(() => import('./pages/Modes/Modes'));
-const Customization = React.lazy(() => import('./pages/Customization/Customization'));
const Analytics = React.lazy(() => import('./pages/Analytics/Analytics'));
const OnboardingRoot = React.lazy(() =>
import('./components/Onboarding').then((m) => ({ default: m.OnboardingRoot })),
@@ -54,20 +50,11 @@ if (typeof window !== 'undefined') {
(window as any).__openswarmPrefetchRoute = (path: string) => {
switch (path) {
- case '/skills': void import('./pages/Skills/Skills'); return;
- case '/actions':
- case '/tools': void import('./pages/Tools/Tools'); return;
- case '/modes': void import('./pages/Modes/Modes'); return;
case '/views':
- case '/customization': void import('./pages/Customization/Customization'); return;
case '/analytics': void import('./pages/Analytics/Analytics'); return;
}
};
const prefetchAll = () => {
- void import('./pages/Skills/Skills');
- void import('./pages/Tools/Tools');
- void import('./pages/Modes/Modes');
- void import('./pages/Customization/Customization');
void import('./pages/Analytics/Analytics');
};
const ric = (window as any).requestIdleCallback as
@@ -528,10 +515,6 @@ const ThemedApp: React.FC = () => {
} />
{/* Dashboard renders persistently in AppShell so webviews survive nav. */}
- } />
- } />
- } />
- } />
} />
diff --git a/frontend/src/app/components/Layout/AppShell.tsx b/frontend/src/app/components/Layout/AppShell.tsx
index f20d7be9..2b47615c 100644
--- a/frontend/src/app/components/Layout/AppShell.tsx
+++ b/frontend/src/app/components/Layout/AppShell.tsx
@@ -18,12 +18,9 @@ import Alert from '@mui/material/Alert';
import InputBase from '@mui/material/InputBase';
// One outlined icon language for the sidebar: thin monoline glyphs (not the filled Material clip-art) so the rail reads as designed, not assembled.
import { LayoutDashboard } from 'lucide-react';
-import PsychologyIcon from '@mui/icons-material/PsychologyOutlined';
-import BuildIcon from '@mui/icons-material/BuildOutlined';
import { LayoutGrid } from 'lucide-react';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { Settings as LucideSettings } from 'lucide-react';
-import { Palette } from 'lucide-react';
import { ArrowLeft, ArrowRight, Plus, Clock } from 'lucide-react';
import { AnimatedPanelLeft } from './animatedIcons';
import RestartAltIcon from '@mui/icons-material/RestartAlt';
@@ -59,13 +56,6 @@ const SIDEBAR_DEFAULT = 260;
const SIDEBAR_WIDTH_KEY = 'openswarm-sidebar-width';
const UPDATE_DISMISS_KEY = 'openswarm-update-dismissed';
-const CUSTOMIZATION_ITEMS = [
- { label: 'Skills', path: '/skills', icon: , onboarding: 'sidebar-skills' },
- { label: 'Actions', path: '/actions', icon: , onboarding: 'sidebar-actions' },
-];
-
-const CUSTOMIZATION_PATHS = new Set(CUSTOMIZATION_ITEMS.map((i) => i.path));
-
const AppShell: React.FC = () => {
const c = useClaudeTokens();
const dispatch = useAppDispatch();
@@ -88,8 +78,6 @@ const AppShell: React.FC = () => {
const canGoForward = historyIdx < maxHistoryIdx.current;
const [dashboardsExpanded, setDashboardsExpanded] = useState(true);
const [appsExpanded, setAppsExpanded] = useState(true);
- // Collapsed by default: config rows are progressive disclosure, not daily nav. Onboarding reads data-expanded and clicks to open when it needs them.
- const [customizationExpanded, setCustomizationExpanded] = useState(false);
// Starts collapsed so a fresh boot lands on a clean canvas; the toggle brings it back.
const [sidebarCollapsed, setSidebarCollapsed] = useState(true);
const [renamingDashboardId, setRenamingDashboardId] = useState(null);
@@ -433,7 +421,6 @@ const AppShell: React.FC = () => {
const isDashboardRoute = location.pathname === '/' || location.pathname.startsWith('/dashboard/');
const isDashboardViewActive = location.pathname.startsWith('/dashboard/');
const isAppsRoute = false; // /apps route removed; app cards live on the dashboard now.
- const isCustomizationRoute = location.pathname === '/customization' || CUSTOMIZATION_PATHS.has(location.pathname);
const activeDashboardId = location.pathname.startsWith('/dashboard/')
? location.pathname.split('/dashboard/')[1]
: null;
@@ -819,9 +806,6 @@ const AppShell: React.FC = () => {
'& [data-onboarding="sidebar-dashboards"]:hover .MuiListItemIcon-root svg': {
transform: 'scale(1.14)',
},
- '& [data-onboarding="sidebar-customization"]:hover .MuiListItemIcon-root svg': {
- transform: 'rotate(-14deg) scale(1.06)',
- },
'& [data-onboarding="sidebar-apps"]:hover .MuiListItemIcon-root svg': {
transform: 'rotate(8deg) scale(1.08)',
},
@@ -982,106 +966,6 @@ const AppShell: React.FC = () => {
{/* Sections separate with air, not lines. */}
-
- {
- if (isCustomizationRoute) {
- setCustomizationExpanded((prev) => !prev);
- } else {
- navigate('/customization');
- setCustomizationExpanded(true);
- }
- }}
- data-onboarding="sidebar-customization"
- data-expanded={customizationExpanded ? 'true' : 'false'}
- aria-expanded={customizationExpanded}
- sx={{
- borderRadius: 1.5,
- py: 0.6,
- px: 1.25,
- bgcolor: isCustomizationRoute ? `${c.accent.primary}12` : 'transparent',
- '&:hover': { bgcolor: isCustomizationRoute ? `${c.accent.primary}18` : `${c.text.tertiary}0A` },
- transition: 'background-color 0.15s',
- }}
- >
-
-
-
-
-
-
-
-
-
- {CUSTOMIZATION_ITEMS.map((item) => {
- // Manual click handler instead of NavLink: NavLink's internal navigate bypasses our startTransition wrapper.
- const isActive = location.pathname === item.path;
- return (
- navigate(item.path)}
- onMouseEnter={() => {
- // Hover-prefetch lazy chunk so click is ~0ms (see Main.tsx for path -> import map).
- const fn = (window as any).__openswarmPrefetchRoute;
- if (typeof fn === 'function') fn(item.path);
- }}
- sx={{
- display: 'flex',
- alignItems: 'center',
- gap: 0.75,
- pl: 1.25,
- pr: 1,
- py: 0.5,
- mx: 0.5,
- cursor: 'pointer',
- // 25% accent alpha needed for readable contrast on dark-mode bg.secondary; 10% muddied to grey.
- borderRadius: `${c.radius.md}px`,
- bgcolor: isActive ? `${c.accent.primary}40` : 'transparent',
- '&:hover': { bgcolor: isActive ? `${c.accent.primary}55` : `${c.text.tertiary}0A` },
- transition: 'background-color 0.12s',
- }}
- >
-
- {item.label}
-
-
- );
- })}
-
-
-
-
- {/* Sections separate with air, not lines. */}
-
-
{
const lastShowMeClickRef = useRef(0);
const unlockedIds = useUnlockedStepIds();
+ const liveStepIds = useMemo(() => new Set(STEPS.map((s) => s.id)), []);
const currentStep = useMemo(() => {
// Spotlight only lands on an unlocked, not-yet-done step, so we never tell the user to "Show me" something they haven't unlocked yet.
const explicit = progress.currentStepId
@@ -87,8 +88,11 @@ const OnboardingPanel: React.FC = () => {
const stageOf = currentStep?.stage ?? 'get_started';
// Count only what's UNLOCKED, not all 8. A brand-new user sees "0/2" (launch + connect), and the denominator grows as the first win unlocks the rest, so we never dump the whole feature surface on someone before their first output. Guard: never let completed exceed the shown total (data-weirdness safety).
- const done = progress.completedSteps.length;
- const total = Math.max(unlockedIds.size, done);
+ const done = progress.completedSteps.filter((id) => liveStepIds.has(id)).length;
+ const total = Math.max(
+ Array.from(unlockedIds).filter((id) => liveStepIds.has(id)).length,
+ done,
+ );
// Timer lives inside CelebrationView so parent re-renders can't cancel it.
const justDoneStepId = progress.justCompletedStepId;
diff --git a/frontend/src/app/components/Onboarding/OnboardingRoadmapModal.tsx b/frontend/src/app/components/Onboarding/OnboardingRoadmapModal.tsx
index ad4022fa..1c52a8d4 100644
--- a/frontend/src/app/components/Onboarding/OnboardingRoadmapModal.tsx
+++ b/frontend/src/app/components/Onboarding/OnboardingRoadmapModal.tsx
@@ -32,7 +32,8 @@ const OnboardingRoadmapModal: React.FC = () => {
return STEPS.find((s) => !progress.completedSteps.includes(s.id) && unlockedIds.has(s.id));
})();
- const totalDone = progress.completedSteps.length;
+ // Filter to live steps so a user who finished a since-removed step can't read e.g. 8/6.
+ const totalDone = progress.completedSteps.filter((id) => findStepById(id)).length;
const total = STEPS.length;
const jumpToCurrent = () => {
diff --git a/frontend/src/app/components/Onboarding/OnboardingRoot.tsx b/frontend/src/app/components/Onboarding/OnboardingRoot.tsx
index 3372749d..5c211e3f 100644
--- a/frontend/src/app/components/Onboarding/OnboardingRoot.tsx
+++ b/frontend/src/app/components/Onboarding/OnboardingRoot.tsx
@@ -13,6 +13,7 @@ import {
persistToStorage,
markStepCompleted,
setPanelMode,
+ setCurrentStep,
markRevealedAfterWin,
} from '@/shared/state/onboardingProgressSlice';
import AgenticCursor, { type AgenticCursorHandle } from './ac/AgenticCursor';
@@ -98,6 +99,13 @@ const OnboardingRoot: React.FC = () => {
);
}, [progress.initialized, settingsLoaded, dispatch, store]);
+ useEffect(() => {
+ if (!progress.initialized || !progress.currentStepId) return;
+ if (STEPS.some((step) => step.id === progress.currentStepId)) return;
+ const nextStep = STEPS.find((step) => !(progress.completedSteps ?? []).includes(step.id));
+ dispatch(setCurrentStep(nextStep?.id ?? null));
+ }, [progress.initialized, progress.currentStepId, progress.completedSteps, dispatch]);
+
// Bridge Redux signals to bus + auto-mark on skipIf. Coalesces microtask-bursts of dispatches.
useEffect(() => {
let last = new Set(progress.completedSteps);
diff --git a/frontend/src/app/components/Onboarding/ac/acRuntime.ts b/frontend/src/app/components/Onboarding/ac/acRuntime.ts
index 7183627f..fc536586 100644
--- a/frontend/src/app/components/Onboarding/ac/acRuntime.ts
+++ b/frontend/src/app/components/Onboarding/ac/acRuntime.ts
@@ -311,15 +311,11 @@ async function runOp(op: ACOp, ctx: RunContext): Promise {
switch (op.kind) {
case 'move_to': {
- // Order matters: open the whole sidebar first (sub-section markers must exist in DOM), THEN expand Customization, THEN target.
+ // Open the whole sidebar first so its markers exist in the DOM before we target one.
const expandSidebarOps = maybeBuildExpandSidebarOps(op.target);
if (expandSidebarOps) {
await runOps(expandSidebarOps, ctx);
}
- const expandOps = maybeBuildExpandCustomizationOps(op.target);
- if (expandOps) {
- await runOps(expandOps, ctx);
- }
const el = await waitForSelector(op.target);
const scrolled = scrollIntoViewIfNeeded(el);
const offX = op.offset?.x ?? 0;
@@ -755,25 +751,15 @@ function buildOpenDashboardOps(): ACOp[] {
return ops;
}
-const CUSTOMIZATION_AREA_TARGETS = new Set([
- 'sidebar-actions',
- 'sidebar-skills',
- 'sidebar-modes',
-]);
-
// `sidebar-toggle` excluded: it lives in the top bar (we click it to expand). Recursing would loop.
const SIDEBAR_AREA_TARGETS = new Set([
'sidebar-settings-button',
'sidebar-dashboards',
- 'sidebar-customization',
- 'sidebar-skills',
- 'sidebar-actions',
- 'sidebar-modes',
'sidebar-apps',
'dashboard-row-first',
]);
-/** MUST run before maybeBuildExpandCustomizationOps: Customization header is inside the collapsible panel, so expand-check on hidden panel queues an impossible click. */
+/** Expands the collapsed sidebar so its row markers exist before a move_to targets one. */
function maybeBuildExpandSidebarOps(target: string): ACOp[] | null {
if (!SIDEBAR_AREA_TARGETS.has(target)) return null;
const toggle = document.querySelector(
@@ -789,26 +775,6 @@ function maybeBuildExpandSidebarOps(target: string): ACOp[] | null {
];
}
-function maybeBuildExpandCustomizationOps(target: string): ACOp[] | null {
- if (!CUSTOMIZATION_AREA_TARGETS.has(target)) return null;
- const header = document.querySelector(
- '[data-onboarding="sidebar-customization"]',
- );
- const expanded =
- header?.dataset.expanded === 'true' ||
- header?.getAttribute('aria-expanded') === 'true';
- if (expanded) return null;
- return [
- { kind: 'move_to', target: 'sidebar-customization' },
- { kind: 'popup', text: 'Open Customization.' },
- {
- kind: 'wait_user',
- condition: { kind: 'click_target', target: 'sidebar-customization' },
- timeoutMs: 60000,
- },
- ];
-}
-
interface WaitResult {
timedOut: boolean;
}
diff --git a/frontend/src/app/components/Onboarding/selectors.ts b/frontend/src/app/components/Onboarding/selectors.ts
index 1d9c9935..f74a2240 100644
--- a/frontend/src/app/components/Onboarding/selectors.ts
+++ b/frontend/src/app/components/Onboarding/selectors.ts
@@ -1,9 +1,6 @@
// Central registry of data-onboarding / data-select-type selectors. Step files import S.*; never inline.
export const S = {
- sidebarSkills: 'sidebar-skills',
- sidebarActions: 'sidebar-actions',
- sidebarModes: 'sidebar-modes',
sidebarApps: 'sidebar-apps',
sidebarSettingsButton: 'sidebar-settings-button',
@@ -35,22 +32,9 @@ export const S = {
chatSendButton: 'chat-send-button',
elementSelectionToggle: 'element-selection-toggle',
- actionsRedditToggle: 'actions-reddit-toggle',
- actionsRedditChevron: 'actions-reddit-chevron',
- actionsSubredditsChevron: 'actions-subreddits-chevron',
- actionsPermissionToggle: 'actions-permission-toggle',
- actionsYoutubeToggle: 'actions-youtube-toggle',
- actionsYoutubeChevron: 'actions-youtube-chevron',
-
canvasFitToView: 'canvas-fit-to-view',
canvasTidyLayout: 'canvas-tidy-layout',
canvasMinimapToggle: 'canvas-minimap-toggle',
- /** Header for sidebar's Customization section; runtime auto-expands before targeting children. */
- sidebarCustomization: 'sidebar-customization',
-
- skillItemPdf: 'skill-item-pdf',
- skillInstallButton: 'skill-install-button',
- skillBuilderFab: 'skill-builder-fab',
appsNewButton: 'apps-new-button',
appCardLatest: 'app-card-latest',
diff --git a/frontend/src/app/components/Onboarding/steps/index.ts b/frontend/src/app/components/Onboarding/steps/index.ts
index 5b218938..3b3e0bbf 100644
--- a/frontend/src/app/components/Onboarding/steps/index.ts
+++ b/frontend/src/app/components/Onboarding/steps/index.ts
@@ -1,11 +1,9 @@
import type { OnboardingStep, StepStage } from './types';
import { step01 } from './step01_connectModel';
-import { step02 } from './step02_enableActions';
import { step03 } from './step03_launchAgent';
import { step04 } from './step04_useBrowser';
import { step05 } from './step05_agentUseBrowser';
import { step06 } from './step06_agentControlAgents';
-import { step07 } from './step07_installSkill';
import { step08 } from './step08_makeApp';
import { welcomeOpenStep } from './step00_welcomeNudge';
@@ -13,11 +11,9 @@ import { welcomeOpenStep } from './step00_welcomeNudge';
export const STEPS: OnboardingStep[] = [
step03,
step01,
- step02,
step04,
step05,
step06,
- step07,
step08,
];
diff --git a/frontend/src/app/components/Onboarding/steps/skipPredicates.ts b/frontend/src/app/components/Onboarding/steps/skipPredicates.ts
index 73082e7d..781398d1 100644
--- a/frontend/src/app/components/Onboarding/steps/skipPredicates.ts
+++ b/frontend/src/app/components/Onboarding/steps/skipPredicates.ts
@@ -76,18 +76,6 @@ export function hasAnySkillInstalled(s: RootState): boolean {
return Object.keys(items).length > 0;
}
-/** True if PDF skill installed (id/name/command); step 7 uses this so other skills don't auto-skip. */
-export function hasPdfSkillInstalled(s: RootState): boolean {
- const items = s.skills?.items as any;
- const list: any[] = Array.isArray(items) ? items : Object.values(items ?? {});
- return list.some((sk: any) => {
- const id = (sk?.id ?? '').toString().toLowerCase();
- const name = (sk?.name ?? '').toString().toLowerCase();
- const cmd = (sk?.command ?? '').toString().toLowerCase();
- return id.includes('pdf') || name.includes('pdf') || cmd.includes('pdf');
- });
-}
-
/** True if a browser card exists; step 4 auto-skips the open-a-browser walkthrough. */
export function hasAnyBrowserSpawned(s: RootState): boolean {
const cards = (s as any).dashboardLayout?.browserCards ?? {};
diff --git a/frontend/src/app/components/Onboarding/steps/step02_enableActions.ts b/frontend/src/app/components/Onboarding/steps/step02_enableActions.ts
deleted file mode 100644
index dc220549..00000000
--- a/frontend/src/app/components/Onboarding/steps/step02_enableActions.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import type { OnboardingStep } from './types';
-import { S } from '../selectors';
-import { isYoutubeEnabled } from './skipPredicates';
-
-export const step02: OnboardingStep = {
- id: 'enable_actions',
- // Demoted out of the first-run path: a feature to discover after the first win.
- stage: 'learn_features',
- index: 3,
- title: 'Enable agentic actions',
- description: 'Allow agents to work across your apps.',
- videoSrc: './onboarding-videos/v2/02.mp4',
- videoDurationLabel: '0:24',
- // Narrowed to YouTube so users with other tools still get walked.
- skipIf: isYoutubeEnabled,
- // Two beats only (open Actions, flip YouTube on); the chevron-peek and permission fine-tune popups were trimmed to give the step room to breathe.
- ops: [
- { kind: 'move_to', target: S.sidebarActions },
- { kind: 'popup', text: 'Open Actions.' },
- {
- kind: 'wait_user',
- condition: { kind: 'click_target', target: S.sidebarActions },
- },
- // YouTube on the throughline; step 3 needs it. Waits on Redux state, not click, so toggling stays synced.
- { kind: 'move_to', target: S.actionsYoutubeToggle },
- { kind: 'popup', text: 'Flip YouTube on.' },
- {
- kind: 'wait_user',
- condition: {
- kind: 'redux_predicate',
- selector: isYoutubeEnabled,
- truthy: true,
- },
- timeoutMs: 90000,
- },
- { kind: 'delay', ms: 1200 },
- { kind: 'outro' },
- ],
-};
diff --git a/frontend/src/app/components/Onboarding/steps/step07_installSkill.ts b/frontend/src/app/components/Onboarding/steps/step07_installSkill.ts
deleted file mode 100644
index a3cd61a8..00000000
--- a/frontend/src/app/components/Onboarding/steps/step07_installSkill.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import type { OnboardingStep } from './types';
-import { S } from '../selectors';
-import { hasPdfSkillInstalled } from './skipPredicates';
-
-export const step07: OnboardingStep = {
- id: 'install_skill',
- stage: 'learn_features',
- index: 7,
- title: 'Install a skill',
- description: 'Teach agents how to handle specific tasks.',
- videoSrc: './onboarding-videos/v2/07.mp4',
- videoDurationLabel: '0:24',
- // Narrowed to PDF so other-skill users still walk through this demo.
- skipIf: hasPdfSkillInstalled,
- ops: [
- { kind: 'move_to', target: S.sidebarSkills },
- { kind: 'popup', text: 'Wander into Skills.' },
- {
- kind: 'wait_user',
- condition: { kind: 'click_target', target: S.sidebarSkills },
- },
- { kind: 'move_to', target: S.skillItemPdf },
- { kind: 'popup', text: 'Pick the PDF one.' },
- {
- kind: 'wait_user',
- condition: { kind: 'click_target', target: S.skillItemPdf },
- },
- { kind: 'move_to', target: S.skillInstallButton },
- { kind: 'popup', text: 'Install it!' },
- {
- kind: 'wait_user',
- condition: { kind: 'event_bus', event: 'skill:installed' },
- timeoutMs: 60000,
- },
- {
- kind: 'popup',
- text: 'Boom! Now any chat is way better with PDFs.',
- },
- { kind: 'move_to', target: S.skillBuilderFab },
- { kind: 'click', target: S.skillBuilderFab, simulate: true },
- {
- kind: 'popup',
- text: 'Got an idea? Type it here and the skill builder whips one up.',
- },
- { kind: 'delay', ms: 3500 },
- { kind: 'outro' },
- ],
-};
diff --git a/frontend/src/app/components/overlays/GlobalSearchPalette.tsx b/frontend/src/app/components/overlays/GlobalSearchPalette.tsx
index 2c86b398..e792e4c9 100644
--- a/frontend/src/app/components/overlays/GlobalSearchPalette.tsx
+++ b/frontend/src/app/components/overlays/GlobalSearchPalette.tsx
@@ -53,7 +53,6 @@ const ACTIONS: ActionResult[] = [
{ kind: 'action', id: 'settings-models', name: 'Connect a model', keywords: 'settings models api key provider subscription' },
{ kind: 'action', id: 'go-skills', name: 'Go to Skills', keywords: 'customize skills' },
{ kind: 'action', id: 'go-actions', name: 'Go to Actions', keywords: 'customize tools actions mcp' },
- { kind: 'action', id: 'go-modes', name: 'Go to Modes', keywords: 'customize modes' },
{ kind: 'action', id: 'all-dashboards', name: 'All dashboards', keywords: 'overview picker browse boards' },
];
@@ -149,9 +148,9 @@ const GlobalSearchPalette: React.FC = ({ open, onClose }) => {
break;
case 'settings': dispatch(openSettingsModal()); break;
case 'settings-models': dispatch(openSettingsModal('models')); break;
- case 'go-skills': navigate('/skills'); break;
- case 'go-actions': navigate('/actions'); break;
- case 'go-modes': navigate('/modes'); break;
+ // Skills/Actions live in Settings now (the sidebar Customization section moved there).
+ case 'go-skills': dispatch(openSettingsModal('skills')); break;
+ case 'go-actions': dispatch(openSettingsModal('tools')); break;
case 'all-dashboards': navigate('/'); break;
}
}, [dispatch, navigate]);
diff --git a/frontend/src/app/pages/AgentChat/AgentChat.tsx b/frontend/src/app/pages/AgentChat/AgentChat.tsx
index f5378152..ec28d865 100644
--- a/frontend/src/app/pages/AgentChat/AgentChat.tsx
+++ b/frontend/src/app/pages/AgentChat/AgentChat.tsx
@@ -1,5 +1,5 @@
import React, { useEffect, useRef, useMemo, useState, useCallback } from 'react';
-import { useNavigate, useParams } from 'react-router-dom';
+import { useParams } from 'react-router-dom';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton';
@@ -279,7 +279,6 @@ const AgentChat: React.FC = ({ sessionId: sessionIdProp, onClose
);
});
const testState = useAppSelector((s) => (id ? s.agents.sessions[id]?.workflow_test_state : null) ?? null);
- const navigate = useNavigate();
const dispatch = useAppDispatch();
const session = useAppSelector((state) => (id ? state.agents.sessions[id] : undefined));
const modesMap = useAppSelector((state) => state.modes.items);
@@ -1721,7 +1720,7 @@ const AgentChat: React.FC = ({ sessionId: sessionIdProp, onClose
setActivateError(`Activation failed (${r.status})`);
} else if (body?.status === 'unknown_server') {
// Not yet connected; jump to Actions so the user can finish OAuth.
- navigate('/actions');
+ dispatch(openSettingsModal('tools'));
} else if (id) {
dispatch(clearMcpSuggestions({ sessionId: id }));
}
@@ -2198,7 +2197,7 @@ const AgentChat: React.FC = ({ sessionId: sessionIdProp, onClose
setActivateError(`Activation failed (${r.status})`);
} else if (body?.status === 'unknown_server') {
// Not yet connected; jump straight to Actions so the user can finish OAuth. Nothing here can do it on their behalf.
- navigate('/actions');
+ dispatch(openSettingsModal('tools'));
} else if (id) {
// Activation succeeded; clear the banner so the user gets visual confirmation the click did something.
dispatch(clearMcpSuggestions({ sessionId: id }));
diff --git a/frontend/src/app/pages/Customization/Customization.tsx b/frontend/src/app/pages/Customization/Customization.tsx
deleted file mode 100644
index 3075da07..00000000
--- a/frontend/src/app/pages/Customization/Customization.tsx
+++ /dev/null
@@ -1,105 +0,0 @@
-import React from 'react';
-import { useNavigate } from 'react-router-dom';
-import Box from '@mui/material/Box';
-import Typography from '@mui/material/Typography';
-import Card from '@mui/material/Card';
-import CardActionArea from '@mui/material/CardActionArea';
-import PsychologyIcon from '@mui/icons-material/Psychology';
-import BuildIcon from '@mui/icons-material/Build';
-import TuneIcon from '@mui/icons-material/Tune';
-import { useClaudeTokens } from '@/shared/styles/ThemeContext';
-
-const PANELS = [
- {
- label: 'Skills',
- path: '/skills',
- icon: ,
- description:
- 'Install or author reusable skill packages that teach your agents new capabilities and workflows.',
- },
- {
- label: 'Actions',
- path: '/actions',
- icon: ,
- description:
- 'Define and manage the actions your agents can take.',
- },
- {
- label: 'Modes',
- path: '/modes',
- icon: ,
- description:
- 'Configure agent interaction modes with custom system prompts, allowed actions, and auto-switching rules.',
- },
-];
-
-const Customization: React.FC = () => {
- const c = useClaudeTokens();
- const navigate = useNavigate();
-
- return (
-
-
-
-
- Customization
-
-
- Tailor how your agents behave, what they can do, and how they interact.
-
-
-
-
- {PANELS.map((panel) => (
-
- navigate(panel.path)}
- sx={{ p: 3, display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 1.5 }}
- >
-
- {React.cloneElement(panel.icon, { sx: { fontSize: 24 } })}
-
-
- {panel.label}
-
-
- {panel.description}
-
-
-
- ))}
-
-
-
- );
-};
-
-export default Customization;
diff --git a/frontend/src/app/pages/Modes/Modes.tsx b/frontend/src/app/pages/Modes/Modes.tsx
deleted file mode 100644
index 55fadb9c..00000000
--- a/frontend/src/app/pages/Modes/Modes.tsx
+++ /dev/null
@@ -1,604 +0,0 @@
-import React, { useEffect, useState, useMemo } from 'react';
-import Box from '@mui/material/Box';
-import Typography from '@mui/material/Typography';
-import Button from '@mui/material/Button';
-import Card from '@mui/material/Card';
-import CardContent from '@mui/material/CardContent';
-import CardActions from '@mui/material/CardActions';
-import Dialog from '@mui/material/Dialog';
-import DialogTitle from '@mui/material/DialogTitle';
-import DialogContent from '@mui/material/DialogContent';
-import DialogActions from '@mui/material/DialogActions';
-import TextField from '@mui/material/TextField';
-import IconButton from '@mui/material/IconButton';
-import Chip from '@mui/material/Chip';
-import CircularProgress from '@mui/material/CircularProgress';
-import Tooltip from '@mui/material/Tooltip';
-import { Skeleton } from '@/app/components/feedback/Loading';
-import FormControl from '@mui/material/FormControl';
-import InputLabel from '@mui/material/InputLabel';
-import Select from '@mui/material/Select';
-import MenuItem from '@mui/material/MenuItem';
-import Checkbox from '@mui/material/Checkbox';
-import ListItemText from '@mui/material/ListItemText';
-import OutlinedInput from '@mui/material/OutlinedInput';
-import AddIcon from '@mui/icons-material/Add';
-import EditIcon from '@mui/icons-material/Edit';
-import DeleteIcon from '@mui/icons-material/Delete';
-import TuneIcon from '@mui/icons-material/Tune';
-import LockIcon from '@mui/icons-material/Lock';
-import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
-import RestoreIcon from '@mui/icons-material/Restore';
-import SmartToyOutlinedIcon from '@mui/icons-material/SmartToyOutlined';
-import QuestionAnswerOutlinedIcon from '@mui/icons-material/QuestionAnswerOutlined';
-import MapOutlinedIcon from '@mui/icons-material/MapOutlined';
-import CategoryOutlinedIcon from '@mui/icons-material/CategoryOutlined';
-import { useAppDispatch, useAppSelector } from '@/shared/hooks';
-import {
- fetchModes,
- createMode,
- updateMode,
- deleteMode,
- resetMode,
- Mode,
-} from '@/shared/state/modesSlice';
-import { fetchBuiltinTools, fetchTools } from '@/shared/state/toolsSlice';
-import { fetchSkills } from '@/shared/state/skillsSlice';
-import FolderOpenIcon from '@mui/icons-material/FolderOpen';
-import ExtensionIcon from '@mui/icons-material/Extension';
-import ListSubheader from '@mui/material/ListSubheader';
-import { useClaudeTokens } from '@/shared/styles/ThemeContext';
-import DirectoryBrowser from '@/app/components/editor/DirectoryBrowser';
-import RichPromptEditor from '@/app/components/editor/RichPromptEditor';
-
-const ICON_MAP: Record = {
- smart_toy: ,
- question_answer: ,
- map: ,
- category: ,
- tune: ,
-};
-
-const ICON_OPTIONS = [
- { value: 'smart_toy', label: 'Robot' },
- { value: 'question_answer', label: 'Q&A' },
- { value: 'map', label: 'Map' },
- { value: 'category', label: 'Category' },
- { value: 'tune', label: 'Tune' },
-];
-
-const COLOR_OPTIONS = [
- { value: '#ae5630', label: 'Terra Cotta' },
- { value: '#4ade80', label: 'Green' },
- { value: '#fbbf24', label: 'Amber' },
- { value: '#f87171', label: 'Red' },
- { value: '#38bdf8', label: 'Sky' },
- { value: '#c084fc', label: 'Purple' },
- { value: '#fb923c', label: 'Orange' },
- { value: '#2dd4bf', label: 'Teal' },
-];
-
-interface ModeForm {
- name: string;
- description: string;
- system_prompt: string;
- tools: string[];
- toolsEnabled: boolean;
- default_next_mode: string;
- icon: string;
- color: string;
- default_folder: string;
-}
-
-const emptyForm: ModeForm = {
- name: '',
- description: '',
- system_prompt: '',
- tools: [],
- toolsEnabled: false,
- default_next_mode: '',
- icon: 'smart_toy',
- color: '#ae5630',
- default_folder: '',
-};
-
-const ALL_BUILTIN_TOOL_NAMES = ['Read', 'Edit', 'Write', 'Bash', 'Glob', 'Grep', 'AskUserQuestion'];
-
-const Modes: React.FC = () => {
- const c = useClaudeTokens();
- const dispatch = useAppDispatch();
- const { items, builtinDefaults, loading } = useAppSelector((s) => s.modes);
- const toolItems = useAppSelector((s) => s.tools.items);
- const modes = useMemo(() => Object.values(items), [items]);
-
- const mcpToolNames = useMemo(() => {
- return Object.values(toolItems)
- .filter((t) => t.mcp_config && Object.keys(t.mcp_config).length > 0 && t.auth_status !== 'none')
- .map((t) => `mcp:${t.name}`);
- }, [toolItems]);
-
- const [dialogOpen, setDialogOpen] = useState(false);
- const [editingId, setEditingId] = useState(null);
- const [form, setForm] = useState(emptyForm);
- const [browseOpen, setBrowseOpen] = useState(false);
-
- useEffect(() => {
- dispatch(fetchModes());
- dispatch(fetchBuiltinTools());
- dispatch(fetchTools());
- dispatch(fetchSkills());
- }, [dispatch]);
-
- const openCreate = () => {
- setEditingId(null);
- setForm(emptyForm);
- setDialogOpen(true);
- };
-
- const openEdit = (mode: Mode) => {
- setEditingId(mode.id);
- setForm({
- name: mode.name,
- description: mode.description,
- system_prompt: mode.system_prompt ?? '',
- tools: mode.tools ?? [],
- toolsEnabled: mode.tools !== null,
- default_next_mode: mode.default_next_mode ?? '',
- icon: mode.icon,
- color: mode.color,
- default_folder: mode.default_folder ?? '',
- });
- setDialogOpen(true);
- };
-
- const handleSave = async () => {
- const payload = {
- name: form.name,
- description: form.description,
- system_prompt: form.system_prompt || null,
- tools: form.toolsEnabled ? form.tools : null,
- default_next_mode: form.default_next_mode || null,
- icon: form.icon,
- color: form.color,
- default_folder: form.default_folder || null,
- };
-
- if (editingId) {
- await dispatch(updateMode({ id: editingId, ...payload }));
- } else {
- await dispatch(createMode(payload as any));
- }
- setDialogOpen(false);
- };
-
- const handleDelete = async (id: string) => {
- await dispatch(deleteMode(id));
- };
-
- const editingIsBuiltin = editingId ? items[editingId]?.is_builtin ?? false : false;
-
- const hasDiverged = useMemo(() => {
- if (!editingId || !editingIsBuiltin) return false;
- const defaults = builtinDefaults[editingId];
- if (!defaults) return false;
- const current = items[editingId];
- if (!current) return false;
- return (
- current.name !== defaults.name ||
- current.description !== defaults.description ||
- (current.system_prompt ?? '') !== (defaults.system_prompt ?? '') ||
- JSON.stringify(current.tools) !== JSON.stringify(defaults.tools) ||
- (current.default_next_mode ?? '') !== (defaults.default_next_mode ?? '') ||
- current.icon !== defaults.icon ||
- current.color !== defaults.color ||
- (current.default_folder ?? '') !== (defaults.default_folder ?? '')
- );
- }, [editingId, editingIsBuiltin, items, builtinDefaults]);
-
- const handleReset = async () => {
- if (!editingId) return;
- const action = await dispatch(resetMode(editingId));
- if (resetMode.fulfilled.match(action)) {
- const m = action.payload;
- setForm({
- name: m.name,
- description: m.description,
- system_prompt: m.system_prompt ?? '',
- tools: m.tools ?? [],
- toolsEnabled: m.tools !== null,
- default_next_mode: m.default_next_mode ?? '',
- icon: m.icon,
- color: m.color,
- default_folder: m.default_folder ?? '',
- });
- }
- };
-
- const otherModes = modes.filter((m) => m.id !== editingId);
-
- return (
-
-
-
-
- Modes
-
-
- Configure agent interaction modes with custom system prompts, actions, and auto-switching.
-
-
- }
- onClick={openCreate}
- sx={{
- bgcolor: c.accent.primary,
- '&:hover': { bgcolor: c.accent.pressed },
- textTransform: 'none',
- borderRadius: 2,
- }}
- >
- New Mode
-
-
-
- {loading ? (
-
- {[0, 1, 2, 3, 4, 5].map((i) => (
-
- ))}
-
- ) : modes.length === 0 ? (
-
-
- No modes defined yet. Create one to get started.
-
- ) : (
-
- {modes.map((mode) => (
-
-
-
-
- {ICON_MAP[mode.icon] || ICON_MAP.smart_toy}
-
-
- {mode.name}
-
- {mode.is_builtin && (
- }
- label="Built-in"
- size="small"
- sx={{ bgcolor: c.bg.secondary, color: c.text.muted, fontSize: '0.7rem', height: 22 }}
- />
- )}
-
- {mode.description && (
-
- {mode.description}
-
- )}
-
- {mode.tools !== null ? (
-
- ) : (
-
- )}
- {mode.system_prompt && (
-
- )}
- {mode.default_next_mode && (
- }
- label={items[mode.default_next_mode]?.name || mode.default_next_mode}
- size="small"
- sx={{ bgcolor: 'rgba(251,191,36,0.15)', color: '#fbbf24', fontSize: '0.75rem', height: 24 }}
- />
- )}
- {mode.default_folder && (
- }
- label={mode.default_folder.split('/').pop() || mode.default_folder}
- size="small"
- sx={{ bgcolor: 'rgba(56,189,248,0.15)', color: '#38bdf8', fontSize: '0.75rem', height: 24 }}
- />
- )}
-
-
-
-
- openEdit(mode)} sx={{ color: c.text.tertiary, '&:hover': { color: c.accent.primary } }}>
-
-
-
- {!mode.is_builtin && (
-
- handleDelete(mode.id)} sx={{ color: c.text.tertiary, '&:hover': { color: c.status.error } }}>
-
-
-
- )}
-
-
- ))}
-
- )}
-
-
-
- setBrowseOpen(false)}
- onSelect={(item) => setForm({ ...form, default_folder: item.path })}
- initialPath={form.default_folder || ''}
- />
-
- );
-};
-
-export default Modes;
diff --git a/frontend/src/app/pages/Settings/Settings.tsx b/frontend/src/app/pages/Settings/Settings.tsx
index e1612023..f1eacc28 100644
--- a/frontend/src/app/pages/Settings/Settings.tsx
+++ b/frontend/src/app/pages/Settings/Settings.tsx
@@ -4,6 +4,7 @@ import Snackbar from '@mui/material/Snackbar';
import Alert from '@mui/material/Alert';
import Dialog from '@mui/material/Dialog';
import DialogContent from '@mui/material/DialogContent';
+import CircularProgress from '@mui/material/CircularProgress';
import { useAppDispatch, useAppSelector } from '@/shared/hooks';
import { updateSettingsPatch, closeSettingsModal, AppSettings } from '@/shared/state/settingsSlice';
import { onboardingBus } from '@/app/components/Onboarding/eventBus';
@@ -18,6 +19,10 @@ import UsageStats from './sections/usage/UsageStats';
import SettingsHeader from './sections/SettingsHeader';
import { makeSettingsStyles } from './sections/settingsStyles';
+// Skills/Tools moved here from the old sidebar Customization section; lazy since both pull heavy deps and Settings opens nearly every session.
+const SkillsTab = React.lazy(() => import('@/app/pages/Skills/Skills'));
+const ToolsTab = React.lazy(() => import('@/app/pages/Tools/Tools'));
+
// Brand colors for provider group headers; mirrors ChatInput picker.
const PROVIDER_COLORS: Record = {
anthropic: '#E8927A',
@@ -85,7 +90,7 @@ const Settings: React.FC = () => {
}, [modelsByProvider, modelsLoaded, settings.connection_mode, settings.default_model]);
const initialTab = useAppSelector((s) => s.settings.initialTab);
- const TAB_VALUES = ['general', 'models', 'usage', 'commands'] as const;
+ const TAB_VALUES = ['general', 'models', 'skills', 'tools', 'commands', 'usage'] as const;
type SettingsTab = typeof TAB_VALUES[number];
const isValidTab = (t: string | null | undefined): t is SettingsTab =>
!!t && (TAB_VALUES as readonly string[]).includes(t);
@@ -219,6 +224,8 @@ const Settings: React.FC = () => {
sx: {
width: 780,
height: '85vh',
+ display: 'flex',
+ flexDirection: 'column',
bgcolor: c.bg.page,
borderRadius: 2,
border: `1px solid ${c.border.subtle}`,
@@ -236,6 +243,8 @@ const Settings: React.FC = () => {
{
+ ) : activeTab === 'skills' ? (
+
+ }>
+
+
+
+ ) : activeTab === 'tools' ? (
+
+ }>
+
+
+
) : (
diff --git a/frontend/src/app/pages/Settings/sections/SettingsHeader.tsx b/frontend/src/app/pages/Settings/sections/SettingsHeader.tsx
index c81fa4c2..a012c273 100644
--- a/frontend/src/app/pages/Settings/sections/SettingsHeader.tsx
+++ b/frontend/src/app/pages/Settings/sections/SettingsHeader.tsx
@@ -54,8 +54,10 @@ const SettingsHeader: React.FC<{
>
-
+
+
+
);
diff --git a/frontend/src/app/pages/Skills/Skills.tsx b/frontend/src/app/pages/Skills/Skills.tsx
index 80c2104f..8399a2a9 100644
--- a/frontend/src/app/pages/Skills/Skills.tsx
+++ b/frontend/src/app/pages/Skills/Skills.tsx
@@ -128,7 +128,7 @@ const Skills: React.FC = () => {
const regGrouped = useMemo(() => {
const groups: Record = {};
- const q = searchFilter.toLowerCase();
+ const q = searchFilter.trim().toLowerCase();
for (const sk of regSkills) {
if (q && !sk.name.toLowerCase().includes(q) && !sk.description.toLowerCase().includes(q)) continue;
const cat = sk.category || 'General';
@@ -139,7 +139,7 @@ const Skills: React.FC = () => {
}, [regSkills, searchFilter]);
const filteredLocal = useMemo(() => {
- const q = searchFilter.toLowerCase();
+ const q = searchFilter.trim().toLowerCase();
if (!q) return localSkills;
return localSkills.filter((s) => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q));
}, [localSkills, searchFilter]);
diff --git a/frontend/src/app/pages/Tools/Tools.tsx b/frontend/src/app/pages/Tools/Tools.tsx
index c1048263..02f4c54a 100644
--- a/frontend/src/app/pages/Tools/Tools.tsx
+++ b/frontend/src/app/pages/Tools/Tools.tsx
@@ -106,8 +106,8 @@ const Tools: React.FC = () => {
- Action Library
- Define and manage custom actions for your Claude Code agents.
+ Tool Library
+ Define and manage custom tools for your Claude Code agents.
{coreTools.length > 0 && (
- } count={coreTools.length} open={coreSectionOpen} onToggle={() => setCoreSectionOpen((v) => !v)} grouped={groupedCore} collapsedCategories={collapsedCategories} toggleCategory={toggleCategory} expandedBuiltin={expandedBuiltin} toggleBuiltinExpand={toggleBuiltinExpand} builtinPermissions={builtinPermissions} onPermissionChange={a.handleBuiltinPermissionChange} onCategoryPermissionChange={a.handleBuiltinCategoryPermissionChange} enabled={coreSectionEnabled} onEnabledChange={(v) => a.handleSectionEnabledChange(coreTools, v)} />
+ } count={coreTools.length} open={coreSectionOpen} onToggle={() => setCoreSectionOpen((v) => !v)} grouped={groupedCore} collapsedCategories={collapsedCategories} toggleCategory={toggleCategory} expandedBuiltin={expandedBuiltin} toggleBuiltinExpand={toggleBuiltinExpand} builtinPermissions={builtinPermissions} onPermissionChange={a.handleBuiltinPermissionChange} onCategoryPermissionChange={a.handleBuiltinCategoryPermissionChange} enabled={coreSectionEnabled} onEnabledChange={(v) => a.handleSectionEnabledChange(coreTools, v)} />
)}
{deferredTools.length > 0 && (
- } count={deferredTools.length} open={deferredSectionOpen} onToggle={() => setDeferredSectionOpen((v) => !v)} grouped={groupedDeferred} collapsedCategories={collapsedCategories} toggleCategory={toggleCategory} expandedBuiltin={expandedBuiltin} toggleBuiltinExpand={toggleBuiltinExpand} deferred builtinPermissions={builtinPermissions} onPermissionChange={a.handleBuiltinPermissionChange} onCategoryPermissionChange={a.handleBuiltinCategoryPermissionChange} enabled={deferredSectionEnabled} onEnabledChange={(v) => a.handleSectionEnabledChange(deferredTools, v)} />
+ } count={deferredTools.length} open={deferredSectionOpen} onToggle={() => setDeferredSectionOpen((v) => !v)} grouped={groupedDeferred} collapsedCategories={collapsedCategories} toggleCategory={toggleCategory} expandedBuiltin={expandedBuiltin} toggleBuiltinExpand={toggleBuiltinExpand} deferred builtinPermissions={builtinPermissions} onPermissionChange={a.handleBuiltinPermissionChange} onCategoryPermissionChange={a.handleBuiltinCategoryPermissionChange} enabled={deferredSectionEnabled} onEnabledChange={(v) => a.handleSectionEnabledChange(deferredTools, v)} />
)}
{browserTools.length > 0 && (
@@ -183,7 +183,7 @@ const Tools: React.FC = () => {
setCustomSectionOpen((v) => !v)} sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mb: 1, cursor: 'pointer', userSelect: 'none', '&:hover .section-arrow': { color: c.text.secondary } }}>
{customSectionOpen ? : }
- Custom Action Sets
+ Connections
@@ -196,7 +196,7 @@ const Tools: React.FC = () => {
) : (tools.length === 0 && uninstalledIntegrations.length === 0) ? (
- No custom actions defined yet. Create one to get started.
+ No custom tools defined yet. Create one to get started.
) : (
diff --git a/frontend/src/app/pages/Tools/cards/BrowserPermissionCard.tsx b/frontend/src/app/pages/Tools/cards/BrowserPermissionCard.tsx
index 7dad43c1..5fb2ce38 100644
--- a/frontend/src/app/pages/Tools/cards/BrowserPermissionCard.tsx
+++ b/frontend/src/app/pages/Tools/cards/BrowserPermissionCard.tsx
@@ -64,9 +64,9 @@ const BrowserPermissionCard: React.FC = ({
Browser
-
+
- Browser automation delegation and individual browser actions
+ Browser automation delegation and individual browser tools
e.stopPropagation()}>
= ({
- Action Permissions
-
+ Tool Permissions
+
@@ -156,7 +156,7 @@ const BrowserPermissionCard: React.FC = ({
>
- Browser Actions
+ Browser Tools
e.stopPropagation()}>
diff --git a/frontend/src/app/pages/Tools/cards/CustomToolCard.tsx b/frontend/src/app/pages/Tools/cards/CustomToolCard.tsx
index 05248bd1..d6c7da39 100644
--- a/frontend/src/app/pages/Tools/cards/CustomToolCard.tsx
+++ b/frontend/src/app/pages/Tools/cards/CustomToolCard.tsx
@@ -129,7 +129,7 @@ const CustomToolCard: React.FC = ({
} label="Configured" size="small" sx={{ bgcolor: c.status.warningBg, color: c.status.warning, fontSize: '0.7rem', height: 20, '& .MuiChip-icon': { color: c.status.warning } }} />
)}
{ig && totalToolCount > 0 && (
-
+
)}
{ig && (
} label="docs" size="small" sx={{ bgcolor: c.bg.secondary, color: c.text.ghost, fontSize: '0.65rem', height: 18, '& .MuiChip-label': { px: 0.4 }, '& .MuiChip-icon': { ml: 0.4, fontSize: 10 } }} />
@@ -190,13 +190,13 @@ const CustomToolCard: React.FC = ({
- Action Permissions
- {hasPerms && }
+ Tool Permissions
+ {hasPerms && }
{hasPerms && (
<>
-
+
@@ -208,7 +208,7 @@ const CustomToolCard: React.FC = ({
>
)}
-
+
handleDiscover(tool.id)}
@@ -224,7 +224,7 @@ const CustomToolCard: React.FC = ({
{!hasPerms ? (
- No actions discovered yet
+ No tools discovered yet
{!canDiscover && (
- Add an MCP configuration to enable action discovery
+ Add an MCP configuration to enable tool discovery
)}
) : (
diff --git a/frontend/src/app/pages/Tools/cards/ToolSection.tsx b/frontend/src/app/pages/Tools/cards/ToolSection.tsx
index 9c32964e..68597e49 100644
--- a/frontend/src/app/pages/Tools/cards/ToolSection.tsx
+++ b/frontend/src/app/pages/Tools/cards/ToolSection.tsx
@@ -84,8 +84,8 @@ const ToolSection: React.FC = ({
const overallPolicy = getCatGroupPolicy(allSectionTools);
const categoryCount = CATEGORY_ORDER.filter((cat) => grouped[cat]).length;
const sectionDescription = deferred
- ? 'On-demand actions loaded via ToolSearch for planning, scheduling, and extended operations'
- : 'Built-in Claude Agent SDK actions for file operations, shell commands, and search';
+ ? 'On-demand tools loaded via ToolSearch for planning, scheduling, and extended operations'
+ : 'Built-in Claude Agent SDK tools for file operations, shell commands, and search';
const firstSentence = (desc: string) => {
if (!desc) return '';
@@ -110,7 +110,7 @@ const ToolSection: React.FC = ({
{label}
-
+
{deferred && (
)}
@@ -139,8 +139,8 @@ const ToolSection: React.FC = ({
- Action Permissions
-
+ Tool Permissions
+
diff --git a/frontend/src/app/pages/Tools/hooks/useRegistryBrowser.ts b/frontend/src/app/pages/Tools/hooks/useRegistryBrowser.ts
index a311b0b3..520f9a5a 100644
--- a/frontend/src/app/pages/Tools/hooks/useRegistryBrowser.ts
+++ b/frontend/src/app/pages/Tools/hooks/useRegistryBrowser.ts
@@ -140,10 +140,10 @@ export function useRegistryBrowser({ regServersRaw, setSnackbar, setEditingId, s
}));
if (createTool.fulfilled.match(result)) {
const newTool = result.payload;
- setSnackbar({ open: true, message: `Installed "${f.name}", discovering actions…` });
+ setSnackbar({ open: true, message: `Installed "${f.name}", discovering tools…` });
const discoverResult = await dispatch(discoverTools(newTool.id));
if (discoverTools.fulfilled.match(discoverResult)) {
- setSnackbar({ open: true, message: `${f.name} ready, actions discovered` });
+ setSnackbar({ open: true, message: `${f.name} ready, tools discovered` });
} else {
const detail = (discoverResult as any).error?.message
|| 'discovery failed; the MCP server may need setup first';
diff --git a/frontend/src/app/pages/Tools/hooks/useToolConnections.ts b/frontend/src/app/pages/Tools/hooks/useToolConnections.ts
index 68ca30ea..158b6631 100644
--- a/frontend/src/app/pages/Tools/hooks/useToolConnections.ts
+++ b/frontend/src/app/pages/Tools/hooks/useToolConnections.ts
@@ -45,7 +45,7 @@ export function useToolConnections({ items, setSnackbar, setExpandedToolId }: De
const afterConnect = async () => {
const statusResult = await dispatch(fetchToolStatus(toolId));
if (fetchToolStatus.fulfilled.match(statusResult) && statusResult.payload.auth_status === 'connected') {
- setSnackbar({ open: true, message: 'Account connected! Discovering actions…' });
+ setSnackbar({ open: true, message: 'Account connected! Discovering tools…' });
setExpandedToolId(toolId);
dispatch(discoverTools(toolId));
} else {
@@ -97,7 +97,7 @@ export function useToolConnections({ items, setSnackbar, setExpandedToolId }: De
if (status === 'connected') {
clearInterval(poll);
setDeviceCodeStatus('connected');
- setSnackbar({ open: true, message: `Connected to Microsoft 365${email ? ` as ${email}` : ''}! Discovering actions…` });
+ setSnackbar({ open: true, message: `Connected to Microsoft 365${email ? ` as ${email}` : ''}! Discovering tools…` });
setDeviceCodeDialogOpen(false);
setExpandedToolId(toolId);
await dispatch(fetchToolStatus(toolId));
@@ -148,7 +148,7 @@ export function useToolConnections({ items, setSnackbar, setExpandedToolId }: De
}));
if (updateTool.fulfilled.match(result)) {
setCredDialogOpen(false);
- setSnackbar({ open: true, message: `${credDialogIntegration.name} connected! Re-discovering actions…` });
+ setSnackbar({ open: true, message: `${credDialogIntegration.name} connected! Re-discovering tools…` });
dispatch(discoverTools(credDialogToolId));
} else {
setSnackbar({ open: true, message: 'Failed to save credentials', severity: 'error' });
@@ -177,7 +177,7 @@ export function useToolConnections({ items, setSnackbar, setExpandedToolId }: De
}));
if (updateTool.fulfilled.match(result)) {
setCredDialogOpen(false);
- setSnackbar({ open: true, message: 'Slack connected! Re-discovering actions…' });
+ setSnackbar({ open: true, message: 'Slack connected! Re-discovering tools…' });
dispatch(discoverTools(credDialogToolId));
} else {
setSnackbar({ open: true, message: 'Failed to save Slack credentials', severity: 'error' });
diff --git a/frontend/src/app/pages/Tools/hooks/useToolsActions.ts b/frontend/src/app/pages/Tools/hooks/useToolsActions.ts
index 5b0d1ba6..9a3c6916 100644
--- a/frontend/src/app/pages/Tools/hooks/useToolsActions.ts
+++ b/frontend/src/app/pages/Tools/hooks/useToolsActions.ts
@@ -56,12 +56,12 @@ export function useToolsActions({ items, allTools, regServersRaw, closeMenu }: T
} else if (existing && existing.enabled === false) {
await dispatch(updateTool({ id: existing.id, enabled: true }));
if (integration.authType === 'oauth2' && existing.auth_status !== 'connected') {
- setSnackbar({ open: true, message: `Enabled ${integration.name}, connect your account to discover actions` });
+ setSnackbar({ open: true, message: `Enabled ${integration.name}, connect your account to discover tools` });
} else {
- setSnackbar({ open: true, message: `Enabled ${integration.name}, re-discovering actions…` });
+ setSnackbar({ open: true, message: `Enabled ${integration.name}, re-discovering tools…` });
const discoverResult = await dispatch(discoverTools(existing.id));
if (discoverTools.fulfilled.match(discoverResult)) {
- setSnackbar({ open: true, message: `${integration.name} ready, actions discovered` });
+ setSnackbar({ open: true, message: `${integration.name} ready, tools discovered` });
} else {
const detail = (discoverResult as any).error?.message || 'discovery failed';
setSnackbar({ open: true, message: `${integration.name}: ${detail}`, severity: 'error' });
@@ -80,12 +80,12 @@ export function useToolsActions({ items, allTools, regServersRaw, closeMenu }: T
if (createTool.fulfilled.match(result)) {
const newTool = result.payload;
if (integration.authType === 'oauth2' || integration.authType === 'device_code') {
- setSnackbar({ open: true, message: `Enabled ${integration.name}, connect your account to discover actions` });
+ setSnackbar({ open: true, message: `Enabled ${integration.name}, connect your account to discover tools` });
} else {
- setSnackbar({ open: true, message: `Enabled ${integration.name}, discovering actions…` });
+ setSnackbar({ open: true, message: `Enabled ${integration.name}, discovering tools…` });
const discoverResult = await dispatch(discoverTools(newTool.id));
if (discoverTools.fulfilled.match(discoverResult)) {
- setSnackbar({ open: true, message: `${integration.name} ready, actions discovered` });
+ setSnackbar({ open: true, message: `${integration.name} ready, tools discovered` });
} else {
const detail = (discoverResult as any).error?.message
|| `discovery failed; is ${integration.mcp_config.command || 'the server'} installed?`;
@@ -104,7 +104,7 @@ export function useToolsActions({ items, allTools, regServersRaw, closeMenu }: T
try {
const result = await dispatch(discoverTools(toolId));
if (discoverTools.fulfilled.match(result)) {
- setSnackbar({ open: true, message: 'Actions discovered successfully' });
+ setSnackbar({ open: true, message: 'Tools discovered successfully' });
} else {
const detail = (result as any).error?.message || 'Discovery failed; is the MCP server running?';
setSnackbar({ open: true, message: detail, severity: 'error' });