diff --git a/apps/commons-app/app/studio/[tab]/page.tsx b/apps/commons-app/app/studio/[tab]/page.tsx index 4ae1f97c..237cd518 100644 --- a/apps/commons-app/app/studio/[tab]/page.tsx +++ b/apps/commons-app/app/studio/[tab]/page.tsx @@ -228,7 +228,7 @@ const StudioPage: NextPage = () => { case "tools": return "Create new tool"; case "tasks": - return "Create new task"; + return "Create new scheduled task"; case "workflows": return "Create new workflow"; case "skills": @@ -249,7 +249,7 @@ const StudioPage: NextPage = () => { }; case "tasks": return { - title: "Tasks", + title: "Scheduled tasks", description: "Schedule one-off or recurring tasks for your agents to run automatically.", }; diff --git a/apps/commons-app/app/studio/agents/[agent]/page.tsx b/apps/commons-app/app/studio/agents/[agent]/page.tsx index ddcba42b..254a9e5b 100644 --- a/apps/commons-app/app/studio/agents/[agent]/page.tsx +++ b/apps/commons-app/app/studio/agents/[agent]/page.tsx @@ -9,7 +9,6 @@ import { BarChart3, Bot, Brain, - CalendarCheck, Check, CheckCircle2, ChevronRight, @@ -27,13 +26,14 @@ import { Search, Server, Settings2, - Sparkles, TerminalSquare, Wallet, Wrench, XCircle, Zap, } from "lucide-react"; +import { ClipboardClock } from "@/components/icons/clipboard-clock"; +import { SkillIcon } from "@/components/skills/skill-icon"; import { formatDistanceToNow } from "date-fns"; import { Area, @@ -120,9 +120,9 @@ const sections: Array<{ key: SectionKey; label: string; icon: typeof Bot }> = [ { key: "new-session", label: "New session", icon: Plus }, { key: "sessions", label: "Sessions", icon: MessageSquare }, { key: "computer", label: "Computer", icon: Monitor }, - { key: "tasks", label: "Tasks", icon: CalendarCheck }, + { key: "tasks", label: "Scheduled tasks", icon: ClipboardClock }, { key: "tools", label: "Tools", icon: Wrench }, - { key: "skills", label: "Skills", icon: Sparkles }, + { key: "skills", label: "Skills", icon: Zap }, { key: "artifacts", label: "Artifacts", icon: FileText }, { key: "observability", label: "Observability", icon: TerminalSquare }, { key: "usage", label: "Usage", icon: BarChart3 }, @@ -1473,7 +1473,7 @@ function SkillsView({ ) : visibleSkills.length === 0 ? (
- +

{view === "assigned" ? "No skills enabled" : "No skills found"}

@@ -1500,9 +1500,7 @@ function SkillsView({ } className="flex min-w-0 flex-1 items-start gap-3 text-left" > - - {skill.icon || } - + diff --git a/apps/commons-app/app/studio/skills/[skillId]/page.tsx b/apps/commons-app/app/studio/skills/[skillId]/page.tsx index 4593a775..5e07d34f 100644 --- a/apps/commons-app/app/studio/skills/[skillId]/page.tsx +++ b/apps/commons-app/app/studio/skills/[skillId]/page.tsx @@ -2,10 +2,11 @@ import { use, useEffect, useState } from "react"; import { useRouter } from "next/navigation"; -import { ArrowLeft, Globe, Lock, Sparkles } from "lucide-react"; +import { ArrowLeft, Globe, Lock } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; +import { SkillIcon } from "@/components/skills/skill-icon"; import type { Skill } from "@agent-commons/sdk"; export default function SkillDetailPage({ @@ -74,13 +75,7 @@ export default function SkillDetailPage({
- - {skill.icon ? ( - {skill.icon} - ) : ( - - )} - +

{skill.name}

{skill.slug}

diff --git a/apps/commons-app/app/studio/tasks/[taskId]/page.tsx b/apps/commons-app/app/studio/tasks/[taskId]/page.tsx index 092395e6..611afa33 100644 --- a/apps/commons-app/app/studio/tasks/[taskId]/page.tsx +++ b/apps/commons-app/app/studio/tasks/[taskId]/page.tsx @@ -258,7 +258,7 @@ export default function TaskDetailPage({

Task not found.

); diff --git a/apps/commons-app/components/agents/runtime-native-tooling.tsx b/apps/commons-app/components/agents/runtime-native-tooling.tsx index 1ab4ae2a..a1a7fbb4 100644 --- a/apps/commons-app/components/agents/runtime-native-tooling.tsx +++ b/apps/commons-app/components/agents/runtime-native-tooling.tsx @@ -13,9 +13,9 @@ import { Send, Server, ShieldCheck, - Sparkles, Unplug, X, + Zap, } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -1467,7 +1467,7 @@ export function RuntimeSkillsNote({ const meta = RUNTIME_NATIVE_TOOLING[key]; return (
- +

{meta.skillsNote}

); diff --git a/apps/commons-app/components/copilot/floating-commons-copilot.tsx b/apps/commons-app/components/copilot/floating-commons-copilot.tsx index bfc50fa5..89981251 100644 --- a/apps/commons-app/components/copilot/floating-commons-copilot.tsx +++ b/apps/commons-app/components/copilot/floating-commons-copilot.tsx @@ -47,7 +47,7 @@ const SCOPES = [ ["agents", "Agents"], ["tools", "Tools"], ["skills", "Skills"], - ["tasks", "Tasks"], + ["tasks", "Scheduled tasks"], ] as const; const PANEL_WIDTH_KEY = "copilot-panel-width"; diff --git a/apps/commons-app/components/customize/customize-tabs.tsx b/apps/commons-app/components/customize/customize-tabs.tsx index 2270d942..0fc49a1c 100644 --- a/apps/commons-app/components/customize/customize-tabs.tsx +++ b/apps/commons-app/components/customize/customize-tabs.tsx @@ -2,7 +2,7 @@ import Link from "next/link"; import { usePathname } from "next/navigation"; -import { AppWindow, Sparkles } from "lucide-react"; +import { AppWindow, Zap } from "lucide-react"; import { cn } from "@/lib/utils"; const tabs = [ @@ -16,7 +16,7 @@ const tabs = [ label: "Skills", href: "/studio/customize/skills", segment: "/studio/customize/skills", - icon: Sparkles, + icon: Zap, }, ] as const; diff --git a/apps/commons-app/components/icons/clipboard-clock.tsx b/apps/commons-app/components/icons/clipboard-clock.tsx new file mode 100644 index 00000000..68c3a146 --- /dev/null +++ b/apps/commons-app/components/icons/clipboard-clock.tsx @@ -0,0 +1,40 @@ +import { forwardRef } from "react"; +import type { LucideProps } from "lucide-react"; + +/** + * `clipboard-clock` from Lucide, inlined because the pinned lucide-react + * (0.474) predates the icon. Same 24×24 geometry, props, and stroke defaults + * as every other Lucide glyph, so it sizes and colors identically alongside + * them. + * + * This is the single icon for scheduled tasks across the app. + */ +export const ClipboardClock = forwardRef( + ({ size = 24, strokeWidth = 2, absoluteStrokeWidth, ...props }, ref) => ( + + + + + + + + ) +); + +ClipboardClock.displayName = "ClipboardClock"; diff --git a/apps/commons-app/components/landing/landing-sidebar.tsx b/apps/commons-app/components/landing/landing-sidebar.tsx index b61664c5..88a821fb 100644 --- a/apps/commons-app/components/landing/landing-sidebar.tsx +++ b/apps/commons-app/components/landing/landing-sidebar.tsx @@ -2,18 +2,18 @@ import Image from "next/image"; import Link from "next/link"; import { Bot, - BriefcaseBusiness, LibraryBig, MoreHorizontal, Search, Wrench, Workflow, } from "lucide-react"; +import { ClipboardClock } from "@/components/icons/clipboard-clock"; const NAV_ITEMS = [ { label: "Agents", icon: Bot, target: "/studio/agents" }, { label: "Tools", icon: Wrench, target: "/studio/tools" }, - { label: "Tasks", icon: BriefcaseBusiness, target: "/studio/tasks" }, + { label: "Scheduled tasks", icon: ClipboardClock, target: "/studio/tasks" }, { label: "Workflows", icon: Workflow, target: "/studio/workflows" }, { label: "Library", icon: LibraryBig, target: "/library" }, ]; diff --git a/apps/commons-app/components/layout/dashboard-bar.tsx b/apps/commons-app/components/layout/dashboard-bar.tsx index ec6b504d..97658991 100644 --- a/apps/commons-app/components/layout/dashboard-bar.tsx +++ b/apps/commons-app/components/layout/dashboard-bar.tsx @@ -3,14 +3,8 @@ import { FC, ReactNode } from "react"; import { useRouter } from "next/navigation"; -import { - Bot, - BriefcaseBusiness, - LibraryBig, - Wrench, - Workflow, - Settings2, -} from "lucide-react"; +import { Bot, LibraryBig, Wrench, Workflow, Settings2 } from "lucide-react"; +import { ClipboardClock } from "@/components/icons/clipboard-clock"; import Link from "next/link"; import Image from "next/image"; import { SearchTrigger } from "@/components/search/search-trigger"; @@ -35,8 +29,8 @@ export const DashboardBar: FC = ({ { key: "tools", label: "Tools", icon: Wrench, path: "/studio/tools" }, { key: "tasks", - label: "Tasks", - icon: BriefcaseBusiness, + label: "Scheduled tasks", + icon: ClipboardClock, path: "/studio/tasks", }, { diff --git a/apps/commons-app/components/layout/dashboard-side-bar.tsx b/apps/commons-app/components/layout/dashboard-side-bar.tsx index 878ce708..355c6508 100644 --- a/apps/commons-app/components/layout/dashboard-side-bar.tsx +++ b/apps/commons-app/components/layout/dashboard-side-bar.tsx @@ -7,7 +7,6 @@ import Image from "next/image"; import { cn } from "@/lib/utils"; import { Bot, - BriefcaseBusiness, PanelLeft, PanelRight, LibraryBig, @@ -16,6 +15,7 @@ import { Workflow, Settings2, } from "lucide-react"; +import { ClipboardClock } from "@/components/icons/clipboard-clock"; import { ScrollArea } from "@/components/ui/scroll-area"; import { DashboardBar } from "./dashboard-bar"; import { SidebarAccount } from "./sidebar-account"; @@ -160,9 +160,9 @@ export function DashboardSideBar({ username }: { username: string }) { }, { key: "tasks", - icon: BriefcaseBusiness, + icon: ClipboardClock, path: "/studio/tasks", - label: "Tasks", + label: "Scheduled tasks", }, { key: "workflows", diff --git a/apps/commons-app/components/search/global-search.tsx b/apps/commons-app/components/search/global-search.tsx index ffcc0cfc..d4e69b18 100644 --- a/apps/commons-app/components/search/global-search.tsx +++ b/apps/commons-app/components/search/global-search.tsx @@ -15,7 +15,6 @@ import { Bot, MessageSquare, Wrench, - BriefcaseBusiness, Workflow, Earth, LibraryBig, @@ -23,6 +22,7 @@ import { Wallet, Loader2, } from "lucide-react"; +import { ClipboardClock } from "@/components/icons/clipboard-clock"; import { useAuth } from "@/context/AuthContext"; import { normalizePrincipalId } from "@/lib/principal-id"; import { useAgents } from "@/hooks/use-agents"; @@ -38,7 +38,7 @@ interface GlobalSearchProps { const NAV_ITEMS = [ { label: "Agents", path: "/studio/agents", icon: Bot, keywords: "agents" }, { label: "Tools", path: "/studio/tools", icon: Wrench, keywords: "tools integrations" }, - { label: "Tasks", path: "/studio/tasks", icon: BriefcaseBusiness, keywords: "tasks queue" }, + { label: "Scheduled tasks", path: "/studio/tasks", icon: ClipboardClock, keywords: "tasks queue scheduled cron recurring" }, { label: "Workflows", path: "/studio/workflows", icon: Workflow, keywords: "workflows automation" }, { label: "Spaces", path: "/spaces", icon: Earth, keywords: "spaces rooms live" }, { label: "Library", path: "/library", icon: LibraryBig, keywords: "library files documents collections" }, @@ -191,14 +191,14 @@ export function GlobalSearch({ open, onOpenChange }: GlobalSearchProps) { {tasks.length > 0 && ( <> - + {tasks.map((t) => ( go(`/studio/tasks/${t.taskId}`)} > - + {t.title} ))} diff --git a/apps/commons-app/components/skills/skill-icon.tsx b/apps/commons-app/components/skills/skill-icon.tsx new file mode 100644 index 00000000..d92036a4 --- /dev/null +++ b/apps/commons-app/components/skills/skill-icon.tsx @@ -0,0 +1,135 @@ +"use client"; + +import type { ElementType } from "react"; +import { + BarChart3, + Bot, + Brain, + Calendar, + Code2, + Database, + FileSpreadsheet, + FileText, + FileType, + Folder, + Github, + Globe, + Image, + Layers, + Link2, + Mail, + MessageSquare, + Monitor, + PanelsTopLeft, + PenLine, + Presentation, + Search, + Send, + Settings2, + Table2, + Terminal, + Users, + Video, + Workflow, + Wrench, + Zap, +} from "lucide-react"; +import { cn } from "@/lib/utils"; + +/** + * Skills store `icon` as either a Lucide icon name (platform skills seed + * kebab-case names like `panels-top-left`) or a single emoji (what the create + * form asks users for). Anything unrecognised falls back to the Zap mark + * rather than leaking a raw slug into the layout. + */ +const lucideByName: Record = { + "bar-chart-3": BarChart3, + bot: Bot, + brain: Brain, + calendar: Calendar, + "code-2": Code2, + code: Code2, + database: Database, + "file-spreadsheet": FileSpreadsheet, + "file-text": FileText, + "file-type": FileType, + folder: Folder, + github: Github, + globe: Globe, + image: Image, + layers: Layers, + "link-2": Link2, + mail: Mail, + "message-square": MessageSquare, + monitor: Monitor, + "panels-top-left": PanelsTopLeft, + "pen-line": PenLine, + presentation: Presentation, + search: Search, + send: Send, + "settings-2": Settings2, + "table-2": Table2, + terminal: Terminal, + users: Users, + video: Video, + workflow: Workflow, + wrench: Wrench, + zap: Zap, +}; + +/** `PanelsTopLeft` / `panelsTopLeft` / `Panels Top Left` → `panels-top-left`. */ +function toKebabCase(value: string) { + return value + .trim() + .replace(/([a-z0-9])([A-Z])/g, "$1-$2") + .replace(/[\s_]+/g, "-") + .toLowerCase(); +} + +/** Emoji and other pictographs are safe to render as text; slugs are not. */ +function isGlyph(value: string) { + const glyph = value.trim(); + if (!glyph || /[a-zA-Z0-9]/.test(glyph)) return false; + return Array.from(glyph).length <= 2; +} + +const sizeStyles = { + sm: { chip: "h-7 w-7 rounded-md", glyph: "text-sm", lucide: "h-3.5 w-3.5" }, + md: { chip: "h-9 w-9 rounded-lg", glyph: "text-base", lucide: "h-4 w-4" }, + lg: { chip: "h-11 w-11 rounded-xl", glyph: "text-lg", lucide: "h-5 w-5" }, +} as const; + +export function SkillIcon({ + icon, + size = "md", + className, +}: { + icon?: string | null; + size?: keyof typeof sizeStyles; + className?: string; +}) { + const styles = sizeStyles[size]; + const raw = icon?.trim() ?? ""; + const Lucide = raw ? lucideByName[toKebabCase(raw)] : undefined; + const glyph = !Lucide && isGlyph(raw) ? raw : null; + const Fallback = Zap; + + return ( + + {glyph ? ( + {glyph} + ) : Lucide ? ( + + ) : ( + + )} + + ); +} diff --git a/apps/commons-app/components/skills/skills-marketplace-view.tsx b/apps/commons-app/components/skills/skills-marketplace-view.tsx index 93632265..014f3a6c 100644 --- a/apps/commons-app/components/skills/skills-marketplace-view.tsx +++ b/apps/commons-app/components/skills/skills-marketplace-view.tsx @@ -3,7 +3,7 @@ import { useState, useEffect, useRef } from "react"; import { Loader2, - Sparkles, + Zap, Search, Trash2, Globe, @@ -35,6 +35,7 @@ import { import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Switch } from "@/components/ui/switch"; +import { SkillIcon } from "@/components/skills/skill-icon"; import type { Skill } from "@agent-commons/sdk"; interface SkillsMarketplaceViewProps { @@ -115,22 +116,39 @@ function SkillCard({ const enabledAgents = (skill.assignedAgents ?? []).filter( (assignment) => assignment.isEnabled ); + const assignmentLabel = enabledAgents.length + ? `Available to ${enabledAgents + .slice(0, 2) + .map((assignment) => assignment.agentName) + .join(", ")}${ + enabledAgents.length > 2 ? ` +${enabledAgents.length - 2}` : "" + }` + : "Not assigned to an agent"; return (
onOpen(skill.skillId)} + onKeyDown={(event) => { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + onOpen(skill.skillId); + } + }} > -
- - {skill.icon || ( - - )} - -
+
+ +
+

+ {skill.name} +

+

+ {skill.description} +

+
+
{ event.stopPropagation(); onDelete(skill.skillId); }} - aria-label="Delete skill" + aria-label={`Delete ${skill.name}`} > @@ -152,41 +170,26 @@ function SkillCard({
-

- {skill.name} -

-

- {skill.description} -

- - + + {assignmentLabel} + -
-
+
+
{skill.tags.slice(0, 3).map((tag) => ( {tag} @@ -194,14 +197,14 @@ function SkillCard({ {skill.tags.length > 3 && ( +{skill.tags.length - 3} )}
- - {skill.usageCount} uses + + {skill.usageCount} {skill.usageCount === 1 ? "use" : "uses"}
@@ -612,23 +615,25 @@ export function SkillsMarketplaceView({
) : filtered.length === 0 ? ( -
- -

+

+ +

{search ? "No skills match your search" : tab === "mine" ? "No skills yet" : "No platform skills"}

- {!search && tab === "mine" && ( -

- Create your first skill to add custom capabilities to your agents -

- )} +

+ {search + ? "Try a different name, description, or tag." + : tab === "mine" + ? "Create your first skill to add custom capabilities to your agents." + : "Platform skills will appear here once they are published."} +

) : ( -
+
{filtered.map((skill) => (