diff --git a/apps/desktop/src/components/qr-code.tsx b/apps/desktop/src/components/qr-code.tsx
new file mode 100644
index 000000000..35ba0d8d9
--- /dev/null
+++ b/apps/desktop/src/components/qr-code.tsx
@@ -0,0 +1,228 @@
+import { useState, useEffect, useCallback, useMemo } from 'react'
+import { QRCodeSVG } from 'qrcode.react'
+import { Button } from '@multica/ui/components/ui/button'
+import { HugeiconsIcon } from '@hugeicons/react'
+import {
+ RefreshIcon,
+ CheckmarkCircle02Icon,
+ Copy01Icon,
+} from '@hugeicons/core-free-icons'
+
+export interface QRCodeData {
+ type: 'multica-connect'
+ gateway: string
+ hubId: string
+ agentId: string
+ token: string
+ expires: number
+}
+
+export interface ConnectionQRCodeProps {
+ gateway: string
+ hubId: string
+ agentId: string
+ /** QR code expiry time in seconds (default: 300 = 5 minutes) */
+ expirySeconds?: number
+ /** Size of the QR code in pixels (default: 180) */
+ size?: number
+ /** Callback when token is refreshed */
+ onRefresh?: (data: QRCodeData) => void
+}
+
+/**
+ * Generate a secure random token for QR code authentication
+ */
+function generateToken(): string {
+ return crypto.randomUUID()
+}
+
+/**
+ * ConnectionQRCode - A QR code component for sharing Agent connection info
+ *
+ * Features:
+ * - Generates time-limited tokens for secure connections
+ * - Countdown timer showing expiry time
+ * - Refresh button to generate new token
+ * - Copy link button for manual sharing
+ * - Decorative corner accents for visual polish
+ */
+export function ConnectionQRCode({
+ gateway,
+ hubId,
+ agentId,
+ expirySeconds = 300,
+ size = 180,
+ onRefresh,
+}: ConnectionQRCodeProps) {
+ const [token, setToken] = useState(() => generateToken())
+ const [expiresAt, setExpiresAt] = useState(() => Date.now() + expirySeconds * 1000)
+ const [remainingSeconds, setRemainingSeconds] = useState(expirySeconds)
+ const [copied, setCopied] = useState(false)
+
+ // QR code data payload
+ const qrData: QRCodeData = useMemo(
+ () => ({
+ type: 'multica-connect',
+ gateway,
+ hubId,
+ agentId,
+ token,
+ expires: expiresAt,
+ }),
+ [gateway, hubId, agentId, token, expiresAt]
+ )
+
+ // URL format for the connection
+ const connectionUrl = useMemo(() => {
+ const params = new URLSearchParams({
+ gateway,
+ hub: hubId,
+ agent: agentId,
+ token,
+ exp: expiresAt.toString(),
+ })
+ return `multica://connect?${params.toString()}`
+ }, [gateway, hubId, agentId, token, expiresAt])
+
+ // Countdown timer
+ useEffect(() => {
+ const timer = setInterval(() => {
+ const remaining = Math.max(0, Math.floor((expiresAt - Date.now()) / 1000))
+ setRemainingSeconds(remaining)
+
+ // Auto-refresh when expired
+ if (remaining === 0) {
+ handleRefresh()
+ }
+ }, 1000)
+
+ return () => clearInterval(timer)
+ }, [expiresAt])
+
+ // Refresh token handler
+ const handleRefresh = useCallback(() => {
+ const newToken = generateToken()
+ const newExpires = Date.now() + expirySeconds * 1000
+
+ setToken(newToken)
+ setExpiresAt(newExpires)
+ setRemainingSeconds(expirySeconds)
+
+ if (onRefresh) {
+ onRefresh({
+ type: 'multica-connect',
+ gateway,
+ hubId,
+ agentId,
+ token: newToken,
+ expires: newExpires,
+ })
+ }
+ }, [gateway, hubId, agentId, expirySeconds, onRefresh])
+
+ // Copy link handler
+ const handleCopyLink = async () => {
+ try {
+ await navigator.clipboard.writeText(connectionUrl)
+ setCopied(true)
+ setTimeout(() => setCopied(false), 2000)
+ } catch (err) {
+ console.error('Failed to copy link:', err)
+ }
+ }
+
+ // Format remaining time as M:SS
+ const formatTime = (seconds: number) => {
+ const m = Math.floor(seconds / 60)
+ const s = seconds % 60
+ return `${m}:${s.toString().padStart(2, '0')}`
+ }
+
+ // Warning state when less than 1 minute remaining
+ const isExpiringSoon = remainingSeconds < 60 && remainingSeconds > 0
+ const isExpired = remainingSeconds === 0
+
+ return (
+
+ {/* QR Code with decorative corners */}
+
+ {/* Corner accents */}
+
+
+
+
+
+ {/* QR Code */}
+
+
+
+
+ {/* Expired overlay */}
+ {isExpired && (
+
+
+
+ )}
+
+
+ {/* Info section */}
+
+
+ Scan with your phone to connect
+
+
+ {/* Expiry timer */}
+
+
+ {isExpired ? 'Expired' : `Expires in ${formatTime(remainingSeconds)}`}
+
+ {!isExpired && (
+
+ )}
+
+
+ {/* Copy link button */}
+
+
+
+ )
+}
+
+export default ConnectionQRCode
diff --git a/apps/desktop/src/components/skill-list.tsx b/apps/desktop/src/components/skill-list.tsx
new file mode 100644
index 000000000..6f18501b0
--- /dev/null
+++ b/apps/desktop/src/components/skill-list.tsx
@@ -0,0 +1,225 @@
+import { useState } from 'react'
+import { Button } from '@multica/ui/components/ui/button'
+import { Badge } from '@multica/ui/components/ui/badge'
+import { Switch } from '@multica/ui/components/ui/switch'
+import { HugeiconsIcon } from '@hugeicons/react'
+import {
+ RotateClockwiseIcon,
+ Loading03Icon,
+ CheckmarkCircle02Icon,
+ Cancel01Icon,
+} from '@hugeicons/core-free-icons'
+import type { SkillInfo, SkillSource } from '../hooks/use-skills'
+
+// Source badge colors
+const SOURCE_COLORS: Record = {
+ bundled: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300',
+ global: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300',
+ profile: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-300',
+}
+
+// Source section titles
+const SOURCE_TITLES: Record = {
+ bundled: 'Built-in Skills',
+ global: 'Global Skills',
+ profile: 'Profile Skills',
+}
+
+interface SkillListProps {
+ skills: SkillInfo[]
+ loading: boolean
+ error: string | null
+ onToggleSkill: (skillId: string) => Promise
+ onRefresh: () => Promise
+}
+
+export function SkillList({
+ skills,
+ loading,
+ error,
+ onToggleSkill,
+ onRefresh,
+}: SkillListProps) {
+ // Track toggling state for individual skills
+ const [togglingSkills, setTogglingSkills] = useState>(new Set())
+
+ const handleToggleSkill = async (skillId: string) => {
+ setTogglingSkills((prev) => new Set(prev).add(skillId))
+ try {
+ await onToggleSkill(skillId)
+ } finally {
+ setTogglingSkills((prev) => {
+ const next = new Set(prev)
+ next.delete(skillId)
+ return next
+ })
+ }
+ }
+
+ // Group skills by source
+ const skillsBySource: Record = {
+ bundled: skills.filter((s) => s.source === 'bundled'),
+ global: skills.filter((s) => s.source === 'global'),
+ profile: skills.filter((s) => s.source === 'profile'),
+ }
+
+ // Order of sources to display
+ const sourceOrder: SkillSource[] = ['bundled', 'global', 'profile']
+
+ if (loading && skills.length === 0) {
+ return (
+
+
+ Loading skills...
+
+ )
+ }
+
+ return (
+
+ {/* Header */}
+
+
+ {skills.filter((s) => s.enabled).length} of {skills.length} skills enabled
+
+
+
+
+ {/* Error message */}
+ {error && (
+
+ {error}
+
+ )}
+
+ {/* Skills grouped by source */}
+ {sourceOrder.map((source) => {
+ const sourceSkills = skillsBySource[source]
+ if (sourceSkills.length === 0) return null
+
+ return (
+
+
+
+ {SOURCE_TITLES[source]}
+ ({sourceSkills.length})
+
+
+ {sourceSkills.map((skill) => {
+ const isToggling = togglingSkills.has(skill.id)
+
+ return (
+
+ {/* Left: Name + Description */}
+
+
+ {skill.name}
+
+ /{skill.id}
+
+
+ {skill.source}
+
+
+
+ {skill.description}
+
+ {skill.triggers.length > 0 && (
+
+ {skill.triggers.slice(0, 3).map((trigger) => (
+
+ {trigger}
+
+ ))}
+ {skill.triggers.length > 3 && (
+
+ +{skill.triggers.length - 3} more
+
+ )}
+
+ )}
+
+
+ {/* Center: Status */}
+
+
+
+
+ {skill.enabled ? 'Enabled' : 'Disabled'}
+
+
+
+
+ {/* Right: Toggle */}
+
+ {isToggling && (
+
+ )}
+ handleToggleSkill(skill.id)}
+ disabled={isToggling}
+ />
+
+
+ )
+ })}
+
+
+ )
+ })}
+
+ {/* Empty state */}
+ {skills.length === 0 && !loading && (
+
+ )}
+
+ {/* Note about persistence */}
+
+ Changes are saved automatically. Restart Agent session to apply skill changes.
+
+
+ )
+}
+
+export default SkillList
diff --git a/apps/desktop/src/components/tool-list.tsx b/apps/desktop/src/components/tool-list.tsx
new file mode 100644
index 000000000..2ecc05d52
--- /dev/null
+++ b/apps/desktop/src/components/tool-list.tsx
@@ -0,0 +1,208 @@
+import { useState } from 'react'
+import { Switch } from '@multica/ui/components/ui/switch'
+import { Button } from '@multica/ui/components/ui/button'
+import { HugeiconsIcon } from '@hugeicons/react'
+import {
+ RotateClockwiseIcon,
+ FolderOpenIcon,
+ CodeIcon,
+ GlobalIcon,
+ AiBrainIcon,
+ ArrowDown01Icon,
+ ArrowUp01Icon,
+ Loading03Icon,
+} from '@hugeicons/core-free-icons'
+import type { ToolInfo, ToolGroup } from '../hooks/use-tools'
+
+// Group icons
+const GROUP_ICONS: Record = {
+ fs: FolderOpenIcon,
+ runtime: CodeIcon,
+ web: GlobalIcon,
+ memory: AiBrainIcon,
+ other: CodeIcon,
+}
+
+interface ToolListProps {
+ tools: ToolInfo[]
+ groups: ToolGroup[]
+ loading: boolean
+ error: string | null
+ onToggleTool: (toolName: string) => Promise
+ onRefresh: () => Promise
+}
+
+export function ToolList({
+ tools,
+ groups,
+ loading,
+ error,
+ onToggleTool,
+ onRefresh,
+}: ToolListProps) {
+ // Track which groups are expanded
+ const [expandedGroups, setExpandedGroups] = useState>(
+ () => new Set(groups.map((g) => g.id))
+ )
+
+ // Track toggling state for individual tools
+ const [togglingTools, setTogglingTools] = useState>(new Set())
+
+ const toggleGroup = (groupId: string) => {
+ setExpandedGroups((prev) => {
+ const next = new Set(prev)
+ if (next.has(groupId)) {
+ next.delete(groupId)
+ } else {
+ next.add(groupId)
+ }
+ return next
+ })
+ }
+
+ const handleToggleTool = async (toolName: string) => {
+ setTogglingTools((prev) => new Set(prev).add(toolName))
+ try {
+ await onToggleTool(toolName)
+ } finally {
+ setTogglingTools((prev) => {
+ const next = new Set(prev)
+ next.delete(toolName)
+ return next
+ })
+ }
+ }
+
+ // Group tools by their group
+ const toolsByGroup = groups.map((group) => ({
+ ...group,
+ tools: tools.filter((t) => t.group === group.id),
+ enabledCount: tools.filter((t) => t.group === group.id && t.enabled).length,
+ totalCount: tools.filter((t) => t.group === group.id).length,
+ }))
+
+ if (loading && tools.length === 0) {
+ return (
+
+
+ Loading tools...
+
+ )
+ }
+
+ return (
+
+ {/* Header: Refresh button */}
+
+
+ {tools.filter((t) => t.enabled).length} of {tools.length} tools enabled
+
+
+
+
+
+ {/* Error message */}
+ {error && (
+
+ {error}
+
+ )}
+
+ {/* Tool groups */}
+
+ {toolsByGroup.map((group) => {
+ const isExpanded = expandedGroups.has(group.id)
+ const GroupIcon = GROUP_ICONS[group.id] || CodeIcon
+
+ return (
+
+ {/* Group header */}
+
+
+ {/* Group tools */}
+ {isExpanded && (
+
+ {group.tools.map((tool) => {
+ const isToggling = togglingTools.has(tool.name)
+
+ return (
+
+
+
+
+ {tool.name}
+
+ {!tool.enabled && (
+
+ disabled
+
+ )}
+
+ {tool.description && (
+
+ {tool.description}
+
+ )}
+
+
+ {isToggling && (
+
+ )}
+ handleToggleTool(tool.name)}
+ disabled={isToggling}
+ />
+
+
+ )
+ })}
+
+ )}
+
+ )
+ })}
+
+
+ {/* Note about persistence */}
+
+ Changes are saved automatically and apply to the running Agent immediately.
+
+
+ )
+}
+
+export default ToolList
diff --git a/packages/ui/src/components/ui/dialog.tsx b/packages/ui/src/components/ui/dialog.tsx
new file mode 100644
index 000000000..1e14624a2
--- /dev/null
+++ b/packages/ui/src/components/ui/dialog.tsx
@@ -0,0 +1,140 @@
+"use client"
+
+import * as React from "react"
+import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"
+
+import { cn } from "@multica/ui/lib/utils"
+import { Button } from "@multica/ui/components/ui/button"
+import { HugeiconsIcon } from "@hugeicons/react"
+import { Cancel01Icon } from "@hugeicons/core-free-icons"
+
+function Dialog({ ...props }: DialogPrimitive.Root.Props) {
+ return
+}
+
+function DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {
+ return
+}
+
+function DialogClose({ ...props }: DialogPrimitive.Close.Props) {
+ return
+}
+
+function DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {
+ return
+}
+
+function DialogOverlay({
+ className,
+ ...props
+}: DialogPrimitive.Backdrop.Props) {
+ return (
+
+ )
+}
+
+function DialogContent({
+ className,
+ children,
+ showCloseButton = true,
+ ...props
+}: DialogPrimitive.Popup.Props & {
+ showCloseButton?: boolean
+}) {
+ return (
+
+
+
+ {children}
+ {showCloseButton && (
+
+ }
+ >
+
+ Close
+
+ )}
+
+
+ )
+}
+
+function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {
+ return (
+
+ )
+}
+
+function DialogDescription({
+ className,
+ ...props
+}: DialogPrimitive.Description.Props) {
+ return (
+
+ )
+}
+
+export {
+ Dialog,
+ DialogTrigger,
+ DialogClose,
+ DialogContent,
+ DialogHeader,
+ DialogFooter,
+ DialogTitle,
+ DialogDescription,
+ DialogOverlay,
+ DialogPortal,
+}