diff --git a/app/knip.json b/app/knip.json
new file mode 100644
index 000000000..668da51ad
--- /dev/null
+++ b/app/knip.json
@@ -0,0 +1,27 @@
+{
+ "$schema": "https://unpkg.com/knip@6/schema.json",
+ "entry": ["src/main.tsx", "test/e2e/specs/**/*.spec.ts"],
+ "project": [
+ "src/**/*.{ts,tsx}",
+ "test/**/*.{ts,tsx}",
+ "test/e2e/globals.d.ts",
+ "*.config.{js,ts}"
+ ],
+ "ignoreDependencies": [
+ "@tauri-apps/cli",
+ "@testing-library/dom",
+ "@wdio/appium-service",
+ "@wdio/cli",
+ "@wdio/local-runner",
+ "@wdio/mocha-framework",
+ "@wdio/spec-reporter",
+ "buffer",
+ "eslint",
+ "husky",
+ "os-browserify",
+ "prettier",
+ "process",
+ "util"
+ ],
+ "ignoreBinaries": ["eslint", "knip", "open", "prettier", "tauri", "tsc", "vite", "vitest"]
+}
diff --git a/app/package.json b/app/package.json
index b89ad3c1c..95d06bd69 100644
--- a/app/package.json
+++ b/app/package.json
@@ -44,10 +44,10 @@
"format:check": "prettier --check . && yarn rust:format:check",
"lint": "eslint . --ext .ts,.tsx",
"lint:fix": "eslint . --ext .ts,.tsx --fix",
- "knip": "knip"
+ "knip": "knip --config knip.json",
+ "knip:production": "knip --config knip.json --production"
},
"dependencies": {
- "@heroicons/react": "^2.2.0",
"@noble/hashes": "^2.0.1",
"@noble/secp256k1": "^3.0.0",
"@reduxjs/toolkit": "^2.11.2",
diff --git a/app/src/__tests__/setup.ts b/app/src/__tests__/setup.ts
deleted file mode 100644
index 193e8d302..000000000
--- a/app/src/__tests__/setup.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * Global test setup — runs before every test file.
- *
- * - Silences console output during tests (unless DEBUG_TESTS=1)
- * - Resets rate limiter module-level state between tests
- */
-import { beforeEach, vi } from 'vitest';
-
-// Silence console during tests to keep output clean
-if (!process.env.DEBUG_TESTS) {
- vi.spyOn(console, 'log').mockImplementation(() => {});
- vi.spyOn(console, 'warn').mockImplementation(() => {});
- vi.spyOn(console, 'error').mockImplementation(() => {});
-}
-
-// Reset rate limiter per-request counter before each test.
-// The module keeps mutable state (callHistory, lastCallTime, callsInCurrentRequest)
-// that leaks between tests if not cleared.
-beforeEach(async () => {
- try {
- const { resetRequestCallCount } = await import('../lib/mcp/rateLimiter');
- if (typeof resetRequestCallCount === 'function') {
- resetRequestCallCount();
- }
- } catch {
- // Module may be fully mocked in some test files — safe to skip
- }
-});
diff --git a/app/src/assets/icons/ExchangeIcon.tsx b/app/src/assets/icons/ExchangeIcon.tsx
deleted file mode 100644
index b83dfe738..000000000
--- a/app/src/assets/icons/ExchangeIcon.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-const ExchangeIcon = ({ className = 'w-5 h-5' }: { className?: string }) => {
- return (
-
-
-
- );
-};
-
-export default ExchangeIcon;
diff --git a/app/src/assets/icons/GmailIcon.tsx b/app/src/assets/icons/GmailIcon.tsx
deleted file mode 100644
index c55f5e45c..000000000
--- a/app/src/assets/icons/GmailIcon.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-const GmailIcon = ({ className = 'w-4 h-4' }: { className?: string }) => {
- return (
-
-
-
-
- );
-};
-
-export default GmailIcon;
diff --git a/app/src/assets/icons/WalletIcon.tsx b/app/src/assets/icons/WalletIcon.tsx
deleted file mode 100644
index 18d7daec5..000000000
--- a/app/src/assets/icons/WalletIcon.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-const WalletIcon = ({ className = 'w-5 h-5' }: { className?: string }) => {
- return (
-
-
-
- );
-};
-
-export default WalletIcon;
diff --git a/app/src/components/DesignSystemShowcase.tsx b/app/src/components/DesignSystemShowcase.tsx
deleted file mode 100644
index 70beecccb..000000000
--- a/app/src/components/DesignSystemShowcase.tsx
+++ /dev/null
@@ -1,394 +0,0 @@
-import React from 'react';
-
-/**
- * Design System Showcase Component
- *
- * This component demonstrates the premium design system created for the crypto platform.
- * It showcases all the major components and styles that capture the calm, trustworthy
- * aesthetic from the reference screenshots while elevating it for professional crypto users.
- */
-
-const DesignSystemShowcase: React.FC = () => {
- return (
-
- {/* Hero Section - Similar to Screenshot #1 but elevated */}
-
- {/* Atmospheric Background */}
-
-
-
-
-
- {/* Greeting Section */}
-
-
- Good afternoon,
- Cyrus
-
-
Your portfolio is up 12.5% today
-
- {/* Status Card */}
-
-
-
- {/* Quick Actions */}
-
-
-
-
-
- Start Trading
-
-
View Portfolio
-
Market Analysis
-
-
-
-
- {/* Color Palette Section */}
-
- Color System
-
-
- {/* Primary Colors */}
-
-
Primary Ocean
-
-
-
-
-
Primary 500
-
#5B9BF3
-
-
-
-
-
-
Primary 600
-
#4A83DD
-
-
-
-
-
- {/* Success Colors */}
-
-
- {/* Accent Colors */}
-
-
-
-
- {/* Typography Section */}
-
- Typography Scale
-
-
-
-
Display · 4.5rem
-
Beautiful Typography
-
-
-
-
Heading 1 · 3rem
-
- Clear Information Hierarchy
-
-
-
-
-
Heading 2 · 1.875rem
-
- Structured Content Design
-
-
-
-
-
Body · 1rem
-
- The typography system is designed to create clear visual hierarchy while maintaining
- excellent readability. Each text size has been carefully calibrated with appropriate
- line heights and letter spacing to ensure optimal legibility across all device sizes.
-
-
-
-
-
Monospace · For prices and data
-
- $48,392.50 +2.45%
-
-
-
-
-
- {/* Card Components Section */}
-
- Card Components
-
-
- {/* Market Card */}
-
-
-
-
-
-
$48,392.50
-
- +$1,185.20
- (+2.45%)
-
-
-
-
-
- {/* Interactive Card */}
-
-
Portfolio Overview
-
- Track your investments and monitor performance in real-time.
-
-
-
-
- {/* Elevated Card */}
-
-
-
-
- Total Value
- $125,430
-
-
- 24h Change
- +3.2%
-
-
- Holdings
- 12
-
-
-
-
-
-
- {/* Navigation Pattern - Similar to Screenshot #2 */}
-
-
- Navigation & Settings
-
-
-
-
-
- {/* Form Elements */}
-
- Form Elements
-
-
-
-
- Email Address
-
-
-
-
-
- Investment Amount
-
-
- $
-
-
-
-
-
-
- Select Currency
-
-
- Bitcoin (BTC)
- Ethereum (ETH)
- USDT
-
-
-
-
-
-
- I agree to the terms and conditions
-
-
-
-
Complete Transaction
-
-
-
-
- );
-};
-
-export default DesignSystemShowcase;
diff --git a/app/src/components/GmailConnectionIndicator.tsx b/app/src/components/GmailConnectionIndicator.tsx
deleted file mode 100644
index bc26dabfd..000000000
--- a/app/src/components/GmailConnectionIndicator.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import GmailIcon from '../assets/icons/GmailIcon';
-
-interface GmailConnectionIndicatorProps {
- description?: string;
- className?: string;
-}
-
-const GmailConnectionIndicator = ({
- description,
- className = '',
-}: GmailConnectionIndicatorProps) => {
- // Gmail is always offline for now (placeholder)
- const gmailIsOnline = false;
-
- return (
-
-
-
-
-
-
- {gmailIsOnline ? 'Connected to Gmail' : 'Gmail is Offline'}
-
-
-
- {description && (
-
{description}
- )}
-
- );
-};
-
-export default GmailConnectionIndicator;
diff --git a/app/src/components/MiniSidebar.tsx b/app/src/components/MiniSidebar.tsx
deleted file mode 100644
index 636614d33..000000000
--- a/app/src/components/MiniSidebar.tsx
+++ /dev/null
@@ -1,242 +0,0 @@
-import { useState } from 'react';
-import { useLocation, useNavigate } from 'react-router-dom';
-
-import { useCoreState } from '../providers/CoreStateProvider';
-import { useAppSelector } from '../store/hooks';
-import { isTauri } from '../utils/tauriCommands';
-import DaemonHealthIndicator from './daemon/DaemonHealthIndicator';
-import DaemonHealthPanel from './daemon/DaemonHealthPanel';
-
-const navItems = [
- {
- id: 'home',
- label: 'Home',
- path: '/home',
- icon: (
-
-
-
- ),
- },
- {
- id: 'skills',
- label: 'Skills',
- path: '/skills',
- icon: (
-
-
-
- ),
- },
- {
- id: 'conversations',
- label: 'Conversations',
- path: '/conversations',
- icon: (
-
-
-
- ),
- },
- {
- id: 'intelligence',
- label: 'Intelligence',
- path: '/intelligence',
- icon: (
-
-
-
- ),
- },
- // {
- // id: 'invites',
- // label: 'Invite Friends',
- // path: '/invites',
- // icon: (
- //
- //
- //
- // ),
- // },
- {
- id: 'settings',
- label: 'Settings',
- path: '/settings',
- icon: (
-
-
-
-
- ),
- },
- {
- id: 'channels',
- label: 'Channels',
- path: '/channels',
- icon: (
-
-
-
- ),
- },
- {
- id: 'cron-jobs',
- label: 'Cron Jobs',
- path: '/settings/cron-jobs',
- icon: (
-
-
-
- ),
- },
-];
-
-const MiniSidebar = () => {
- const location = useLocation();
- const navigate = useNavigate();
- const { snapshot } = useCoreState();
- const token = snapshot.sessionToken;
- const [showDaemonPanel, setShowDaemonPanel] = useState(false);
-
- // Unread count for Conversations: threads with lastMessageAt > lastViewedAt (must be before early return)
- const conversationsUnreadCount = useAppSelector(state => {
- const { threads, lastViewedAt } = state.thread;
- if (threads.length === 0) return 0;
- return threads.filter(t => {
- const viewed = lastViewedAt[t.id];
- const lastMsg = new Date(t.lastMessageAt || t.createdAt).getTime();
- return viewed == null || lastMsg > viewed;
- }).length;
- });
-
- // Hide sidebar on public/setup routes and when not authenticated.
- const hiddenPaths = ['/', '/login'];
- if (
- !token ||
- hiddenPaths.some(path => location.pathname === path || location.pathname.startsWith(`${path}/`))
- ) {
- return null;
- }
-
- const isActive = (path: string) => {
- if (path === '/settings') {
- return (
- location.pathname === '/settings' ||
- (location.pathname.startsWith('/settings/') &&
- !location.pathname.startsWith('/settings/cron-jobs'))
- );
- }
- if (path === '/channels') return location.pathname.startsWith('/channels');
- if (path === '/settings/cron-jobs') return location.pathname.startsWith('/settings/cron-jobs');
- if (path === '/conversations') return location.pathname.startsWith('/conversations');
- return location.pathname === path;
- };
-
- return (
- <>
-
- {/* Navigation Items */}
-
- {navItems.map(item => {
- const active = isActive(item.path);
- const showUnreadBadge = item.id === 'conversations' && conversationsUnreadCount > 0;
- return (
-
-
navigate(item.path)}
- className={`w-10 h-10 flex items-center justify-center rounded-xl transition-all duration-200 cursor-pointer ${
- active
- ? 'bg-white/10 text-black'
- : 'text-stone-500 hover:text-stone-300 hover:bg-white/5'
- }`}
- aria-label={item.label}>
- {item.icon}
-
- {showUnreadBadge && (
-
- {conversationsUnreadCount > 99 ? '99+' : conversationsUnreadCount}
-
- )}
- {/* Tooltip - appears to the right */}
-
- {item.label}
-
-
- );
- })}
-
-
- {/* Daemon Health Indicator - Only show in Tauri mode */}
- {isTauri() && (
-
-
- setShowDaemonPanel(true)} />
-
- {/* Tooltip */}
-
- Agent Status
-
-
- )}
-
-
- {/* Daemon Health Panel Modal */}
- {showDaemonPanel && (
-
-
- setShowDaemonPanel(false)} />
-
-
- )}
- >
- );
-};
-
-export default MiniSidebar;
diff --git a/app/src/components/PrivacyFeatureCard.tsx b/app/src/components/PrivacyFeatureCard.tsx
deleted file mode 100644
index 02522d7c1..000000000
--- a/app/src/components/PrivacyFeatureCard.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-interface PrivacyFeatureCardProps {
- title: string;
- description: string;
-}
-
-const PrivacyFeatureCard = ({ title, description }: PrivacyFeatureCardProps) => {
- return (
-
-
-
-
{title}
-
{description}
-
-
-
- );
-};
-
-export default PrivacyFeatureCard;
diff --git a/app/src/components/TelegramConnectionIndicator.tsx b/app/src/components/TelegramConnectionIndicator.tsx
deleted file mode 100644
index de5fcea7a..000000000
--- a/app/src/components/TelegramConnectionIndicator.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import TelegramIcon from '../assets/icons/telegram.svg';
-import { useSkillConnectionStatus } from '../lib/skills/hooks';
-import type { SkillConnectionStatus } from '../lib/skills/types';
-
-interface TelegramConnectionIndicatorProps {
- className?: string;
-}
-
-const STATUS_CONFIG: Record = {
- connected: { dot: 'bg-sage-500', text: 'text-sage-500', label: 'Telegram Connected' },
- connecting: { dot: 'bg-amber-500', text: 'text-amber-500', label: 'Telegram Connecting...' },
- not_authenticated: {
- dot: 'bg-amber-500',
- text: 'text-amber-500',
- label: 'Telegram Not Authenticated',
- },
- disconnected: { dot: 'bg-gray-500', text: 'text-gray-500', label: 'Telegram Disconnected' },
- error: { dot: 'bg-coral-500', text: 'text-coral-500', label: 'Telegram Error' },
- offline: { dot: 'bg-gray-500', text: 'text-gray-500', label: 'Telegram Offline' },
- setup_required: { dot: 'bg-gray-500', text: 'text-gray-500', label: 'Telegram Not Set Up' },
-};
-
-const TelegramConnectionIndicator = ({ className = '' }: TelegramConnectionIndicatorProps) => {
- const status = useSkillConnectionStatus('telegram');
- const config = STATUS_CONFIG[status];
- const isActive = status === 'connected';
-
- return (
-
-
-
-
-
-
{config.label}
-
-
-
- );
-};
-
-export default TelegramConnectionIndicator;
diff --git a/app/src/components/TypewriterGreeting.tsx b/app/src/components/TypewriterGreeting.tsx
deleted file mode 100644
index 2763db4bf..000000000
--- a/app/src/components/TypewriterGreeting.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import { useEffect, useState } from 'react';
-
-interface TypewriterGreetingProps {
- greetings: string[];
- typingSpeed?: number;
- deletingSpeed?: number;
- pauseTime?: number;
- className?: string;
-}
-
-const TypewriterGreeting = ({
- greetings,
- typingSpeed = 100,
- deletingSpeed = 50,
- pauseTime = 2000,
- className = '',
-}: TypewriterGreetingProps) => {
- const [currentGreetingIndex, setCurrentGreetingIndex] = useState(0);
- const [displayedText, setDisplayedText] = useState('');
- const [isDeleting, setIsDeleting] = useState(false);
- const [isPaused, setIsPaused] = useState(false);
-
- useEffect(() => {
- if (greetings.length === 0) return;
-
- const currentGreeting = greetings[currentGreetingIndex];
- const speed = isDeleting ? deletingSpeed : typingSpeed;
-
- if (isPaused) {
- const pauseTimer = setTimeout(() => {
- setIsPaused(false);
- setIsDeleting(true);
- }, pauseTime);
- return () => clearTimeout(pauseTimer);
- }
-
- const timer = setTimeout(() => {
- if (!isDeleting) {
- // Typing
- if (displayedText.length < currentGreeting.length) {
- setDisplayedText(currentGreeting.slice(0, displayedText.length + 1));
- } else {
- // Finished typing, pause before deleting
- setIsPaused(true);
- }
- } else {
- // Deleting
- if (displayedText.length > 0) {
- setDisplayedText(displayedText.slice(0, -1));
- } else {
- // Finished deleting, move to next greeting
- setIsDeleting(false);
- setCurrentGreetingIndex(prev => (prev + 1) % greetings.length);
- }
- }
- }, speed);
-
- return () => clearTimeout(timer);
- }, [
- displayedText,
- isDeleting,
- isPaused,
- currentGreetingIndex,
- greetings,
- typingSpeed,
- deletingSpeed,
- pauseTime,
- ]);
-
- return (
-
- {displayedText}
- |
-
- );
-};
-
-export default TypewriterGreeting;
diff --git a/app/src/components/WalletInfoSection.tsx b/app/src/components/WalletInfoSection.tsx
deleted file mode 100644
index 74ca795c8..000000000
--- a/app/src/components/WalletInfoSection.tsx
+++ /dev/null
@@ -1,236 +0,0 @@
-import { useCallback, useEffect, useRef, useState } from 'react';
-
-import { useUser } from '../hooks/useUser';
-import { useSkillConnectionStatus } from '../lib/skills/hooks';
-import { skillManager } from '../lib/skills/manager';
-import { useCoreState } from '../providers/CoreStateProvider';
-import { buildManualSentryEvent, enqueueError } from '../services/errorReportQueue';
-
-function truncateAddress(address: string): string {
- if (!address || address.length < 12) return address;
- return `${address.slice(0, 6)}…${address.slice(-4)}`;
-}
-
-export default function WalletInfoSection() {
- const walletStatus = useSkillConnectionStatus('wallet');
- const { user } = useUser();
- const { snapshot } = useCoreState();
- const primaryAddress = user?._id ? snapshot.localState.primaryWalletAddress : undefined;
-
- const [networkName, setNetworkName] = useState(null);
- const [balance, setBalance] = useState(null);
- const [loading, setLoading] = useState(false);
- const [error, setError] = useState(null);
-
- const isConnected = walletStatus === 'connected' && !!primaryAddress;
- const cancelledRef = useRef(false);
- const retryTimerRef = useRef | null>(null);
- const fetchWalletInfoRef = useRef<(address: string, attempt?: number) => Promise>(null!);
-
- const fetchWalletInfo = useCallback(async (address: string, attempt = 0): Promise => {
- if (cancelledRef.current) return;
-
- if (!skillManager.isSkillRunning('wallet')) {
- if (attempt < 5 && !cancelledRef.current) {
- retryTimerRef.current = setTimeout(
- () => fetchWalletInfoRef.current(address, attempt + 1),
- 1500
- );
- return;
- }
- if (!cancelledRef.current) {
- setLoading(false);
- setNetworkName(null);
- setBalance(null);
- setError(null);
- }
- return;
- }
-
- try {
- // Wallet skill only supports Ethereum Mainnet (chain_id "1")
- const listRes = await skillManager.callTool('wallet', 'list_networks', {});
- if (cancelledRef.current) return;
- const listText = listRes.content?.[0]?.text;
- if (!listText || listRes.isError) {
- if (!cancelledRef.current) {
- setError('Could not load networks');
- setLoading(false);
- }
- return;
- }
- const listData = JSON.parse(listText) as {
- networks?: Array<{ chain_id?: string; name?: string; chain_type?: string }>;
- };
- const networks = Array.isArray(listData.networks) ? listData.networks : [];
- // Prefer Ethereum Mainnet (skill always has it); else first EVM, else first network
- const ethMainnet = networks.find(n => n?.chain_id === '1' && n?.chain_type === 'evm');
- const firstEvm = networks.find(n => n && n.chain_type === 'evm');
- const chosen = ethMainnet ?? firstEvm ?? networks.find(Boolean);
- if (!chosen || cancelledRef.current) {
- if (!cancelledRef.current) setLoading(false);
- return;
- }
-
- const networkNameVal = chosen.name ?? chosen.chain_id ?? 'Unknown';
- if (!cancelledRef.current) setNetworkName(networkNameVal);
-
- const chainId = chosen.chain_id ?? '';
- if (!chainId) {
- if (!cancelledRef.current) {
- setBalance('—');
- setLoading(false);
- }
- return;
- }
-
- const balanceRes = await skillManager.callTool('wallet', 'get_balance', {
- address,
- chain_id: chainId,
- chain_type: chosen.chain_type ?? 'evm',
- });
- if (cancelledRef.current) return;
- const balanceText = balanceRes.content?.[0]?.text;
- if (!balanceText || balanceRes.isError) {
- if (!cancelledRef.current) {
- setBalance('—');
- setLoading(false);
- }
- return;
- }
- const balanceData = JSON.parse(balanceText) as {
- balance_eth?: string;
- symbol?: string;
- error?: string;
- };
- if (balanceData.error) {
- if (!cancelledRef.current) {
- setBalance('—');
- setLoading(false);
- }
- return;
- }
- const eth = balanceData.balance_eth ?? '0';
- const symbol = balanceData.symbol ?? 'ETH';
- const parsed = parseFloat(eth);
- const value = Number.isFinite(parsed) ? parsed : 0;
- const display = value < 0.0001 ? '0' : value.toFixed(4);
- if (!cancelledRef.current) {
- setBalance(`${display} ${symbol}`);
- setLoading(false);
- }
- } catch (e) {
- if (!cancelledRef.current) {
- const msg = e instanceof Error ? e.message : String(e);
- const isTransient =
- msg.includes('not running') || msg.includes('not started') || msg.includes('transport');
- if (isTransient && attempt < 3) {
- retryTimerRef.current = setTimeout(
- () => fetchWalletInfoRef.current(address, attempt + 1),
- 2000
- );
- return;
- }
- console.error('[WalletInfoSection] Failed to load wallet info:', e);
- enqueueError({
- id: crypto.randomUUID(),
- timestamp: Date.now(),
- source: 'manual',
- title: 'Failed to load wallet info',
- message: e instanceof Error ? e.message : String(e),
- sentryEvent: buildManualSentryEvent(
- { type: 'WalletInfoLoadError', value: e instanceof Error ? e.message : String(e) },
- { component: 'WalletInfoSection' }
- ),
- originalError: e instanceof Error ? e : new Error(String(e)),
- });
- setError('Failed to load wallet info');
- setBalance(null);
- setNetworkName(null);
- setLoading(false);
- }
- }
- }, []);
-
- fetchWalletInfoRef.current = fetchWalletInfo;
-
- useEffect(() => {
- cancelledRef.current = false;
- if (retryTimerRef.current) {
- clearTimeout(retryTimerRef.current);
- retryTimerRef.current = null;
- }
-
- if (!isConnected || !primaryAddress) {
- setLoading(false);
- setNetworkName(null);
- setBalance(null);
- setError(null);
- return;
- }
-
- setLoading(true);
- setError(null);
-
- fetchWalletInfo(primaryAddress);
-
- return () => {
- cancelledRef.current = true;
- if (retryTimerRef.current) clearTimeout(retryTimerRef.current);
- };
- }, [isConnected, primaryAddress, fetchWalletInfo]);
-
- if (!isConnected) return null;
-
- return (
-
-
-
-
- Address
-
- {primaryAddress ? truncateAddress(primaryAddress) : '—'}
-
-
-
- Network
-
- {loading ? (
- Loading…
- ) : error ? (
- {error}
- ) : (
- (networkName ?? '—')
- )}
-
-
-
- Balance
-
- {loading ? (
- Loading…
- ) : error ? (
- '—'
- ) : (
- (balance ?? '—')
- )}
-
-
-
-
- );
-}
diff --git a/app/src/components/daemon/DaemonHealthIndicator.tsx b/app/src/components/daemon/DaemonHealthIndicator.tsx
deleted file mode 100644
index ca59aeb75..000000000
--- a/app/src/components/daemon/DaemonHealthIndicator.tsx
+++ /dev/null
@@ -1,108 +0,0 @@
-/**
- * Daemon Health Indicator
- *
- * Compact status indicator showing daemon health with a colored dot and optional label.
- * Can be clicked to show detailed health information.
- */
-import type React from 'react';
-
-import type { DaemonStatus } from '../../features/daemon/store';
-import { formatRelativeTime, useDaemonHealth } from '../../hooks/useDaemonHealth';
-
-interface Props {
- userId?: string;
- size?: 'sm' | 'md' | 'lg';
- showLabel?: boolean;
- onClick?: () => void;
- className?: string;
-}
-
-const DaemonHealthIndicator: React.FC = ({
- userId,
- size = 'md',
- showLabel = false,
- onClick,
- className = '',
-}) => {
- const daemonHealth = useDaemonHealth(userId);
-
- // Size configurations
- const sizeConfig = {
- sm: { dot: 'w-2 h-2', text: 'text-xs', container: 'gap-1.5' },
- md: { dot: 'w-3 h-3', text: 'text-sm', container: 'gap-2' },
- lg: { dot: 'w-4 h-4', text: 'text-base', container: 'gap-2.5' },
- };
-
- const config = sizeConfig[size];
-
- // Status color mapping
- const getStatusColor = (status: DaemonStatus): string => {
- switch (status) {
- case 'running':
- return 'bg-green-500';
- case 'starting':
- return 'bg-yellow-500';
- case 'error':
- return 'bg-red-500';
- case 'disconnected':
- default:
- return 'bg-gray-500';
- }
- };
-
- // Status text mapping
- const getStatusText = (status: DaemonStatus): string => {
- switch (status) {
- case 'running':
- return 'Running';
- case 'starting':
- return 'Starting';
- case 'error':
- return 'Error';
- case 'disconnected':
- default:
- return 'Disconnected';
- }
- };
-
- // Tooltip content
- const getTooltipContent = (): string => {
- const { status, componentCount, healthyComponentCount, errorComponentCount, lastUpdate } =
- daemonHealth;
-
- let tooltip = `Status: ${getStatusText(status)}`;
-
- if (componentCount > 0) {
- tooltip += `\nComponents: ${healthyComponentCount}/${componentCount} healthy`;
- if (errorComponentCount > 0) {
- tooltip += ` (${errorComponentCount} errors)`;
- }
- }
-
- if (lastUpdate) {
- tooltip += `\nLast update: ${formatRelativeTime(lastUpdate)}`;
- }
-
- return tooltip;
- };
-
- const statusColor = getStatusColor(daemonHealth.status);
- const statusText = getStatusText(daemonHealth.status);
-
- const containerClasses = `
- flex items-center ${config.container}
- ${onClick ? 'cursor-pointer hover:opacity-80 transition-opacity' : ''}
- ${className}
- `.trim();
-
- return (
-
-
- {showLabel && (
-
{statusText}
- )}
-
- );
-};
-
-export default DaemonHealthIndicator;
diff --git a/app/src/components/daemon/DaemonHealthPanel.tsx b/app/src/components/daemon/DaemonHealthPanel.tsx
deleted file mode 100644
index dc1525177..000000000
--- a/app/src/components/daemon/DaemonHealthPanel.tsx
+++ /dev/null
@@ -1,322 +0,0 @@
-/**
- * Agent Health Panel
- *
- * Detailed health breakdown component showing agent status, component health,
- * and providing manual control buttons for agent lifecycle management.
- */
-import {
- ArrowPathIcon,
- CheckCircleIcon,
- ClockIcon,
- PlayIcon,
- StopIcon,
- XCircleIcon,
- XMarkIcon,
-} from '@heroicons/react/24/outline';
-import { useEffect, useState } from 'react';
-
-import type { ComponentHealth, DaemonStatus } from '../../features/daemon/store';
-import { formatRelativeTime, useDaemonHealth } from '../../hooks/useDaemonHealth';
-import { IS_DEV } from '../../utils/config';
-import {
- isTauri,
- openhumanGetDaemonHostConfig,
- openhumanSetDaemonHostConfig,
-} from '../../utils/tauriCommands';
-
-interface Props {
- userId?: string;
- onClose?: () => void;
- className?: string;
-}
-
-const DaemonHealthPanel = ({ userId, onClose, className = '' }: Props) => {
- const daemonHealth = useDaemonHealth(userId);
- const [operationLoading, setOperationLoading] = useState(null);
- const [showTray, setShowTray] = useState(true);
- const [trayLoaded, setTrayLoaded] = useState(false);
-
- // Handle agent operations with loading states
- const handleOperation = async (operation: () => Promise, operationName: string) => {
- setOperationLoading(operationName);
- try {
- await operation();
- } catch (error) {
- console.error(`[AgentHealthPanel] ${operationName} failed:`, error);
- } finally {
- setOperationLoading(null);
- }
- };
-
- // Status styling
- const getStatusStyling = (status: DaemonStatus) => {
- switch (status) {
- case 'running':
- return {
- bg: 'bg-green-900/20 border-green-500/30',
- text: 'text-green-400',
- icon: CheckCircleIcon,
- };
- case 'starting':
- return {
- bg: 'bg-yellow-900/20 border-yellow-500/30',
- text: 'text-yellow-400',
- icon: ClockIcon,
- };
- case 'error':
- return { bg: 'bg-red-900/20 border-red-500/30', text: 'text-red-400', icon: XCircleIcon };
- case 'disconnected':
- default:
- return {
- bg: 'bg-gray-900/20 border-gray-500/30',
- text: 'text-gray-400',
- icon: XCircleIcon,
- };
- }
- };
-
- // Component status styling
- const getComponentStyling = (component: ComponentHealth) => {
- switch (component.status) {
- case 'ok':
- return { bg: 'bg-green-500', text: 'text-green-400', icon: CheckCircleIcon };
- case 'error':
- return { bg: 'bg-red-500', text: 'text-red-400', icon: XCircleIcon };
- case 'starting':
- return { bg: 'bg-yellow-500', text: 'text-yellow-400', icon: ClockIcon };
- }
- };
-
- const statusStyling = getStatusStyling(daemonHealth.status);
- const StatusIcon = statusStyling.icon;
-
- useEffect(() => {
- if (!isTauri()) {
- return;
- }
- const loadTray = async () => {
- try {
- const result = await openhumanGetDaemonHostConfig();
- setShowTray(result.result.show_tray);
- setTrayLoaded(true);
- } catch (error) {
- console.error('[AgentHealthPanel] Failed to load daemon host config:', error);
- }
- };
- void loadTray();
- }, []);
-
- const updateTraySetting = async (enabled: boolean) => {
- if (!isTauri()) {
- return;
- }
- const previous = showTray;
- setShowTray(enabled);
- setOperationLoading('tray');
- try {
- await openhumanSetDaemonHostConfig(enabled);
- } catch (error) {
- setShowTray(previous);
- console.error('[AgentHealthPanel] Failed to save daemon host config:', error);
- } finally {
- setOperationLoading(null);
- }
- };
-
- return (
-
- {/* Header */}
-
-
Agent Status
- {onClose && (
-
-
-
- )}
-
-
- {/* Overall Status */}
-
-
-
-
-
-
- Status: {daemonHealth.status.charAt(0).toUpperCase() + daemonHealth.status.slice(1)}
-
- {daemonHealth.healthSnapshot && (
-
- PID: {daemonHealth.healthSnapshot.pid} • Uptime: {daemonHealth.uptimeText}
-
- )}
- {daemonHealth.lastUpdate && (
-
- Last update: {formatRelativeTime(daemonHealth.lastUpdate)}
-
- )}
-
-
-
- {/* Recovery indicator */}
- {daemonHealth.isRecovering && (
-
- )}
-
-
-
- {/* Component Health */}
- {daemonHealth.componentCount > 0 && (
-
-
- Components ({daemonHealth.healthyComponentCount}/{daemonHealth.componentCount} healthy)
-
-
- {Object.entries(daemonHealth.components).map(([name, component]) => {
- const componentStyling = getComponentStyling(component);
- const ComponentIcon = componentStyling.icon;
-
- return (
-
-
-
-
-
- {name}
-
-
- Updated: {formatRelativeTime(component.updated_at)}
-
- {component.restart_count > 0 && (
-
- Restarts: {component.restart_count}
-
- )}
- {component.last_error && component.status === 'error' && (
-
- Error: {component.last_error}
-
- )}
-
-
- );
- })}
-
-
- )}
-
- {/* Auto-start Toggle */}
-
-
-
Auto-start Agent
-
Automatically start agent on app launch
-
-
- daemonHealth.setAutoStart(e.target.checked)}
- />
-
-
-
-
-
-
-
Show Daemon Tray
-
Display tray icon in daemon host mode
-
Requires daemon host restart to apply
-
-
- {
- void updateTraySetting(e.target.checked);
- }}
- />
-
-
-
-
- {/* Control Actions */}
-
-
handleOperation(daemonHealth.startDaemon, 'start')}
- disabled={daemonHealth.status === 'running' || operationLoading !== null}
- className="inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-white bg-green-600 hover:bg-green-700 disabled:bg-gray-600 disabled:cursor-not-allowed rounded-lg transition-colors">
- {operationLoading === 'start' ? (
-
- ) : (
-
- )}
- Start
-
-
-
handleOperation(daemonHealth.stopDaemon, 'stop')}
- disabled={daemonHealth.status === 'disconnected' || operationLoading !== null}
- className="inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-700 disabled:bg-gray-600 disabled:cursor-not-allowed rounded-lg transition-colors">
- {operationLoading === 'stop' ? (
-
- ) : (
-
- )}
- Stop
-
-
-
handleOperation(daemonHealth.restartDaemon, 'restart')}
- disabled={operationLoading !== null}
- className="inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 disabled:bg-gray-600 disabled:cursor-not-allowed rounded-lg transition-colors">
- {operationLoading === 'restart' ? (
-
- ) : (
-
- )}
- Restart
-
-
-
handleOperation(daemonHealth.checkDaemonStatus, 'check')}
- disabled={operationLoading !== null}
- className="inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-gray-300 bg-gray-700 hover:bg-gray-600 disabled:bg-gray-800 disabled:cursor-not-allowed rounded-lg transition-colors">
- {operationLoading === 'check' ? (
-
- ) : (
-
- )}
- Check Status
-
-
-
- {/* Connection Info */}
- {daemonHealth.connectionAttempts > 0 && (
-
-
- Connection attempts: {daemonHealth.connectionAttempts}
-
-
- )}
-
- {/* Debug Info (development only) */}
- {IS_DEV && daemonHealth.healthSnapshot && (
-
- Debug Info
-
- {JSON.stringify(daemonHealth.healthSnapshot, null, 2)}
-
-
- )}
-
- );
-};
-
-export default DaemonHealthPanel;
diff --git a/app/src/components/intelligence/index.ts b/app/src/components/intelligence/index.ts
deleted file mode 100644
index 58b34fc59..000000000
--- a/app/src/components/intelligence/index.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-// Intelligence Components
-// Exports for the AI-powered actionable insights dashboard
-
-export { ActionableCard } from './ActionableCard';
-export { ConfirmationModal } from './ConfirmationModal';
-export { Toast, ToastContainer } from './Toast';
-export { MOCK_ACTIONABLE_ITEMS } from './mockData';
-export { groupItemsByTime, filterItems, getItemStats } from './utils';
-
-// Re-export types for convenience
-export type {
- ActionableItem,
- ActionableItemSource,
- ActionableItemPriority,
- ActionableItemStatus,
- TimeGroup,
- ToastNotification,
- ConfirmationModal as ConfirmationModalType,
-} from '../../types/intelligence';
diff --git a/app/src/components/intelligence/mockData.ts b/app/src/components/intelligence/mockData.ts
deleted file mode 100644
index 87458201d..000000000
--- a/app/src/components/intelligence/mockData.ts
+++ /dev/null
@@ -1,311 +0,0 @@
-import type { ActionableItem } from '../../types/intelligence';
-
-// Helper function to create dates relative to now
-function createDate(minutesAgo: number): Date {
- return new Date(Date.now() - minutesAgo * 60 * 1000);
-}
-
-function createDateDays(daysAgo: number): Date {
- return new Date(Date.now() - daysAgo * 24 * 60 * 60 * 1000);
-}
-
-export const MOCK_ACTIONABLE_ITEMS: ActionableItem[] = [
- // Today - Fresh items
- {
- id: '1',
- title: 'Reply to 2 critical emails expecting response within 24hrs',
- description:
- 'Messages from john@coinbase.com and sarah@ethereum.org about partnership proposals',
- source: 'email',
- priority: 'critical',
- status: 'active',
- createdAt: createDate(2),
- updatedAt: createDate(2),
- expiresAt: createDate(-1440), // Expires in 24 hours
- actionable: true,
- hasComplexAction: true,
- sourceLabel: 'Gmail',
- },
-
- {
- id: '2',
- title: 'Meeting with Sarah in 30 minutes - prepare documents?',
- description:
- 'Q4 strategy meeting. Need to review the crypto market analysis and portfolio updates.',
- source: 'calendar',
- priority: 'important',
- status: 'active',
- createdAt: createDate(5),
- updatedAt: createDate(5),
- actionable: true,
- hasComplexAction: false,
- sourceLabel: 'Calendar',
- },
-
- {
- id: '3',
- title: 'Order lunch for your 2pm work session?',
- description:
- 'You usually order from "Crypto Café" around this time. Your favorite: Bitcoin Bowl ($15.99)',
- source: 'ai_insight',
- priority: 'normal',
- status: 'active',
- createdAt: createDate(15),
- updatedAt: createDate(15),
- actionable: true,
- hasComplexAction: false,
- sourceLabel: 'AI Assistant',
- },
-
- {
- id: '4',
- title: '5 unread messages from Alpha Trading Group',
- description:
- '@cryptowhale mentioned you about the ETH analysis. 3 other traders are discussing market trends.',
- source: 'telegram',
- priority: 'normal',
- status: 'active',
- createdAt: createDate(25),
- updatedAt: createDate(25),
- actionable: true,
- hasComplexAction: true,
- sourceLabel: 'Telegram',
- },
-
- {
- id: '5',
- title: 'Free 45min slot available for gym session',
- description: 'Your calendar shows a gap between 3:15-4:00 PM. LA Fitness has availability.',
- source: 'ai_insight',
- priority: 'normal',
- status: 'active',
- createdAt: createDate(45),
- updatedAt: createDate(45),
- actionable: true,
- hasComplexAction: false,
- sourceLabel: 'AI Planner',
- },
-
- // Yesterday
- {
- id: '6',
- title: 'Alex mentioned you in Alpha Human Development chat',
- description: '"@john can you review the new trading algorithm?" - 3 hours ago',
- source: 'telegram',
- priority: 'important',
- status: 'active',
- createdAt: createDate(180),
- updatedAt: createDate(180),
- actionable: true,
- hasComplexAction: true,
- sourceLabel: 'Telegram',
- },
-
- {
- id: '7',
- title: 'Update available for DeFi trading bot',
- description:
- 'Version 2.1.4 includes security patches and 15% better performance. Requires restart.',
- source: 'system',
- priority: 'important',
- status: 'active',
- createdAt: createDate(360),
- updatedAt: createDate(360),
- actionable: true,
- requiresConfirmation: true,
- sourceLabel: 'System',
- },
-
- {
- id: '8',
- title: 'Backup your crypto wallet keys',
- description: 'Last backup was 30 days ago. Your portfolio is up 34% since then.',
- source: 'security',
- priority: 'critical',
- status: 'active',
- createdAt: createDate(420),
- updatedAt: createDate(420),
- actionable: true,
- requiresConfirmation: false,
- hasComplexAction: true,
- sourceLabel: 'Security',
- },
-
- // This week
- {
- id: '9',
- title: 'Schedule meeting with VCs for Series A discussion',
- description: 'Follow up on last weeks pitch. Andreessen Horowitz and Sequoia are interested.',
- source: 'email',
- priority: 'critical',
- status: 'active',
- createdAt: createDateDays(2),
- updatedAt: createDateDays(2),
- actionable: true,
- hasComplexAction: true,
- sourceLabel: 'Gmail',
- },
-
- {
- id: '10',
- title: 'Review 12 pending GitHub pull requests',
- description:
- 'Features for v0.21.0 release. 8 from team members, 4 from community contributors.',
- source: 'system',
- priority: 'important',
- status: 'active',
- createdAt: createDateDays(3),
- updatedAt: createDateDays(3),
- actionable: true,
- hasComplexAction: true,
- sourceLabel: 'GitHub',
- },
-
- {
- id: '11',
- title: 'BTC price alert: Approaching your target of $65,000',
- description: 'Currently at $64,420. You set this alert 2 weeks ago. Consider taking profits?',
- source: 'trading',
- priority: 'important',
- status: 'active',
- createdAt: createDateDays(3),
- updatedAt: createDateDays(3),
- actionable: true,
- hasComplexAction: false,
- sourceLabel: 'Trading Bot',
- },
-
- {
- id: '12',
- title: 'Complete KYC verification for Binance account',
- description: 'Required for withdrawal limits above $2,000. Upload ID and proof of address.',
- source: 'trading',
- priority: 'normal',
- status: 'active',
- createdAt: createDateDays(4),
- updatedAt: createDateDays(4),
- actionable: true,
- hasComplexAction: true,
- sourceLabel: 'Binance',
- },
-
- // Older items
- {
- id: '13',
- title: 'Respond to podcast interview request from Lex Fridman',
- description: 'Topic: "The Future of Decentralized Communication". Suggested dates: next month.',
- source: 'email',
- priority: 'important',
- status: 'active',
- createdAt: createDateDays(8),
- updatedAt: createDateDays(8),
- actionable: true,
- hasComplexAction: true,
- sourceLabel: 'Gmail',
- },
-
- {
- id: '14',
- title: 'Submit tax documents for Q3 crypto transactions',
- description: '47 transactions across 5 exchanges. Deadline: October 15th.',
- source: 'system',
- priority: 'critical',
- status: 'active',
- createdAt: createDateDays(10),
- updatedAt: createDateDays(10),
- expiresAt: createDate(-14400), // Expires in 10 days
- actionable: true,
- hasComplexAction: true,
- requiresConfirmation: false,
- sourceLabel: 'TaxBot',
- },
-
- {
- id: '15',
- title: 'Renew SSL certificate for api.openhuman.com',
- description:
- 'Certificate expires in 5 days. Automatic renewal failed - manual intervention required.',
- source: 'system',
- priority: 'critical',
- status: 'active',
- createdAt: createDateDays(12),
- updatedAt: createDateDays(12),
- actionable: true,
- hasComplexAction: false,
- requiresConfirmation: false,
- sourceLabel: 'DevOps',
- },
-
- {
- id: '16',
- title: 'Plan team building event for remote employees',
- description:
- '15 team members across 8 time zones. Budget approved: $5,000. Preference: virtual escape room.',
- source: 'ai_insight',
- priority: 'normal',
- status: 'active',
- createdAt: createDateDays(14),
- updatedAt: createDateDays(14),
- actionable: true,
- hasComplexAction: true,
- sourceLabel: 'HR Assistant',
- },
-
- // Expired/old items that should show as low priority
- {
- id: '17',
- title: 'Update LinkedIn profile with latest achievements',
- description: 'Add recent TechCrunch feature and Y Combinator alumni status.',
- source: 'ai_insight',
- priority: 'normal',
- status: 'active',
- createdAt: createDateDays(20),
- updatedAt: createDateDays(20),
- actionable: true,
- hasComplexAction: false,
- sourceLabel: 'Career AI',
- },
-
- {
- id: '18',
- title: 'Review and approve marketing budget for Q4',
- description: '$50K allocated for influencer partnerships and conference sponsorships.',
- source: 'email',
- priority: 'important',
- status: 'active',
- createdAt: createDateDays(21),
- updatedAt: createDateDays(21),
- actionable: true,
- hasComplexAction: true,
- sourceLabel: 'Team Email',
- },
-
- {
- id: '19',
- title: 'Check server capacity for expected user growth',
- description: 'Daily active users grew 23% this month. Current infrastructure may need scaling.',
- source: 'system',
- priority: 'normal',
- status: 'active',
- createdAt: createDateDays(25),
- updatedAt: createDateDays(25),
- actionable: true,
- hasComplexAction: false,
- sourceLabel: 'Monitoring',
- },
-
- {
- id: '20',
- title: 'Organize crypto portfolio - consolidate across 7 wallets',
- description:
- 'Assets spread across MetaMask, Ledger, Trust Wallet, and 4 others. Gas fees optimized for Sunday.',
- source: 'security',
- priority: 'normal',
- status: 'active',
- createdAt: createDateDays(30),
- updatedAt: createDateDays(30),
- actionable: true,
- hasComplexAction: true,
- sourceLabel: 'Portfolio AI',
- },
-];
diff --git a/app/src/components/settings/panels/components/ActionPanel.tsx b/app/src/components/settings/panels/components/ActionPanel.tsx
deleted file mode 100644
index b73d73386..000000000
--- a/app/src/components/settings/panels/components/ActionPanel.tsx
+++ /dev/null
@@ -1,98 +0,0 @@
-import { CheckIcon, ExclamationTriangleIcon } from '@heroicons/react/24/outline';
-import React from 'react';
-
-interface ActionPanelProps {
- children: React.ReactNode;
- hasChanges?: boolean;
- success?: boolean | string;
- error?: string;
- className?: string;
-}
-
-const ActionPanel: React.FC = ({
- children,
- hasChanges = false,
- success = false,
- error,
- className = '',
-}) => {
- return (
-
-
- {children}
- {hasChanges && (
-
- )}
-
-
- {success && (
-
-
- {typeof success === 'string' ? success : 'Operation completed successfully'}
-
- )}
-
- {error && (
-
-
- {error}
-
- )}
-
- );
-};
-
-interface PrimaryButtonProps {
- onClick: () => void;
- loading?: boolean;
- disabled?: boolean;
- variant?: 'primary' | 'secondary' | 'outline';
- children: React.ReactNode;
- className?: string;
-}
-
-const PrimaryButton: React.FC = ({
- onClick,
- loading = false,
- disabled = false,
- variant = 'primary',
- children,
- className = '',
-}) => {
- const baseClasses =
- 'px-6 py-3 rounded-lg font-medium transition-all duration-200 focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed';
- const variantClasses = {
- primary:
- 'bg-primary-600 hover:bg-primary-500 active:bg-primary-700 text-white shadow-soft hover:shadow-lg hover:shadow-primary-500/25 focus:ring-2 focus:ring-primary-500/50 focus:ring-offset-2 focus:ring-offset-white',
- secondary:
- 'bg-stone-100 hover:bg-stone-200 active:bg-stone-300 text-stone-900 border border-stone-200 focus:ring-2 focus:ring-primary-500/50 focus:ring-offset-2 focus:ring-offset-white',
- outline:
- 'border border-stone-200 text-stone-900 hover:bg-stone-100 active:bg-stone-200 focus:ring-2 focus:ring-primary-500/50 focus:ring-offset-2 focus:ring-offset-white',
- };
-
- return (
-
-
- {loading && (
-
- )}
-
{children}
-
-
- );
-};
-
-export default ActionPanel;
-export { PrimaryButton };
diff --git a/app/src/components/settings/panels/components/InputGroup.tsx b/app/src/components/settings/panels/components/InputGroup.tsx
deleted file mode 100644
index 70b8f22de..000000000
--- a/app/src/components/settings/panels/components/InputGroup.tsx
+++ /dev/null
@@ -1,91 +0,0 @@
-import React from 'react';
-
-interface InputGroupProps {
- title?: string;
- description?: string;
- children: React.ReactNode;
- className?: string;
-}
-
-const InputGroup: React.FC = ({
- title,
- description,
- children,
- className = '',
-}) => {
- return (
-
- {title && (
-
-
{title}
- {description &&
{description}
}
-
- )}
-
-
-
- );
-};
-
-interface FieldProps {
- label: string;
- children: React.ReactNode;
- helpText?: string;
- className?: string;
- fullWidth?: boolean;
-}
-
-const Field: React.FC = ({
- label,
- children,
- helpText,
- className = '',
- fullWidth = false,
-}) => {
- return (
-
-
-
{label}
- {helpText &&
{helpText}
}
-
- {children}
-
- );
-};
-
-interface CheckboxFieldProps {
- label: string;
- checked: boolean;
- onChange: (checked: boolean) => void;
- helpText?: string;
- className?: string;
-}
-
-const CheckboxField: React.FC = ({
- label,
- checked,
- onChange,
- helpText,
- className = '',
-}) => {
- return (
-
-
- onChange(e.target.checked)}
- />
- {label}
-
- {helpText &&
{helpText}
}
-
- );
-};
-
-export default InputGroup;
-export { Field, CheckboxField };
diff --git a/app/src/components/settings/panels/components/SectionCard.tsx b/app/src/components/settings/panels/components/SectionCard.tsx
deleted file mode 100644
index 75b7ea011..000000000
--- a/app/src/components/settings/panels/components/SectionCard.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import { ChevronDownIcon, ChevronRightIcon } from '@heroicons/react/24/outline';
-import React, { useState } from 'react';
-
-interface SectionCardProps {
- title: string;
- priority: 'critical' | 'infrastructure' | 'development' | 'tools';
- icon: React.ReactElement;
- children: React.ReactNode;
- collapsible?: boolean;
- defaultExpanded?: boolean;
- hasChanges?: boolean;
- loading?: boolean;
-}
-
-const priorityStyles = {
- critical: 'bg-gradient-to-br from-primary-50 to-white border-primary-200',
- infrastructure: 'bg-gradient-to-br from-stone-50 to-white border-stone-200',
- development: 'bg-gradient-to-br from-amber-50 to-white border-amber-200',
- tools: 'bg-stone-50 border-stone-200',
-} as const;
-
-const priorityIconColors = {
- critical: 'text-primary-500',
- infrastructure: 'text-stone-500',
- development: 'text-amber-500',
- tools: 'text-stone-400',
-} as const;
-
-const SectionCard: React.FC = ({
- title,
- priority,
- icon,
- children,
- collapsible = false,
- defaultExpanded = true,
- hasChanges = false,
- loading = false,
-}) => {
- const [isExpanded, setIsExpanded] = useState(defaultExpanded);
-
- const handleToggle = () => {
- if (collapsible) {
- setIsExpanded(!isExpanded);
- }
- };
-
- return (
-
-
-
-
- {loading ? (
-
- ) : (
- React.cloneElement(icon as React.ReactElement<{ className?: string }>, {
- className: 'h-5 w-5',
- })
- )}
-
-
-
{title}
- {hasChanges &&
}
- {loading &&
Loading... }
-
-
- {collapsible && (
-
- {isExpanded ? (
-
- ) : (
-
- )}
-
- )}
-
-
- {(!collapsible || isExpanded) && (
-
- )}
-
- );
-};
-
-export default SectionCard;
diff --git a/app/src/components/settings/panels/components/ValidatedField.tsx b/app/src/components/settings/panels/components/ValidatedField.tsx
deleted file mode 100644
index 68c425927..000000000
--- a/app/src/components/settings/panels/components/ValidatedField.tsx
+++ /dev/null
@@ -1,187 +0,0 @@
-import { CheckCircleIcon, ExclamationTriangleIcon } from '@heroicons/react/24/outline';
-import React from 'react';
-
-interface ValidatedFieldProps {
- label: string;
- value: string;
- onChange: (value: string) => void;
- error?: string;
- required?: boolean;
- type?: 'text' | 'password' | 'url' | 'number';
- placeholder?: string;
- helpText?: string;
- className?: string;
- fullWidth?: boolean;
- validation?: 'valid' | 'invalid' | 'none';
- disabled?: boolean;
-}
-
-const ValidatedField: React.FC = ({
- label,
- value,
- onChange,
- error,
- required = false,
- type = 'text',
- placeholder,
- helpText,
- className = '',
- fullWidth = false,
- validation = 'none',
- disabled = false,
-}) => {
- const hasError = !!error;
- const isValid = validation === 'valid' && !hasError && value.trim() !== '';
-
- const inputClasses = `
- w-full px-4 py-3 rounded-lg border transition-all duration-200
- focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-white
- disabled:opacity-50 disabled:cursor-not-allowed
- ${
- hasError
- ? 'border-coral-500/60 bg-coral-50 text-coral-700 placeholder-coral-400/50 focus:border-coral-500 focus:ring-coral-500/30'
- : isValid
- ? 'border-sage-500/60 bg-sage-50 text-stone-900 placeholder-stone-400 focus:border-sage-500 focus:ring-sage-500/30'
- : 'border-stone-200 bg-white text-stone-900 placeholder-stone-400 focus:border-primary-500/50 focus:ring-primary-500/30'
- }
- `;
-
- return (
-
-
-
- {label}
- {required && * }
-
- {helpText &&
{helpText}
}
-
-
-
-
onChange(e.target.value)}
- disabled={disabled}
- />
-
- {/* Validation icon */}
- {(hasError || isValid) && (
-
- {hasError ? (
-
- ) : isValid ? (
-
- ) : null}
-
- )}
-
-
- {/* Error message */}
- {hasError && (
-
-
- {error}
-
- )}
-
- );
-};
-
-interface ValidatedSelectProps {
- label: string;
- value: string;
- onChange: (value: string) => void;
- options: Array<{ value: string; label: string; description?: string }>;
- error?: string;
- required?: boolean;
- placeholder?: string;
- helpText?: string;
- className?: string;
- fullWidth?: boolean;
- disabled?: boolean;
- validation?: 'valid' | 'invalid' | 'none';
-}
-
-const ValidatedSelect: React.FC = ({
- label,
- value,
- onChange,
- options,
- error,
- required = false,
- placeholder = 'Select option...',
- helpText,
- className = '',
- fullWidth = false,
- disabled = false,
- validation = 'none',
-}) => {
- const hasError = !!error;
- const isValid = validation === 'valid' && !hasError && value.trim() !== '';
-
- const selectClasses = `
- w-full px-4 py-3 rounded-lg border transition-all duration-200
- focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-white
- disabled:opacity-50 disabled:cursor-not-allowed
- ${
- hasError
- ? 'border-coral-500/60 bg-coral-50 text-coral-700 focus:border-coral-500 focus:ring-coral-500/30'
- : isValid
- ? 'border-sage-500/60 bg-sage-50 text-stone-900 focus:border-sage-500 focus:ring-sage-500/30'
- : 'border-stone-200 bg-white text-stone-900 focus:border-primary-500/50 focus:ring-primary-500/30'
- }
- `;
-
- return (
-
-
-
- {label}
- {required && * }
-
- {helpText &&
{helpText}
}
-
-
-
-
onChange(e.target.value)}
- disabled={disabled}>
- {placeholder && {placeholder} }
- {options.map(option => (
-
- {option.label}
-
- ))}
-
-
- {/* Validation icon */}
- {(hasError || isValid) && (
-
- {hasError ? (
-
- ) : isValid ? (
-
- ) : null}
-
- )}
-
-
- {/* Error message */}
- {hasError && (
-
-
- {error}
-
- )}
-
- );
-};
-
-export default ValidatedField;
-export { ValidatedSelect };
diff --git a/app/src/data/countries.ts b/app/src/data/countries.ts
deleted file mode 100644
index ed87e1362..000000000
--- a/app/src/data/countries.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { Country } from '../types/onboarding';
-
-export const countries: Country[] = [
- { code: 'US', name: 'United States', flag: '🇺🇸', dialCode: '+1' },
- { code: 'CA', name: 'Canada', flag: '🇨🇦', dialCode: '+1' },
- { code: 'GB', name: 'United Kingdom', flag: '🇬🇧', dialCode: '+44' },
- { code: 'DE', name: 'Germany', flag: '🇩🇪', dialCode: '+49' },
- { code: 'FR', name: 'France', flag: '🇫🇷', dialCode: '+33' },
- { code: 'JP', name: 'Japan', flag: '🇯🇵', dialCode: '+81' },
- { code: 'KR', name: 'South Korea', flag: '🇰🇷', dialCode: '+82' },
- { code: 'CN', name: 'China', flag: '🇨🇳', dialCode: '+86' },
- { code: 'IN', name: 'India', flag: '🇮🇳', dialCode: '+91' },
- { code: 'AU', name: 'Australia', flag: '🇦🇺', dialCode: '+61' },
- { code: 'BR', name: 'Brazil', flag: '🇧🇷', dialCode: '+55' },
- { code: 'MX', name: 'Mexico', flag: '🇲🇽', dialCode: '+52' },
- { code: 'AR', name: 'Argentina', flag: '🇦🇷', dialCode: '+54' },
- { code: 'CL', name: 'Chile', flag: '🇨🇱', dialCode: '+56' },
- { code: 'SG', name: 'Singapore', flag: '🇸🇬', dialCode: '+65' },
- { code: 'HK', name: 'Hong Kong', flag: '🇭🇰', dialCode: '+852' },
- { code: 'CH', name: 'Switzerland', flag: '🇨🇭', dialCode: '+41' },
- { code: 'NL', name: 'Netherlands', flag: '🇳🇱', dialCode: '+31' },
- { code: 'SE', name: 'Sweden', flag: '🇸🇪', dialCode: '+46' },
- { code: 'NO', name: 'Norway', flag: '🇳🇴', dialCode: '+47' },
-];
diff --git a/app/src/hooks/useIntelligenceApiFallback.ts b/app/src/hooks/useIntelligenceApiFallback.ts
index d548fa44d..ba94c29da 100644
--- a/app/src/hooks/useIntelligenceApiFallback.ts
+++ b/app/src/hooks/useIntelligenceApiFallback.ts
@@ -1,8 +1,15 @@
import { useCallback, useState } from 'react';
-import type { ConnectedTool } from '../services/intelligenceApi';
import type { ActionableItemStatus, ChatMessage } from '../types/intelligence';
+interface ConnectedTool {
+ name: string;
+ description: string;
+ parameters: Record;
+ skillId: string;
+ enabled: boolean;
+}
+
/**
* Local-only implementations of Intelligence action hooks.
* Items come from the local conscious memory layer — actions are applied in-memory.
diff --git a/app/src/hooks/useLocalModelStatus.ts b/app/src/hooks/useLocalModelStatus.ts
deleted file mode 100644
index 0cb697074..000000000
--- a/app/src/hooks/useLocalModelStatus.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * useLocalModelStatus — returns true when a local AI model is ready and serving.
- *
- * Polls openhumanLocalAiStatus() every POLL_INTERVAL_MS.
- * Returns false in non-Tauri environments (browser dev / remote-only).
- * This is the gate used by the multi-bubble delivery path.
- */
-import { useEffect, useRef, useState } from 'react';
-
-import { isTauri, openhumanLocalAiStatus } from '../utils/tauriCommands';
-
-const POLL_INTERVAL_MS = 12_000;
-
-export function useLocalModelStatus(): boolean {
- const [isActive, setIsActive] = useState(false);
- const mountedRef = useRef(true);
-
- useEffect(() => {
- mountedRef.current = true;
-
- if (!isTauri()) return;
-
- const check = async () => {
- try {
- const res = await openhumanLocalAiStatus();
- if (mountedRef.current) {
- setIsActive(res.result?.state === 'ready');
- }
- } catch {
- if (mountedRef.current) setIsActive(false);
- }
- };
-
- void check();
- const id = setInterval(() => void check(), POLL_INTERVAL_MS);
-
- return () => {
- mountedRef.current = false;
- clearInterval(id);
- };
- }, []);
-
- return isActive;
-}
diff --git a/app/src/hooks/useSocket.ts b/app/src/hooks/useSocket.ts
deleted file mode 100644
index 85b7949b9..000000000
--- a/app/src/hooks/useSocket.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { useEffect, useRef } from 'react';
-import type { Socket } from 'socket.io-client';
-
-import { socketService } from '../services/socketService';
-import { useAppSelector } from '../store/hooks';
-import { selectSocketStatus } from '../store/socketSelectors';
-
-/**
- * React hook for using the Socket.IO connection
- * Note: The socket connection is managed by SocketProvider based on JWT token.
- * This hook provides access to the socket instance and methods.
- *
- * @example
- * ```tsx
- * const { socket, isConnected, emit, on, off } = useSocket();
- *
- * useEffect(() => {
- * on('ready', () => {
- * console.log('Socket ready!');
- * });
- *
- * return () => {
- * off('ready');
- * };
- * }, [on, off]);
- * ```
- */
-export const useSocket = () => {
- const listenersRef = useRef void }>>([]);
- const socketStatus = useAppSelector(selectSocketStatus);
-
- useEffect(() => {
- return () => {
- // Cleanup: remove all listeners registered through this hook
- listenersRef.current.forEach(({ event, callback }) => {
- socketService.off(event, callback);
- });
- listenersRef.current = [];
- };
- }, []);
-
- const emit = (event: string, data?: unknown) => {
- socketService.emit(event, data);
- };
-
- const on = (event: string, callback: (...args: unknown[]) => void) => {
- socketService.on(event, callback);
- listenersRef.current.push({ event, callback });
- };
-
- const off = (event: string, callback?: (...args: unknown[]) => void) => {
- socketService.off(event, callback);
- if (callback) {
- listenersRef.current = listenersRef.current.filter(
- listener => listener.event !== event || listener.callback !== callback
- );
- } else {
- listenersRef.current = listenersRef.current.filter(listener => listener.event !== event);
- }
- };
-
- const once = (event: string, callback: (...args: unknown[]) => void) => {
- socketService.once(event, callback);
- };
-
- return {
- socket: socketService.getSocket() as Socket | null,
- isConnected: socketStatus === 'connected',
- status: socketStatus,
- emit,
- on,
- off,
- once,
- };
-};
diff --git a/app/src/hooks/useToolsUpdates.ts b/app/src/hooks/useToolsUpdates.ts
deleted file mode 100644
index 10535ec31..000000000
--- a/app/src/hooks/useToolsUpdates.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-/**
- * React hook to listen for tools updates and trigger re-renders
- *
- * Components using this hook will automatically re-render when TOOLS.md
- * is updated and the cache is refreshed.
- */
-import { useEffect, useState } from 'react';
-
-import { forceToolsCacheRefresh } from '../lib/tools/file-watcher';
-
-interface ToolsUpdateEvent {
- timestamp: number;
-}
-
-/**
- * Hook to listen for tools updates
- * @returns timestamp of last tools update (triggers re-renders)
- */
-export function useToolsUpdates(): number {
- const [lastUpdate, setLastUpdate] = useState(0);
-
- useEffect(() => {
- const handleToolsUpdate = (event: CustomEvent) => {
- console.log('🔔 Tools update detected, triggering component re-render');
- setLastUpdate(event.detail.timestamp);
- };
-
- // Listen for tools-updated events
- window.addEventListener('tools-updated', handleToolsUpdate as EventListener);
-
- return () => {
- window.removeEventListener('tools-updated', handleToolsUpdate as EventListener);
- };
- }, []);
-
- return lastUpdate;
-}
-
-/**
- * Hook to get a callback that forces tools refresh
- * @returns function to manually trigger tools refresh
- */
-export function useForceToolsRefresh(): () => Promise {
- const forceRefresh = async () => {
- return forceToolsCacheRefresh();
- };
-
- return forceRefresh;
-}
diff --git a/app/src/lib/skills/tool-bridge.ts b/app/src/lib/skills/tool-bridge.ts
deleted file mode 100644
index 62bde7b39..000000000
--- a/app/src/lib/skills/tool-bridge.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-/**
- * Tool Bridge — converts skill tools to AI tool registry format.
- *
- * Namespaces tool names as `__` to avoid collisions.
- * Each tool's execute function delegates to skillManager.callTool().
- */
-
-import { skillManager } from "./manager";
-import type { SkillToolDefinition } from "./types";
-
-export interface BridgedTool {
- name: string;
- description: string;
- parameters: Record;
- execute: (args: Record) => Promise;
- skillId: string;
- originalName: string;
-}
-
-/**
- * Convert a skill's tools into bridged tools with namespaced names.
- */
-export function bridgeSkillTools(
- skillId: string,
- tools: SkillToolDefinition[],
-): BridgedTool[] {
- return tools.map((tool) => ({
- name: `${skillId}__${tool.name}`,
- description: tool.description,
- parameters: tool.inputSchema,
- skillId,
- originalName: tool.name,
- execute: async (args: Record): Promise => {
- const result = await skillManager.callTool(skillId, tool.name, args);
- if (result.isError) {
- throw new Error(
- result.content.map((c) => c.text).join("\n") || "Tool execution failed",
- );
- }
- return result.content.map((c) => c.text).join("\n");
- },
- }));
-}
-
-/**
- * Extract the skill ID and original tool name from a namespaced name.
- */
-export function parseToolName(namespacedName: string): {
- skillId: string;
- toolName: string;
-} | null {
- const idx = namespacedName.indexOf("__");
- if (idx === -1) return null;
- return {
- skillId: namespacedName.substring(0, idx),
- toolName: namespacedName.substring(idx + 2),
- };
-}
diff --git a/app/src/lib/tools/file-watcher.ts b/app/src/lib/tools/file-watcher.ts
deleted file mode 100644
index c6fcf28c7..000000000
--- a/app/src/lib/tools/file-watcher.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * Legacy UI-side tools cache watcher removed.
- * Tool/config refresh now comes from core RPC and socket events.
- */
-export function startToolsFileWatcher(): void {
- // no-op
-}
-
-export function stopToolsFileWatcher(): void {
- // no-op
-}
-
-export async function forceToolsCacheRefresh(): Promise {
- // Preserve compatibility for callers that expect a resolved promise.
- return Promise.resolve();
-}
diff --git a/app/src/lib/webhooks/urls.ts b/app/src/lib/webhooks/urls.ts
deleted file mode 100644
index 43fa61727..000000000
--- a/app/src/lib/webhooks/urls.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { BACKEND_URL } from '../../utils/config';
-
-const DEFAULT_BACKEND_URL = 'https://api.tinyhumans.ai';
-
-function normalizedBackendUrl(baseUrl?: string): string {
- const value = (baseUrl || BACKEND_URL || DEFAULT_BACKEND_URL).trim();
- return value.replace(/\/+$/, '');
-}
-
-export function buildWebhookIngressUrl(tunnelUuid: string, baseUrl?: string): string {
- return `${normalizedBackendUrl(baseUrl)}/webhooks/ingress/${encodeURIComponent(tunnelUuid)}`;
-}
diff --git a/app/src/overlay/parentCoreRpc.ts b/app/src/overlay/parentCoreRpc.ts
deleted file mode 100644
index 48f564ca3..000000000
--- a/app/src/overlay/parentCoreRpc.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-/**
- * HTTP JSON-RPC to the desktop core sidecar shared by the main app and overlay.
- * Mirrors the naming normalization in app/src/services/coreRpcClient.ts (subset).
- */
-
-let nextJsonRpcId = 1;
-
-export const normalizeLegacyMethod = (method: string): string => {
- if (method.startsWith('openhuman.accessibility_')) {
- return method.replace('openhuman.accessibility_', 'openhuman.screen_intelligence_');
- }
- return method;
-};
-
-/** RpcOutcome with non-empty logs serializes as `{ result, logs }` in the core. */
-const unwrapCliCompatibleJson = (raw: unknown): T => {
- if (
- raw !== null &&
- typeof raw === 'object' &&
- Object.prototype.hasOwnProperty.call(raw, 'result') &&
- Object.prototype.hasOwnProperty.call(raw, 'logs')
- ) {
- const keys = Object.keys(raw);
- const { logs } = raw as { logs: unknown };
- if (keys.length === 2 && Array.isArray(logs)) {
- return (raw as { result: T }).result;
- }
- }
- return raw as T;
-};
-
-interface JsonRpcError {
- code: number;
- message: string;
- data?: unknown;
-}
-
-interface JsonRpcResponse {
- jsonrpc?: string;
- id?: number | string | null;
- result?: T;
- error?: JsonRpcError;
-}
-
-const DEFAULT_RPC_TIMEOUT_MS = 10_000;
-
-const isJsonRpcEnvelope = (value: unknown): value is JsonRpcResponse => {
- if (value === null || typeof value !== 'object') {
- return false;
- }
-
- return (
- Object.prototype.hasOwnProperty.call(value, 'error') ||
- Object.prototype.hasOwnProperty.call(value, 'result') ||
- Object.prototype.hasOwnProperty.call(value, 'id') ||
- Object.prototype.hasOwnProperty.call(value, 'jsonrpc')
- );
-};
-
-export const callParentCoreRpc = async (
- rpcUrl: string,
- method: string,
- params: Record = {},
- timeoutMs: number = DEFAULT_RPC_TIMEOUT_MS
-): Promise => {
- const normalizedMethod = normalizeLegacyMethod(method);
- const payload = {
- jsonrpc: '2.0' as const,
- id: nextJsonRpcId++,
- method: normalizedMethod,
- params,
- };
-
- const controller = new AbortController();
- const timer = setTimeout(() => controller.abort(), timeoutMs);
-
- let response: Response;
- try {
- response = await fetch(rpcUrl, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(payload),
- signal: controller.signal,
- });
- } catch (err) {
- clearTimeout(timer);
- if (
- (err instanceof DOMException && err.name === 'AbortError') ||
- (err instanceof Error && err.name === 'AbortError')
- ) {
- throw new Error(
- `Core RPC request timed out after ${timeoutMs}ms (method: ${normalizedMethod})`
- );
- }
- throw err;
- } finally {
- clearTimeout(timer);
- }
-
- if (!response.ok) {
- const text = await response.text();
- throw new Error(`Core RPC HTTP ${response.status}: ${text || response.statusText}`);
- }
-
- const json = await response.json();
- if (!isJsonRpcEnvelope(json)) {
- throw new Error('Invalid JSON-RPC envelope');
- }
-
- if (json.error) {
- throw new Error(json.error.message || 'Core RPC returned an error');
- }
- if (!Object.prototype.hasOwnProperty.call(json, 'result')) {
- throw new Error('Core RPC response missing result');
- }
-
- return unwrapCliCompatibleJson(json.result);
-};
diff --git a/app/src/pages/onboarding/steps/AnalyticsStep.tsx b/app/src/pages/onboarding/steps/AnalyticsStep.tsx
deleted file mode 100644
index e378097ae..000000000
--- a/app/src/pages/onboarding/steps/AnalyticsStep.tsx
+++ /dev/null
@@ -1,128 +0,0 @@
-import { useState } from 'react';
-
-import { isTauri, openhumanUpdateAnalyticsSettings } from '../../../utils/tauriCommands';
-
-interface AnalyticsStepProps {
- onNext: (analyticsEnabled: boolean) => void;
-}
-
-const AnalyticsStep = ({ onNext }: AnalyticsStepProps) => {
- const [selectedOption, setSelectedOption] = useState('shareAnalytics');
-
- const handleContinue = () => {
- const enabled = selectedOption === 'shareAnalytics';
-
- // Sync to core config so the Rust process also respects the setting
- if (isTauri()) {
- openhumanUpdateAnalyticsSettings({ enabled }).catch(() => {
- /* best-effort */
- });
- }
-
- onNext(enabled);
- };
-
- return (
-
-
-
Anonymized Analytics
-
- We collect fully anonymized usage data to help improve the app. No personal data,
- messages, or wallet keys are ever collected. You can change this anytime in Settings.
-
-
-
-
-
setSelectedOption('shareAnalytics')}>
-
-
-
- {selectedOption === 'shareAnalytics' && (
-
- )}
-
-
-
-
- Share Anonymized Usage Data
-
-
- Share anonymized crash reports and usage analytics to help us improve features and
- performance. All data is fully anonymized.
-
-
-
-
-
-
setSelectedOption('maximumPrivacy')}>
-
-
-
- {selectedOption === 'maximumPrivacy' && (
-
- )}
-
-
-
-
Don't Collect Anything
-
- We won't collect any usage analytics or crash reports. Keep all your data completely
- private.
-
-
-
-
-
-
-
-
-
-
-
-
-
- You can change this setting anytime
-
-
- Your privacy preferences can be updated in Settings > Privacy & Security
-
-
-
-
-
-
- Continue
-
-
- );
-};
-
-export default AnalyticsStep;
diff --git a/app/src/pages/onboarding/steps/ConnectStep.tsx b/app/src/pages/onboarding/steps/ConnectStep.tsx
deleted file mode 100644
index 9d29de53f..000000000
--- a/app/src/pages/onboarding/steps/ConnectStep.tsx
+++ /dev/null
@@ -1,176 +0,0 @@
-import { useState } from 'react';
-
-import BinanceIcon from '../../../assets/icons/binance.svg';
-import GoogleIcon from '../../../assets/icons/GoogleIcon';
-import MetamaskIcon from '../../../assets/icons/metamask.svg';
-import NotionIcon from '../../../assets/icons/notion.svg';
-import SkillSetupModal from '../../../components/skills/SkillSetupModal';
-import type { SkillConnectionStatus } from '../../../lib/skills/types';
-
-interface ConnectStepProps {
- onNext: () => void;
-}
-
-interface ConnectOption {
- id: string;
- name: string;
- description: string;
- icon: React.ReactElement;
- comingSoon?: boolean;
- skillId?: string;
-}
-
-const STATUS_BADGE: Record = {
- connected: { label: 'Connected', classes: 'bg-sage-500/20 text-sage-400' },
- connecting: { label: 'Connecting...', classes: 'bg-amber-500/20 text-amber-400' },
- not_authenticated: { label: 'Not Authenticated', classes: 'bg-amber-500/20 text-amber-400' },
- disconnected: { label: 'Disconnected', classes: 'bg-stone-500/20 text-stone-400' },
- error: { label: 'Error', classes: 'bg-coral-500/20 text-coral-400' },
- offline: { label: 'Offline', classes: 'bg-stone-500/20 text-stone-400' },
- setup_required: { label: 'Connect', classes: 'bg-primary-500/20 text-primary-400' },
-};
-
-function ConnectOptionRow({
- option,
- onConnect,
-}: {
- option: ConnectOption;
- onConnect: (option: ConnectOption) => void;
-}) {
- const connectionStatus = 'setup_required' as SkillConnectionStatus;
- const disabled = option.comingSoon;
-
- let badge: React.ReactElement;
- if (option.comingSoon) {
- badge = (
- Coming Soon
- );
- } else if (option.skillId) {
- const cfg = STATUS_BADGE[connectionStatus];
- badge = {cfg.label} ;
- } else {
- badge = (
-
- Connect
-
- );
- }
-
- return (
- onConnect(option)}
- className={`w-full flex items-start space-x-3 p-3 bg-white border border-stone-200 rounded-xl transition-all duration-200 text-left ${
- disabled ? 'opacity-50 cursor-not-allowed' : 'hover:border-stone-300 hover:shadow-medium'
- }`}>
- {option.icon}
-
-
- {option.name}
- {badge}
-
-
{option.description}
-
-
- );
-}
-
-const ConnectStep = ({ onNext }: ConnectStepProps) => {
- const [setupModalOpen, setSetupModalOpen] = useState(false);
- const [activeSkillId, setActiveSkillId] = useState(null);
- const [activeSkillName, setActiveSkillName] = useState('');
- const [activeSkillDescription, setActiveSkillDescription] = useState('');
-
- const connectOptions: ConnectOption[] = [
- {
- id: 'google',
- name: 'Google',
- description: 'Manage emails, contacts and calendar events',
- icon: ,
- comingSoon: true,
- },
- {
- id: 'notion',
- name: 'Notion',
- description: 'Manage tasks, documents and everything else in your Notion',
- icon: ,
- comingSoon: true,
- },
- {
- id: 'wallet',
- name: 'Web3 Wallet',
- description: 'Trade the trenches in a safe and secure way.',
- icon: ,
- comingSoon: true,
- },
- {
- id: 'exchange',
- name: 'Crypto Trading Exchanges',
- description: 'Connect and make trades with deep insights.',
- icon: ,
- comingSoon: true,
- },
- ];
-
- const handleConnect = (option: ConnectOption) => {
- if (option.comingSoon) return;
- if (option.skillId) {
- setActiveSkillId(option.skillId);
- setActiveSkillName(option.name);
- setActiveSkillDescription(option.description);
- setSetupModalOpen(true);
- }
- };
-
- return (
-
-
-
Connect Accounts
-
- The more accounts you connect, the more powerful the intelligence will be.
-
-
-
-
- {connectOptions.map(option => (
-
- ))}
-
-
-
-
-
-
- Remember everything is private & encrypted!
-
-
- All data and credentials are stored locally and follows a strict zero-data retention
- policy so you won't have to worry about anything getting leaked.
-
-
-
-
-
-
- Continue
-
-
- {/* Setup modal */}
- {setupModalOpen && activeSkillId && (
-
{
- setSetupModalOpen(false);
- setActiveSkillId(null);
- }}
- />
- )}
-
- );
-};
-
-export default ConnectStep;
diff --git a/app/src/pages/onboarding/steps/InviteCodeStep.tsx b/app/src/pages/onboarding/steps/InviteCodeStep.tsx
deleted file mode 100644
index 3b089ce36..000000000
--- a/app/src/pages/onboarding/steps/InviteCodeStep.tsx
+++ /dev/null
@@ -1,99 +0,0 @@
-import { useState } from 'react';
-
-import { inviteApi } from '../../../services/api/inviteApi';
-
-interface InviteCodeStepProps {
- onNext: () => void;
-}
-
-const InviteCodeStep = ({ onNext }: InviteCodeStepProps) => {
- const [code, setCode] = useState('');
- const [isLoading, setIsLoading] = useState(false);
- const [error, setError] = useState(null);
- const [success, setSuccess] = useState(false);
-
- const handleRedeem = async () => {
- const trimmed = code.trim();
- if (!trimmed) return;
-
- setIsLoading(true);
- setError(null);
-
- try {
- await inviteApi.redeemInviteCode(trimmed);
- setSuccess(true);
- setTimeout(() => onNext(), 1500);
- } catch (err) {
- const msg =
- err && typeof err === 'object' && 'error' in err
- ? String((err as { error: string }).error)
- : 'Invalid or expired invite code';
- setError(msg);
- } finally {
- setIsLoading(false);
- }
- };
-
- return (
-
-
-
Have an Invite Code?
-
- Enter an invite code from a friend to unlock free credits. You can also skip this step.
-
-
-
- {success ? (
-
-
-
Invite code redeemed successfully!
-
- ) : (
- <>
-
-
setCode(e.target.value.toUpperCase())}
- onKeyDown={e => e.key === 'Enter' && handleRedeem()}
- placeholder="Enter invite code"
- className="w-full px-4 py-3 bg-stone-50 border border-stone-200 rounded-xl text-center font-mono text-lg tracking-widest text-stone-900 placeholder:text-stone-400 placeholder:tracking-normal placeholder:font-sans placeholder:text-sm focus:outline-none focus:ring-2 focus:ring-primary-500/50 focus:border-primary-500/50 transition-all"
- disabled={isLoading}
- />
- {error &&
{error}
}
-
-
-
-
- {isLoading ? 'Redeeming...' : 'Redeem Code'}
-
-
- Skip for now
-
-
- >
- )}
-
- );
-};
-
-export default InviteCodeStep;
diff --git a/app/src/pages/onboarding/steps/MnemonicStep.tsx b/app/src/pages/onboarding/steps/MnemonicStep.tsx
deleted file mode 100644
index 0af6f0407..000000000
--- a/app/src/pages/onboarding/steps/MnemonicStep.tsx
+++ /dev/null
@@ -1,337 +0,0 @@
-import { type KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
-
-import { skillManager } from '../../../lib/skills/manager';
-import { useCoreState } from '../../../providers/CoreStateProvider';
-import {
- deriveAesKeyFromMnemonic,
- deriveEvmAddressFromMnemonic,
- generateMnemonicPhrase,
- MNEMONIC_GENERATE_WORD_COUNT,
- validateMnemonicPhrase,
-} from '../../../utils/cryptoKeys';
-import OnboardingNextButton from '../components/OnboardingNextButton';
-
-const BIP39_IMPORT_LENGTHS = [12, 15, 18, 21, 24] as const;
-
-const IMPORT_SLOTS_INITIAL = MNEMONIC_GENERATE_WORD_COUNT;
-
-interface MnemonicStepProps {
- onNext: () => void | Promise;
- onBack?: () => void;
-}
-
-const MnemonicStep = ({ onNext, onBack: _onBack }: MnemonicStepProps) => {
- const { snapshot, setEncryptionKey } = useCoreState();
- const user = snapshot.currentUser;
- const [mode, setMode] = useState<'generate' | 'import'>('generate');
- const [copied, setCopied] = useState(false);
- const [confirmed, setConfirmed] = useState(false);
- const [loading, setLoading] = useState(false);
- const [error, setError] = useState(null);
-
- const mnemonic = useMemo(() => generateMnemonicPhrase(), []);
- const words = useMemo(() => mnemonic.split(' '), [mnemonic]);
-
- const [importWords, setImportWords] = useState(Array(IMPORT_SLOTS_INITIAL).fill(''));
- const [importValid, setImportValid] = useState(null);
- const inputRefs = useRef<(HTMLInputElement | null)[]>([]);
-
- useEffect(() => {
- if (copied) {
- const timer = setTimeout(() => setCopied(false), 3000);
- return () => clearTimeout(timer);
- }
- }, [copied]);
-
- useEffect(() => {
- setConfirmed(false);
- setError(null);
- setImportValid(null);
- setImportWords(Array(IMPORT_SLOTS_INITIAL).fill(''));
- }, [mode]);
-
- const handleCopy = useCallback(async () => {
- try {
- await navigator.clipboard.writeText(mnemonic);
- setCopied(true);
- } catch {
- const textarea = document.createElement('textarea');
- textarea.value = mnemonic;
- textarea.style.position = 'fixed';
- textarea.style.opacity = '0';
- document.body.appendChild(textarea);
- textarea.select();
- document.execCommand('copy');
- document.body.removeChild(textarea);
- setCopied(true);
- }
- }, [mnemonic]);
-
- const handleImportWordChange = useCallback(
- (index: number, value: string) => {
- const pastedWords = value.trim().split(/\s+/).filter(Boolean);
- if (pastedWords.length > 1) {
- const fullPhraseLen = pastedWords.length;
- if (BIP39_IMPORT_LENGTHS.includes(fullPhraseLen as (typeof BIP39_IMPORT_LENGTHS)[number])) {
- setImportWords(pastedWords.map(w => w.toLowerCase()));
- setImportValid(null);
- inputRefs.current[fullPhraseLen - 1]?.focus();
- return;
- }
- const newWords = [...importWords];
- const slotCount = newWords.length;
- for (let i = 0; i < Math.min(pastedWords.length, slotCount - index); i++) {
- newWords[index + i] = pastedWords[i].toLowerCase();
- }
- setImportWords(newWords);
- setImportValid(null);
- const nextEmpty = newWords.findIndex(w => !w);
- const focusIndex = nextEmpty === -1 ? slotCount - 1 : nextEmpty;
- inputRefs.current[focusIndex]?.focus();
- return;
- }
-
- const newWords = [...importWords];
- newWords[index] = value.toLowerCase().trim();
- setImportWords(newWords);
- setImportValid(null);
-
- if (value.trim() && index < newWords.length - 1) {
- inputRefs.current[index + 1]?.focus();
- }
- },
- [importWords]
- );
-
- const handleImportKeyDown = useCallback(
- (index: number, e: KeyboardEvent) => {
- if (e.key === 'Backspace' && !importWords[index] && index > 0) {
- inputRefs.current[index - 1]?.focus();
- }
- },
- [importWords]
- );
-
- const handleValidateImport = useCallback(() => {
- const phrase = importWords.join(' ').trim();
- const filledWords = importWords.filter(w => w.trim());
- const n = filledWords.length;
-
- if (!BIP39_IMPORT_LENGTHS.includes(n as (typeof BIP39_IMPORT_LENGTHS)[number])) {
- setError(`Recovery phrase must be ${BIP39_IMPORT_LENGTHS.join(', ')} words (you have ${n}).`);
- setImportValid(false);
- return false;
- }
-
- const isValid = validateMnemonicPhrase(phrase);
- setImportValid(isValid);
-
- if (!isValid) {
- setError('Invalid recovery phrase. Please check your words and try again.');
- return false;
- }
-
- setError(null);
- return true;
- }, [importWords]);
-
- const handleContinue = async () => {
- setError(null);
- setLoading(true);
-
- try {
- let phraseToUse: string;
-
- if (mode === 'import') {
- if (!handleValidateImport()) {
- setLoading(false);
- return;
- }
- phraseToUse = importWords.join(' ').trim();
- } else {
- if (!confirmed) {
- setLoading(false);
- return;
- }
- phraseToUse = mnemonic;
- }
-
- const aesKey = deriveAesKeyFromMnemonic(phraseToUse);
- const walletAddress = deriveEvmAddressFromMnemonic(phraseToUse);
-
- if (!user?._id) {
- setError('User not loaded. Please sign in again or refresh the page.');
- return;
- }
- await setEncryptionKey(aesKey);
- await skillManager.setWalletAddress(walletAddress);
- await onNext();
- } catch (e) {
- setError(e instanceof Error ? e.message : 'Something went wrong. Please try again.');
- } finally {
- setLoading(false);
- }
- };
-
- const importWordCount = importWords.filter(w => w.trim()).length;
- const isImportComplete =
- importWords.every(w => w.trim()) &&
- BIP39_IMPORT_LENGTHS.includes(importWordCount as (typeof BIP39_IMPORT_LENGTHS)[number]);
- const canContinue = mode === 'generate' ? confirmed : isImportComplete;
-
- return (
-
- {mode === 'generate' ? (
- <>
-
-
Lastly, your Recovery Phrase
-
- Write down these {MNEMONIC_GENERATE_WORD_COUNT} words in order and store them
- somewhere safe. This phrase encrypts your data and can never be recovered if lost. You
- can always back up later.
-
-
-
-
-
- {words.map((word, index) => (
-
-
- {index + 1}.
-
- {word}
-
- ))}
-
-
-
-
- {copied ? (
- <>
-
-
-
- Copied to Clipboard
- >
- ) : (
- <>
-
-
-
- Copy to Clipboard
- >
- )}
-
-
-
setMode('import')}
- className="w-full text-center text-sm text-primary-600 hover:text-primary-500 transition-colors mb-3">
- I already have a recovery phrase
-
-
-
- setConfirmed(e.target.checked)}
- className="mt-0.5 w-4 h-4 rounded border-stone-300 text-primary-500 focus:ring-primary-500"
- />
-
- I have saved my recovery phrase in a safe place
-
-
- >
- ) : (
- <>
-
-
Import Recovery Phrase
-
- Enter your recovery phrase below, or paste the full phrase into any field (12 words
- for new backups; 24-word phrases from older versions still work).
-
-
-
-
-
- {importWords.map((word, index) => (
-
-
- {index + 1}.
-
- {
- inputRefs.current[index] = el;
- }}
- type="text"
- value={word}
- onChange={e => handleImportWordChange(index, e.target.value)}
- onKeyDown={e => handleImportKeyDown(index, e)}
- autoComplete="off"
- spellCheck={false}
- className={`w-full font-mono text-sm font-medium px-2 py-1.5 rounded-lg border bg-white outline-none transition-colors text-stone-900 ${
- importValid === false && word.trim()
- ? 'border-coral-400 focus:border-coral-300'
- : importValid === true
- ? 'border-sage-400 focus:border-sage-300'
- : 'border-stone-300 focus:border-primary-400'
- }`}
- />
-
- ))}
-
-
-
- {importValid === true && (
-
-
-
-
-
Valid recovery phrase
-
- )}
-
-
setMode('generate')}
- className="w-full text-center text-sm text-primary-600 hover:text-primary-500 transition-colors mb-3">
- Generate a new recovery phrase instead
-
- >
- )}
-
- {error &&
{error}
}
-
-
-
- );
-};
-
-export default MnemonicStep;
diff --git a/app/src/pages/onboarding/steps/ToolsStep.tsx b/app/src/pages/onboarding/steps/ToolsStep.tsx
deleted file mode 100644
index 36b999094..000000000
--- a/app/src/pages/onboarding/steps/ToolsStep.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import { useState } from 'react';
-
-import {
- getDefaultEnabledTools,
- getToolsByCategory,
- TOOL_CATEGORIES,
- type ToolCategory,
-} from '../../../utils/toolDefinitions';
-import OnboardingNextButton from '../components/OnboardingNextButton';
-
-interface ToolsStepProps {
- onNext: (enabledTools: string[]) => void;
- onBack?: () => void;
-}
-
-const CATEGORY_DESCRIPTIONS: Record = {
- System: 'Shell access and version control',
- Files: 'Read and write files on disk',
- Vision: 'Screen capture and image analysis',
- Web: 'Browser, HTTP, and web search',
- Memory: 'Persistent recall for the AI',
- Automation: 'Cron jobs and scheduled tasks',
-};
-
-const ToolsStep = ({ onNext, onBack: _onBack }: ToolsStepProps) => {
- const toolsByCategory = getToolsByCategory();
- const [enabled, setEnabled] = useState>(() => {
- const defaults: Record = {};
- const defaultEnabled = getDefaultEnabledTools();
- for (const cat of TOOL_CATEGORIES) {
- for (const tool of toolsByCategory[cat]) {
- defaults[tool.id] = defaultEnabled.includes(tool.id);
- }
- }
- return defaults;
- });
-
- const toggle = (toolId: string) => {
- setEnabled(prev => ({ ...prev, [toolId]: !prev[toolId] }));
- };
-
- const enabledList = Object.entries(enabled)
- .filter(([, v]) => v)
- .map(([k]) => k);
-
- return (
-
-
-
Enable Tools
-
- Choose which capabilities OpenHuman can use on your behalf.
-
-
-
-
- {TOOL_CATEGORIES.map(category => {
- const tools = toolsByCategory[category];
- if (tools.length === 0) return null;
- return (
-
-
-
- {category}
-
-
{CATEGORY_DESCRIPTIONS[category]}
-
-
- {tools.map(tool => (
-
toggle(tool.id)}
- className="w-full flex items-center justify-between p-2.5 rounded-xl border border-stone-200 bg-white hover:border-stone-300 transition-colors text-left">
-
-
{tool.displayName}
-
{tool.description}
-
-
-
- ))}
-
-
- );
- })}
-
-
-
onNext(enabledList)} />
-
- );
-};
-
-export default ToolsStep;
diff --git a/app/src/services/__tests__/agentToolRegistry.test.ts b/app/src/services/__tests__/agentToolRegistry.test.ts
deleted file mode 100644
index 0a5f2dc00..000000000
--- a/app/src/services/__tests__/agentToolRegistry.test.ts
+++ /dev/null
@@ -1,363 +0,0 @@
-import { invoke } from '@tauri-apps/api/core';
-import { beforeEach, describe, expect, type Mock, test, vi } from 'vitest';
-
-import { AgentToolRegistry } from '../agentToolRegistry';
-
-// Mock Tauri invoke
-vi.mock('@tauri-apps/api/core');
-
-describe('AgentToolRegistry', () => {
- let service: AgentToolRegistry;
- const mockInvoke = invoke as Mock;
-
- beforeEach(() => {
- service = AgentToolRegistry.getInstance();
- vi.clearAllMocks();
- service.clearCache(); // Clear cache between tests
- });
-
- describe('loadToolSchemas', () => {
- test('should load tool schemas from Tauri using OpenClaw format', async () => {
- const mockSchemas = [
- {
- type: 'function',
- function: {
- name: 'github_list_issues',
- description: 'List GitHub issues for a repository',
- parameters: {
- type: 'object',
- properties: {
- owner: { type: 'string', description: 'Repository owner' },
- repo: { type: 'string', description: 'Repository name' },
- },
- required: ['owner', 'repo'],
- },
- },
- },
- {
- type: 'function',
- function: {
- name: 'notion_create_page',
- description: 'Create a new Notion page',
- parameters: {
- type: 'object',
- properties: {
- title: { type: 'string', description: 'Page title' },
- content: { type: 'string', description: 'Page content' },
- },
- required: ['title'],
- },
- },
- },
- ];
-
- mockInvoke.mockResolvedValue(mockSchemas);
-
- const schemas = await service.loadToolSchemas();
-
- expect(schemas).toHaveLength(2);
- expect(schemas[0].function.name).toBe('github_list_issues');
- expect(schemas[1].function.name).toBe('notion_create_page');
- expect(mockInvoke).toHaveBeenCalledWith('runtime_get_tool_schemas');
- });
-
- test('should cache tool schemas to avoid repeated calls', async () => {
- const mockSchemas = [
- {
- type: 'function',
- function: {
- name: 'test_tool',
- description: 'Test tool',
- parameters: { type: 'object', properties: {} },
- },
- },
- ];
-
- mockInvoke.mockResolvedValue(mockSchemas);
-
- // First call
- const schemas1 = await service.loadToolSchemas();
- // Second call
- const schemas2 = await service.loadToolSchemas();
-
- expect(schemas1).toEqual(schemas2);
- // Should only invoke Tauri once due to caching (TTL = 5 minutes)
- expect(mockInvoke).toHaveBeenCalledTimes(1);
- });
-
- test('should force reload when requested', async () => {
- const mockSchemas = [
- {
- type: 'function',
- function: {
- name: 'test_tool',
- description: 'Test tool',
- parameters: { type: 'object', properties: {} },
- },
- },
- ];
-
- mockInvoke.mockResolvedValue(mockSchemas);
-
- // First call
- await service.loadToolSchemas();
- // Force reload
- await service.loadToolSchemas(true);
-
- // Should invoke Tauri twice
- expect(mockInvoke).toHaveBeenCalledTimes(2);
- });
-
- test('should handle empty tool schema response', async () => {
- mockInvoke.mockResolvedValue([]);
-
- const schemas = await service.loadToolSchemas();
-
- expect(schemas).toHaveLength(0);
- expect(mockInvoke).toHaveBeenCalledWith('runtime_get_tool_schemas');
- });
-
- test('should return empty schemas when skill runtime invoke fails', async () => {
- const errorMessage = 'Failed to load tool schemas';
- mockInvoke.mockRejectedValue(new Error(errorMessage));
-
- const schemas = await service.loadToolSchemas();
-
- expect(schemas).toHaveLength(0);
- });
- });
-
- describe('executeTool', () => {
- test('should execute tool using OpenClaw format with success', async () => {
- const mockResult = {
- success: true,
- output: '{"issues": [{"title": "Bug fix", "number": 1}]}',
- error: null,
- execution_time: 1500,
- };
-
- mockInvoke.mockResolvedValue(mockResult);
-
- const result = await service.executeTool(
- 'github',
- 'list_issues',
- '{"owner":"user","repo":"test"}'
- );
-
- expect(result.status).toBe('success');
- expect(result.result).toBe(mockResult.output);
- expect(result.executionTimeMs).toBe(1500);
- expect(result.toolName).toBe('list_issues');
- expect(result.skillId).toBe('github');
-
- // Verify correct tool_id format and arguments
- expect(mockInvoke).toHaveBeenCalledWith('runtime_execute_tool', {
- toolId: 'github_list_issues',
- args: '{"owner":"user","repo":"test"}',
- });
- });
-
- test('should handle tool execution failure', async () => {
- const mockResult = {
- success: false,
- output: '',
- error: 'Tool not found: invalid_tool',
- execution_time: 100,
- };
-
- mockInvoke.mockResolvedValue(mockResult);
-
- const result = await service.executeTool('invalid', 'tool', '{}');
-
- expect(result.status).toBe('error');
- expect(result.errorMessage).toBe('Tool not found: invalid_tool');
- expect(result.result).toBe('Tool not found: invalid_tool');
- expect(result.executionTimeMs).toBe(100);
- });
-
- test('should handle tool execution without execution_time', async () => {
- const mockResult = {
- success: true,
- output: 'Success',
- error: null,
- // No execution_time provided
- };
-
- mockInvoke.mockResolvedValue(mockResult);
-
- const startTime = Date.now();
- const result = await service.executeTool('test', 'tool', '{}');
- const endTime = Date.now();
-
- expect(result.status).toBe('success');
- expect(result.executionTimeMs).toBeGreaterThanOrEqual(0);
- expect(result.executionTimeMs).toBeLessThanOrEqual(endTime - startTime + 10); // Allow small margin
- });
-
- test('should handle Tauri invoke exception', async () => {
- const errorMessage = 'Network error';
- mockInvoke.mockRejectedValue(new Error(errorMessage));
-
- const result = await service.executeTool('test', 'tool', '{}');
-
- expect(result.status).toBe('error');
- expect(result.errorMessage).toBe(errorMessage);
- expect(result.result).toBe(errorMessage);
- expect(result.executionTimeMs).toBeGreaterThanOrEqual(0);
- });
-
- test('should generate unique execution IDs', async () => {
- const mockResult = { success: true, output: 'test', error: null, execution_time: 100 };
-
- mockInvoke.mockResolvedValue(mockResult);
-
- const result1 = await service.executeTool('test', 'tool1', '{}');
- const result2 = await service.executeTool('test', 'tool2', '{}');
-
- expect(result1.id).not.toBe(result2.id);
- expect(result1.id).toMatch(/^exec_\d+_[a-z0-9]+$/);
- expect(result2.id).toMatch(/^exec_\d+_[a-z0-9]+$/);
- });
- });
-
- describe('tool management methods', () => {
- beforeEach(async () => {
- const mockSchemas = [
- {
- type: 'function',
- function: {
- name: 'github_list_issues',
- description: 'List GitHub issues',
- parameters: { type: 'object', properties: {} },
- },
- },
- {
- type: 'function',
- function: {
- name: 'github_create_issue',
- description: 'Create GitHub issue',
- parameters: { type: 'object', properties: {} },
- },
- },
- {
- type: 'function',
- function: {
- name: 'notion_create_page',
- description: 'Create Notion page',
- parameters: { type: 'object', properties: {} },
- },
- },
- ];
-
- mockInvoke.mockResolvedValue(mockSchemas);
- await service.loadToolSchemas();
- });
-
- test('getToolByName should find tool by name', () => {
- const tool = service.getToolByName('github_list_issues');
-
- expect(tool).toBeDefined();
- expect(tool?.function.name).toBe('github_list_issues');
- expect(tool?.function.description).toBe('List GitHub issues');
- });
-
- test('getToolByName should return undefined for non-existent tool', () => {
- const tool = service.getToolByName('non_existent_tool');
-
- expect(tool).toBeUndefined();
- });
-
- test('getAllTools should return all loaded tools', () => {
- const tools = service.getAllTools();
-
- expect(tools).toHaveLength(3);
- expect(tools.map(t => t.function.name)).toEqual([
- 'github_list_issues',
- 'github_create_issue',
- 'notion_create_page',
- ]);
- });
-
- test('getToolsBySkill should organize tools by skill ID', () => {
- const toolsBySkill = service.getToolsBySkill();
-
- // Skill ID is the substring before the last underscore in tool function names
- expect(toolsBySkill).toHaveProperty('github_list');
- expect(toolsBySkill).toHaveProperty('github_create');
- expect(toolsBySkill).toHaveProperty('notion_create');
- expect(toolsBySkill.github_list).toHaveLength(1);
- expect(toolsBySkill.github_create).toHaveLength(1);
- expect(toolsBySkill.notion_create).toHaveLength(1);
-
- expect(toolsBySkill.github_list[0].function.name).toBe('github_list_issues');
- expect(toolsBySkill.github_create[0].function.name).toBe('github_create_issue');
- expect(toolsBySkill.notion_create[0].function.name).toBe('notion_create_page');
- });
-
- test('getToolStats should return accurate statistics', () => {
- const stats = service.getToolStats();
-
- expect(stats.totalTools).toBe(3);
- expect(stats.skillCount).toBe(3);
- expect(stats.categories).toHaveProperty('GitHub', 2);
- expect(stats.categories).toHaveProperty('Notion', 1);
- });
- });
-
- describe('helper methods', () => {
- test('extractSkillIdFromToolName should parse skill ID correctly', () => {
- // Use reflection to access private method
- const extractMethod = (service as any).extractSkillIdFromToolName.bind(service);
-
- // Uses lastIndexOf('_'): skill id is everything before the final underscore
- expect(extractMethod('github_list_issues')).toBe('github_list');
- expect(extractMethod('notion_create_page')).toBe('notion_create');
- expect(extractMethod('complex_skill_name_tool_name')).toBe('complex_skill_name_tool');
- expect(extractMethod('invalid_format')).toBe('invalid');
- expect(extractMethod('simpletool')).toBeNull();
- });
-
- test('extractCategoryFromSkillId should categorize skills correctly', () => {
- // Use reflection to access private method
- const extractMethod = (service as any).extractCategoryFromSkillId.bind(service);
-
- expect(extractMethod('github')).toBe('GitHub');
- expect(extractMethod('github_enterprise')).toBe('GitHub');
- expect(extractMethod('notion')).toBe('Notion');
- expect(extractMethod('telegram')).toBe('Telegram');
- expect(extractMethod('gmail')).toBe('Email');
- expect(extractMethod('calendar')).toBe('Calendar');
- expect(extractMethod('slack')).toBe('Slack');
- expect(extractMethod('crypto_wallet')).toBe('Crypto');
- expect(extractMethod('unknown_skill')).toBe('Other');
- });
- });
-
- describe('clearCache', () => {
- test('should clear cached tool schemas', async () => {
- const mockSchemas = [
- {
- type: 'function',
- function: {
- name: 'test_tool',
- description: 'Test tool',
- parameters: { type: 'object', properties: {} },
- },
- },
- ];
-
- mockInvoke.mockResolvedValue(mockSchemas);
-
- // Load schemas
- await service.loadToolSchemas();
- expect(mockInvoke).toHaveBeenCalledTimes(1);
-
- // Clear cache
- service.clearCache();
-
- // Load again - should call Tauri again
- await service.loadToolSchemas();
- expect(mockInvoke).toHaveBeenCalledTimes(2);
- });
- });
-});
diff --git a/app/src/services/agentToolRegistry.ts b/app/src/services/agentToolRegistry.ts
deleted file mode 100644
index 4febb2e47..000000000
--- a/app/src/services/agentToolRegistry.ts
+++ /dev/null
@@ -1,297 +0,0 @@
-/**
- * Agent Tool Registry Service
- *
- * Unified tool discovery and execution using the consolidated systems:
- * - telegram_get_tools: Get Telegram tools in OpenAI-compatible format
- * - telegram_execute_tool: Execute Telegram tools with enhanced validation
- * - Fallback to skill system for non-Telegram tools (temporary)
- */
-import { invoke } from '@tauri-apps/api/core';
-
-import type { AgentToolExecution, AgentToolSchema, IAgentToolRegistry } from '../types/agent';
-
-// OpenClaw format types from Rust
-interface OpenClawToolSchema {
- type: string;
- function: { name: string; description: string; parameters: Record };
-}
-
-interface OpenClawToolResult {
- success: boolean;
- output: string;
- error?: string;
- execution_time?: number;
-}
-
-export class AgentToolRegistry implements IAgentToolRegistry {
- private static instance: AgentToolRegistry;
- private toolSchemas: AgentToolSchema[] = [];
- private lastLoadTime = 0;
- private readonly CACHE_TTL = 5 * 60 * 1000; // 5 minutes
-
- static getInstance(): AgentToolRegistry {
- if (!this.instance) {
- this.instance = new AgentToolRegistry();
- }
- return this.instance;
- }
-
- /**
- * Load tool schemas from unified systems (Telegram + skill system fallback)
- */
- async loadToolSchemas(forceReload = false): Promise {
- const now = Date.now();
-
- // Return cached tools if still fresh
- if (!forceReload && this.toolSchemas.length > 0 && now - this.lastLoadTime < this.CACHE_TTL) {
- return this.toolSchemas;
- }
-
- try {
- console.log('🔧 Loading tool schemas from unified systems...');
-
- const allTools: AgentToolSchema[] = [];
-
- // Note: Telegram tools removed - no longer available
- console.log('🔧 Telegram tools not available (unified system removed)');
-
- // 2. Load other tools from skill system (fallback for non-Telegram)
- try {
- console.log('🔧 Loading non-Telegram tools from skill system...');
- const skillTools = await invoke('runtime_get_tool_schemas');
-
- // Filter out telegram tools to avoid duplicates
- const nonTelegramTools = skillTools.filter(
- tool =>
- !tool.function.name.includes('telegram') &&
- !tool.function.name.includes('tg') &&
- !this.extractCategoryFromSkillId(
- this.extractSkillIdFromToolName(tool.function.name) || ''
- ).includes('Telegram')
- );
-
- const skillSchemas = nonTelegramTools.map(tool => ({
- type: 'function' as const,
- function: {
- name: tool.function.name,
- description: tool.function.description,
- parameters: tool.function.parameters as AgentToolSchema['function']['parameters'],
- },
- }));
-
- allTools.push(...skillSchemas);
- console.log(`✅ Loaded ${skillSchemas.length} non-Telegram tools from skill system`);
- } catch (error) {
- console.warn('⚠️ Failed to load tools from skill system:', error);
- }
-
- this.toolSchemas = allTools;
- this.lastLoadTime = now;
-
- console.log(`✅ Tool registry updated: ${this.toolSchemas.length} total tools available`);
-
- return this.toolSchemas;
- } catch (error) {
- console.error('❌ Failed to load tool schemas:', error);
- throw new Error(`Failed to load tool schemas: ${error}`);
- }
- }
-
- /**
- * Execute a tool using unified systems (Telegram unified or skill system fallback)
- */
- async executeTool(
- skillId: string,
- toolName: string,
- toolArguments: string
- ): Promise {
- const startTime = Date.now();
- const executionId = `exec_${startTime}_${Math.random().toString(36).substr(2, 9)}`;
-
- const execution: AgentToolExecution = {
- id: executionId,
- toolName,
- skillId,
- arguments: toolArguments,
- status: 'running',
- startTime,
- };
-
- console.log(`🚀 [TOOL EXECUTION START] Executing tool: ${toolName} (skillId: ${skillId})`);
- console.log(`📝 [ARGUMENTS] Raw arguments:`, {
- arguments: toolArguments,
- type: typeof toolArguments,
- length: toolArguments?.length,
- isString: typeof toolArguments === 'string',
- parsed: (() => {
- try {
- return typeof toolArguments === 'string' ? JSON.parse(toolArguments) : toolArguments;
- } catch (e) {
- return 'Failed to parse: ' + e;
- }
- })(),
- });
-
- try {
- // Determine if this is a Telegram tool
- const isTelegramTool =
- skillId.includes('telegram') ||
- skillId.includes('tg') ||
- toolName.includes('telegram') ||
- toolName.includes('tg') ||
- this.extractCategoryFromSkillId(skillId).includes('Telegram');
-
- let result: OpenClawToolResult;
-
- if (isTelegramTool) {
- // Telegram tools no longer available
- console.log(`🔧 [TELEGRAM TOOL] Tool "${toolName}" not available (unified system removed)`);
- result = {
- success: false,
- output: '',
- error: 'Telegram tools are no longer available (unified system removed)',
- };
- } else {
- // Use skill system for non-Telegram tools
- const toolId = `${skillId}_${toolName}`;
- console.log(`🔧 [BEFORE INVOKE] Calling runtime_execute_tool with:`);
- console.log(` toolId: "${toolId}"`);
- console.log(` args: ${toolArguments}`);
- console.log(` args type: ${typeof toolArguments}`);
-
- result = await invoke('runtime_execute_tool', {
- toolId,
- args: toolArguments,
- });
- }
-
- console.log(`🔧 [AFTER INVOKE] Tool execution result:`, result);
-
- execution.endTime = Date.now();
- // Use execution time from Rust if available, otherwise calculate locally
- execution.executionTimeMs = result.execution_time || execution.endTime - execution.startTime;
-
- if (!result.success) {
- execution.status = 'error';
- execution.errorMessage = result.error || 'Unknown error occurred';
- execution.result = execution.errorMessage;
-
- console.log(`❌ Tool execution failed: ${toolName} (${execution.executionTimeMs}ms)`);
- console.log(`❌ Error:`, execution.errorMessage);
- } else {
- execution.status = 'success';
- execution.result = result.output;
-
- console.log(`✅ Tool execution completed: ${toolName} (${execution.executionTimeMs}ms)`);
- }
-
- return execution;
- } catch (error) {
- execution.endTime = Date.now();
- execution.executionTimeMs = execution.endTime - execution.startTime;
- execution.status = 'error';
- execution.errorMessage = error instanceof Error ? error.message : String(error);
- execution.result = execution.errorMessage;
-
- console.error(`❌ Tool execution error: ${toolName}`, error);
-
- return execution;
- }
- }
-
- /**
- * Get a specific tool by name
- */
- getToolByName(toolName: string): AgentToolSchema | undefined {
- return this.toolSchemas.find(tool => tool.function.name === toolName);
- }
-
- /**
- * Get all available tools
- */
- getAllTools(): AgentToolSchema[] {
- return [...this.toolSchemas];
- }
-
- /**
- * Get tools organized by skill
- */
- getToolsBySkill(): Record {
- const toolsBySkill: Record = {};
-
- for (const tool of this.toolSchemas) {
- // Extract skill ID from tool name (format: skillId_toolName)
- const skillId = this.extractSkillIdFromToolName(tool.function.name) || 'unknown';
-
- if (!toolsBySkill[skillId]) {
- toolsBySkill[skillId] = [];
- }
- toolsBySkill[skillId].push(tool);
- }
-
- return toolsBySkill;
- }
-
- /**
- * Get tool execution statistics
- */
- getToolStats(): { totalTools: number; skillCount: number; categories: Record } {
- const categories: Record = {};
- const skills = new Set();
-
- for (const tool of this.toolSchemas) {
- const skillId = this.extractSkillIdFromToolName(tool.function.name) || 'unknown';
- skills.add(skillId);
-
- // Categorize by skill name
- const category = this.extractCategoryFromSkillId(skillId);
- categories[category] = (categories[category] || 0) + 1;
- }
-
- return { totalTools: this.toolSchemas.length, skillCount: skills.size, categories };
- }
-
- /**
- * Clear the tool registry cache
- */
- clearCache(): void {
- this.toolSchemas = [];
- this.lastLoadTime = 0;
- console.log('🔧 Tool registry cache cleared');
- }
-
- // =============================================================================
- // Private Helper Methods
- // =============================================================================
-
- /**
- * Extract skill ID from tool name (format: skillId_toolName)
- */
- private extractSkillIdFromToolName(toolName: string): string | null {
- const underscoreIndex = toolName.lastIndexOf('_');
- if (underscoreIndex === -1) {
- return null;
- }
- return toolName.substring(0, underscoreIndex);
- }
-
- /**
- * Extract category name from skill ID for organization
- */
- private extractCategoryFromSkillId(skillId: string): string {
- // Common skill naming patterns
- if (skillId.includes('github') || skillId.includes('git')) return 'GitHub';
- if (skillId.includes('notion')) return 'Notion';
- if (skillId.includes('telegram') || skillId.includes('tg')) return 'Telegram';
- if (skillId.includes('email') || skillId.includes('gmail')) return 'Email';
- if (skillId.includes('calendar')) return 'Calendar';
- if (skillId.includes('slack')) return 'Slack';
- if (skillId.includes('discord')) return 'Discord';
- if (skillId.includes('twitter') || skillId.includes('x')) return 'Social';
- if (skillId.includes('file') || skillId.includes('fs')) return 'File System';
- if (skillId.includes('crypto') || skillId.includes('blockchain')) return 'Crypto';
- if (skillId.includes('ai') || skillId.includes('ml')) return 'AI/ML';
-
- return 'Other';
- }
-}
diff --git a/app/src/services/api/__tests__/managedDmApi.test.ts b/app/src/services/api/__tests__/managedDmApi.test.ts
deleted file mode 100644
index f76295bae..000000000
--- a/app/src/services/api/__tests__/managedDmApi.test.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { beforeEach, describe, expect, it, vi } from 'vitest';
-
-import { managedDmApi } from '../managedDmApi';
-
-vi.mock('../../apiClient', () => ({ apiClient: { post: vi.fn(), get: vi.fn() } }));
-
-const apiClient = vi.mocked((await import('../../apiClient')).apiClient);
-
-describe('managedDmApi', () => {
- beforeEach(() => {
- vi.clearAllMocks();
- });
-
- it('initiates managed dm through the backend api', async () => {
- apiClient.post.mockResolvedValueOnce({
- data: {
- token: 'dm-token',
- deepLink: 'https://t.me/openhuman_bot?start=manageddm_dm-token',
- expiresAt: '2026-04-04T12:00:00.000Z',
- },
- });
-
- await expect(managedDmApi.initiateManagedDm()).resolves.toEqual({
- token: 'dm-token',
- deepLink: 'https://t.me/openhuman_bot?start=manageddm_dm-token',
- expiresAt: '2026-04-04T12:00:00.000Z',
- });
- });
-
- it('polls until verified and returns the verified status', async () => {
- apiClient.get
- .mockResolvedValueOnce({ data: { verified: false, telegramUsername: null } })
- .mockResolvedValueOnce({ data: { verified: true, telegramUsername: 'telegram-user' } });
-
- await expect(
- managedDmApi.pollManagedDmStatusUntilVerified('dm-token', { intervalMs: 0, timeoutMs: 100 })
- ).resolves.toEqual({ verified: true, telegramUsername: 'telegram-user' });
- expect(apiClient.get).toHaveBeenCalledTimes(2);
- });
-});
diff --git a/app/src/services/api/actionableItemsApi.ts b/app/src/services/api/actionableItemsApi.ts
deleted file mode 100644
index 06564dfd9..000000000
--- a/app/src/services/api/actionableItemsApi.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-import type { ApiResponse } from '../../types/api';
-import { apiClient } from '../apiClient';
-
-interface ActionableItem {
- id: string;
- title: string;
- description?: string;
- status: 'pending' | 'dismissed' | 'snoozed' | 'completed';
- createdAt: string;
- updatedAt: string;
- // Add other fields based on backend schema
-}
-
-interface ExecutionSession {
- id: string;
- itemId: string;
- status: 'running' | 'pending' | 'completed' | 'failed';
- // Add other fields based on backend schema
-}
-
-interface ThreadData {
- threadId: string;
- conversationId: string;
-}
-
-/**
- * Actionable Items API endpoints
- */
-export const actionableItemsApi = {
- /**
- * List actionable items for the authenticated user
- * GET /telegram/actionable-items
- */
- getActionableItems: async (): Promise => {
- const response = await apiClient.get>(
- '/telegram/actionable-items'
- );
- return response.data;
- },
-
- /**
- * Update an actionable item (dismiss, snooze, etc.)
- * PATCH /telegram/actionable-items/:itemId
- */
- updateActionableItem: async (
- itemId: string,
- updates: { status?: ActionableItem['status']; snoozeUntil?: string }
- ): Promise => {
- const response = await apiClient.patch>(
- `/telegram/actionable-items/${itemId}`,
- updates
- );
- return response.data;
- },
-
- /**
- * Get or create conversation thread for an actionable item
- * GET /telegram/actionable-items/:itemId/thread
- */
- getItemThread: async (itemId: string): Promise => {
- const response = await apiClient.get>(
- `/telegram/actionable-items/${itemId}/thread`
- );
- return response.data;
- },
-
- /**
- * Get current execution session for an actionable item
- * GET /telegram/actionable-items/:itemId/session
- */
- getItemSession: async (itemId: string): Promise => {
- const response = await apiClient.get>(
- `/telegram/actionable-items/${itemId}/session`
- );
- return response.data;
- },
-
- /**
- * Start execution of an actionable item
- * POST /telegram/actionable-items/:itemId/execute
- */
- executeItem: async (itemId: string): Promise => {
- const response = await apiClient.post>(
- `/telegram/actionable-items/${itemId}/execute`
- );
- return response.data;
- },
-
- /**
- * Get execution session status
- * GET /telegram/execution-sessions/:sessionId
- */
- getExecutionSession: async (sessionId: string): Promise => {
- const response = await apiClient.get>(
- `/telegram/execution-sessions/${sessionId}`
- );
- return response.data;
- },
-
- /**
- * Confirm or reject pending execution step
- * POST /telegram/execution-sessions/:sessionId/confirm
- */
- confirmExecutionStep: async (
- sessionId: string,
- action: 'confirm' | 'reject',
- data?: unknown
- ): Promise => {
- const response = await apiClient.post>(
- `/telegram/execution-sessions/${sessionId}/confirm`,
- { action, data }
- );
- return response.data;
- },
-};
diff --git a/app/src/services/api/apiKeysApi.ts b/app/src/services/api/apiKeysApi.ts
deleted file mode 100644
index 82c70a6ae..000000000
--- a/app/src/services/api/apiKeysApi.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import type { ApiResponse } from '../../types/api';
-import { apiClient } from '../apiClient';
-
-interface ApiKey {
- id: string;
- name: string;
- keyPreview: string; // First few characters of the key
- createdAt: string;
- lastUsedAt?: string;
- // Add other fields based on backend schema
-}
-
-interface CreateApiKeyData {
- name: string;
-}
-
-interface CreateApiKeyResponse {
- id: string;
- name: string;
- key: string; // Full key only returned on creation
- createdAt: string;
-}
-
-/**
- * API Keys management endpoints
- */
-export const apiKeysApi = {
- /**
- * Create API key
- * POST /api-keys
- */
- createApiKey: async (data: CreateApiKeyData): Promise => {
- const response = await apiClient.post>('/api-keys', data);
- return response.data;
- },
-
- /**
- * List API keys
- * GET /api-keys
- */
- getApiKeys: async (): Promise => {
- const response = await apiClient.get>('/api-keys');
- return response.data;
- },
-
- /**
- * Revoke API key
- * DELETE /api-keys/:keyId
- */
- revokeApiKey: async (keyId: string): Promise => {
- await apiClient.delete>(`/api-keys/${keyId}`);
- },
-};
diff --git a/app/src/services/api/authApi.ts b/app/src/services/api/authApi.ts
index d28cb6d12..b79c84335 100644
--- a/app/src/services/api/authApi.ts
+++ b/app/src/services/api/authApi.ts
@@ -1,54 +1,5 @@
-import { base64ToBytes, encryptIntegrationTokens } from '../../utils/integrationTokensCrypto';
-import { callCoreCommand } from '../coreCommandClient';
import { callCoreRpc } from '../coreRpcClient';
-interface IntegrationTokensResponse {
- success: boolean;
- data?: { encrypted: string };
-}
-
-interface IntegrationTokensPayload {
- accessToken: string;
- refreshToken?: string;
- expiresAt: string;
-}
-
-type LinkableChannel = 'telegram' | 'discord';
-
-interface RawChannelLinkTokenData {
- token?: string;
- linkToken?: string;
- jwtToken?: string;
- url?: string;
- linkUrl?: string;
- authUrl?: string;
- deepLinkUrl?: string;
- expiresAt?: string;
- expires_at?: string;
- [key: string]: unknown;
-}
-
-export interface ChannelLinkTokenResult {
- token: string;
- launchUrl?: string;
- expiresAt?: string;
-}
-
-function bytesToHex(bytes: Uint8Array): string {
- return Array.from(bytes)
- .map(byte => byte.toString(16).padStart(2, '0'))
- .join('');
-}
-
-function normalizeKeyToHex(rawKey: string): string {
- const trimmed = rawKey.trim();
- const maybeHex = trimmed.replace(/^0x/i, '');
- if (maybeHex.length === 64 && /^[0-9a-fA-F]+$/.test(maybeHex)) {
- return maybeHex.toLowerCase();
- }
- return bytesToHex(base64ToBytes(trimmed));
-}
-
/**
* Consume a verified login token and return the JWT.
* Works for both Telegram and OAuth login tokens.
@@ -65,69 +16,3 @@ export async function consumeLoginToken(loginToken: string): Promise {
}
return jwtToken;
}
-
-/**
- * Fetch encrypted OAuth tokens for an integration using a client-provided key.
- * POST /auth/integrations/:integrationId/tokens (auth required)
- */
-export async function fetchIntegrationTokens(
- integrationId: string,
- key: string
-): Promise {
- const response = await callCoreRpc<{ result: IntegrationTokensPayload }>({
- method: 'openhuman.auth.oauth_fetch_integration_tokens',
- params: { integrationId, key },
- });
- const tokens = response.result;
- if (!tokens?.accessToken || !tokens?.expiresAt) {
- throw new Error('Integration token handoff did not return required fields');
- }
-
- const encrypted = await encryptIntegrationTokens(
- JSON.stringify({
- accessToken: tokens.accessToken,
- refreshToken: tokens.refreshToken ?? '',
- expiresAt: tokens.expiresAt,
- }),
- normalizeKeyToHex(key)
- );
- return { success: true, data: { encrypted } };
-}
-
-/**
- * Create a short-lived link token that can be handed to a messaging channel login flow.
- * POST /auth/channels/:channel/link-token (auth required)
- */
-export async function createChannelLinkToken(
- channel: LinkableChannel
-): Promise {
- const data = await callCoreCommand(
- 'openhuman.auth_create_channel_link_token',
- { channel }
- );
- const token =
- typeof data?.token === 'string'
- ? data.token
- : typeof data?.linkToken === 'string'
- ? data.linkToken
- : typeof data?.jwtToken === 'string'
- ? data.jwtToken
- : '';
-
- if (!token) {
- throw new Error('Channel link token response missing token');
- }
-
- const launchUrlCandidates = [data?.url, data?.linkUrl, data?.authUrl, data?.deepLinkUrl];
- const launchUrl = launchUrlCandidates.find(
- (value): value is string => typeof value === 'string' && value.trim().length > 0
- );
- const expiresAt =
- typeof data?.expiresAt === 'string'
- ? data.expiresAt
- : typeof data?.expires_at === 'string'
- ? data.expires_at
- : undefined;
-
- return { token, launchUrl, expiresAt };
-}
diff --git a/app/src/services/api/feedbackApi.ts b/app/src/services/api/feedbackApi.ts
deleted file mode 100644
index be2ecec6d..000000000
--- a/app/src/services/api/feedbackApi.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import type { ApiResponse } from '../../types/api';
-import { apiClient } from '../apiClient';
-
-interface FeedbackItem {
- id: string;
- type: 'bug' | 'feature_request' | 'general';
- title: string;
- description: string;
- steps?: string;
- status: 'open' | 'in_progress' | 'closed';
- createdAt: string;
- updatedAt: string;
- // Add other fields based on backend schema
-}
-
-interface CreateFeedbackData {
- type: FeedbackItem['type'];
- title: string;
- description: string;
- steps?: string;
-}
-
-interface UpdateFeedbackData {
- title?: string;
- description?: string;
- steps?: string;
-}
-
-/**
- * Feedback API endpoints
- */
-export const feedbackApi = {
- /**
- * Submit feedback (bug, feature_request, general)
- * POST /feedback
- */
- createFeedback: async (feedback: CreateFeedbackData): Promise => {
- const response = await apiClient.post>('/feedback', feedback);
- return response.data;
- },
-
- /**
- * List current user's feedback
- * GET /feedback
- */
- getFeedback: async (): Promise => {
- const response = await apiClient.get>('/feedback');
- return response.data;
- },
-
- /**
- * Get a single feedback item
- * GET /feedback/:id
- */
- getFeedbackById: async (id: string): Promise => {
- const response = await apiClient.get>(`/feedback/${id}`);
- return response.data;
- },
-
- /**
- * Update feedback (description, steps, etc.)
- * PUT /feedback/:id
- */
- updateFeedback: async (id: string, updates: UpdateFeedbackData): Promise => {
- const response = await apiClient.put>(`/feedback/${id}`, updates);
- return response.data;
- },
-};
diff --git a/app/src/services/api/inferenceApi.ts b/app/src/services/api/inferenceApi.ts
deleted file mode 100644
index 386c7bbdd..000000000
--- a/app/src/services/api/inferenceApi.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-import { apiClient } from '../apiClient';
-
-// ── Request types ────────────────────────────────────────────────────────────
-
-export type ChatRole = 'system' | 'user' | 'assistant' | 'tool';
-
-export interface ToolCall {
- id: string;
- type: 'function';
- function: { name: string; arguments: string };
-}
-
-export interface ChatMessage {
- role: ChatRole;
- content: string | null;
- tool_calls?: ToolCall[];
- tool_call_id?: string;
-}
-
-export interface ToolFunction {
- name: string;
- description: string;
- parameters: Record;
-}
-
-export interface Tool {
- type: 'function';
- function: ToolFunction;
-}
-
-export interface ChatCompletionRequest {
- model: string;
- messages: ChatMessage[];
- tools?: Tool[];
- tool_choice?: 'auto' | 'none' | { type: 'function'; function: { name: string } };
- openhuman?: { trace_tools?: boolean };
- stream?: boolean;
- temperature?: number;
- max_tokens?: number;
-}
-
-export interface TextCompletionRequest {
- model: string;
- prompt: string;
- stream?: boolean;
- temperature?: number;
- max_tokens?: number;
-}
-
-// ── Response types (OpenAI-compatible) ───────────────────────────────────────
-
-export interface ModelInfo {
- id: string;
- object: string;
- created: number;
- owned_by: string;
-}
-
-export interface ModelsListResponse {
- object: string;
- data: ModelInfo[];
-}
-
-export interface ChatCompletionChoice {
- index: number;
- message: ChatMessage & { tool_calls?: ToolCall[] };
- finish_reason: string | null;
-}
-
-export interface TextCompletionChoice {
- index: number;
- text: string;
- finish_reason: string | null;
-}
-
-export interface CompletionUsage {
- prompt_tokens: number;
- completion_tokens: number;
- total_tokens: number;
-}
-
-export interface ChatCompletionResponse {
- id: string;
- object: string;
- created: number;
- model: string;
- choices: ChatCompletionChoice[];
- usage: CompletionUsage;
-}
-
-export interface TextCompletionResponse {
- id: string;
- object: string;
- created: number;
- model: string;
- choices: TextCompletionChoice[];
- usage: CompletionUsage;
-}
-
-// ── API ───────────────────────────────────────────────────────────────────────
-
-export const inferenceApi = {
- /** GET /openai/v1/models — list available models */
- listModels: async (): Promise => {
- return apiClient.get('/openai/v1/models');
- },
-
- /** POST /openai/v1/chat/completions — create a chat completion */
- createChatCompletion: async (body: ChatCompletionRequest): Promise => {
- return apiClient.post('/openai/v1/chat/completions', body);
- },
-
- /** POST /openai/v1/completions — create a text completion */
- createCompletion: async (body: TextCompletionRequest): Promise => {
- return apiClient.post('/openai/v1/completions', body);
- },
-};
diff --git a/app/src/services/api/managedDmApi.ts b/app/src/services/api/managedDmApi.ts
deleted file mode 100644
index 3d6e394bf..000000000
--- a/app/src/services/api/managedDmApi.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-import { apiClient } from '../apiClient';
-
-export interface ManagedDmInitiateResponse {
- token: string;
- deepLink: string;
- expiresAt: string;
-}
-
-export interface ManagedDmStatusResponse {
- verified: boolean;
- telegramUsername?: string | null;
- expiresAt?: string;
-}
-
-interface ApiEnvelope {
- success: boolean;
- data: T;
-}
-
-export interface ManagedDmPollOptions {
- intervalMs?: number;
- timeoutMs?: number;
- signal?: AbortSignal;
-}
-
-const DEFAULT_POLL_INTERVAL_MS = 3_000;
-const DEFAULT_POLL_TIMEOUT_MS = 5 * 60 * 1_000;
-
-const sleep = (ms: number, signal?: AbortSignal): Promise =>
- new Promise(resolve => {
- if (signal?.aborted) {
- resolve();
- return;
- }
-
- const timeoutId = window.setTimeout(() => {
- cleanup();
- resolve();
- }, ms);
-
- const onAbort = () => {
- cleanup();
- resolve();
- };
-
- const cleanup = () => {
- window.clearTimeout(timeoutId);
- signal?.removeEventListener('abort', onAbort);
- };
-
- signal?.addEventListener('abort', onAbort, { once: true });
- });
-
-export async function initiateManagedDm(): Promise {
- const response = await apiClient.post>(
- '/telegram/managed-dm/initiate'
- );
- return response.data;
-}
-
-export async function getManagedDmStatus(token: string): Promise {
- const response = await apiClient.get>(
- `/telegram/managed-dm/status/${encodeURIComponent(token)}`
- );
- return response.data;
-}
-
-export async function pollManagedDmStatusUntilVerified(
- token: string,
- options: ManagedDmPollOptions = {}
-): Promise {
- const intervalMs = options.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
- const timeoutMs = options.timeoutMs ?? DEFAULT_POLL_TIMEOUT_MS;
- const startedAt = Date.now();
-
- while (Date.now() - startedAt < timeoutMs) {
- if (options.signal?.aborted) {
- return null;
- }
-
- try {
- const status = await getManagedDmStatus(token);
- if (status.verified) {
- return status;
- }
- } catch {
- // Best-effort polling: keep trying until timeout or cancellation.
- }
-
- await sleep(intervalMs, options.signal);
- }
-
- return null;
-}
-
-export const managedDmApi = {
- initiateManagedDm,
- getManagedDmStatus,
- pollManagedDmStatusUntilVerified,
-};
diff --git a/app/src/services/api/settingsApi.ts b/app/src/services/api/settingsApi.ts
deleted file mode 100644
index f74a07ffe..000000000
--- a/app/src/services/api/settingsApi.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import type { ApiResponse } from '../../types/api';
-import { apiClient } from '../apiClient';
-
-interface UserSettings {
- // Add specific settings types based on backend schema
- [key: string]: unknown;
-}
-
-/**
- * Settings API endpoints
- */
-export const settingsApi = {
- /**
- * Get user settings
- * GET /settings
- */
- getSettings: async (): Promise => {
- const response = await apiClient.get>('/settings');
- return response.data;
- },
-
- /**
- * Update user settings
- * PATCH /settings
- */
- updateSettings: async (settings: Partial): Promise => {
- const response = await apiClient.patch>('/settings', settings);
- return response.data;
- },
-
- /**
- * Set platforms connected
- * POST /settings/platforms-connected
- */
- setPlatformsConnected: async (platforms: string[]): Promise => {
- await apiClient.post>('/settings/platforms-connected', { platforms });
- },
-};
diff --git a/app/src/services/daemonHealthService.ts b/app/src/services/daemonHealthService.ts
index 2a2a8c671..e61fc235f 100644
--- a/app/src/services/daemonHealthService.ts
+++ b/app/src/services/daemonHealthService.ts
@@ -1,8 +1,7 @@
/**
* Daemon Health Service
*
- * Manages health monitoring for the openhuman daemon by polling
- * `openhuman.health_snapshot` over core RPC from the frontend.
+ * Polls the Rust core health snapshot and keeps the frontend daemon store in sync.
*/
import {
type ComponentHealth,
@@ -15,14 +14,10 @@ import { callCoreRpc } from './coreRpcClient';
export class DaemonHealthService {
private healthTimeoutId: ReturnType | null = null;
- private readonly HEALTH_TIMEOUT_MS = 30000; // 30 seconds
+ private readonly HEALTH_TIMEOUT_MS = 30000;
private pollingIntervalId: ReturnType | null = null;
private readonly POLL_MS = 2000;
- /**
- * Setup health event listener from the Rust daemon.
- * Should be called once when the app starts in Tauri mode.
- */
async setupHealthListener(): Promise<(() => void) | null> {
if (this.pollingIntervalId) {
return () => this.cleanup();
@@ -37,7 +32,7 @@ export class DaemonHealthService {
this.startHealthTimeout();
}
} catch {
- // Health endpoint can fail while sidecar is starting; timeout will mark disconnected.
+ // The health endpoint can fail while the sidecar is starting.
}
};
@@ -50,9 +45,6 @@ export class DaemonHealthService {
return () => this.cleanup();
}
- /**
- * Cleanup the health event listener.
- */
cleanup(): void {
if (this.pollingIntervalId) {
clearInterval(this.pollingIntervalId);
@@ -65,9 +57,6 @@ export class DaemonHealthService {
}
}
- /**
- * Parse the health snapshot received from Rust.
- */
private parseHealthSnapshot(payload: unknown): HealthSnapshot | null {
try {
if (!payload || typeof payload !== 'object') {
@@ -75,8 +64,6 @@ export class DaemonHealthService {
}
const data = payload as Record;
-
- // Validate required fields
if (
typeof data.pid !== 'number' ||
typeof data.updated_at !== 'string' ||
@@ -87,7 +74,6 @@ export class DaemonHealthService {
return null;
}
- // Parse components
const components: Record = {};
const componentsData = data.components as Record;
@@ -105,13 +91,12 @@ export class DaemonHealthService {
continue;
}
- // Validate status is a valid ComponentStatus
if (comp.status !== 'ok' && comp.status !== 'error' && comp.status !== 'starting') {
continue;
}
components[name] = {
- status: comp.status as 'ok' | 'error' | 'starting',
+ status: comp.status,
updated_at: comp.updated_at,
last_ok: typeof comp.last_ok === 'string' ? comp.last_ok : undefined,
last_error: typeof comp.last_error === 'string' ? comp.last_error : undefined,
@@ -120,9 +105,9 @@ export class DaemonHealthService {
}
return {
- pid: data.pid as number,
- updated_at: data.updated_at as string,
- uptime_seconds: data.uptime_seconds as number,
+ pid: data.pid,
+ updated_at: data.updated_at,
+ uptime_seconds: data.uptime_seconds,
components,
};
} catch (error) {
@@ -131,33 +116,20 @@ export class DaemonHealthService {
}
}
- /**
- * Update daemon state based on received health snapshot.
- */
private updateDaemonStoreFromHealth(snapshot: HealthSnapshot): void {
- const userId = this.getUserId();
-
try {
- updateHealthSnapshot(userId, snapshot);
+ updateHealthSnapshot(this.getUserId(), snapshot);
} catch (error) {
console.error('[DaemonHealth] Error updating daemon store from health:', error);
}
}
- /**
- * Start or restart the health timeout.
- * If no health events are received within the timeout period,
- * the daemon status will be set to 'disconnected'.
- */
private startHealthTimeout(): void {
- // Clear existing timeout
if (this.healthTimeoutId) {
clearTimeout(this.healthTimeoutId);
}
const userId = this.getUserId();
-
- // Set new timeout
this.healthTimeoutId = setTimeout(() => {
console.warn('[DaemonHealth] Health timeout reached - setting status to disconnected');
setDaemonStatus(userId, 'disconnected');
@@ -165,19 +137,25 @@ export class DaemonHealthService {
}, this.HEALTH_TIMEOUT_MS);
}
- /**
- * Get the current user ID for daemon state management.
- */
private getUserId(): string {
const token = getCoreStateSnapshot().snapshot.sessionToken;
- if (!token) return '__pending__';
+ if (!token) {
+ return '__pending__';
+ }
try {
const parts = token.split('.');
- if (parts.length !== 3) return '__pending__';
+ if (parts.length !== 3) {
+ return '__pending__';
+ }
+
const payloadBase64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
const payloadJson = atob(payloadBase64);
- const payload = JSON.parse(payloadJson);
+ const payload = JSON.parse(payloadJson) as {
+ sub?: string;
+ tgUserId?: string;
+ userId?: string;
+ };
return payload.tgUserId || payload.userId || payload.sub || '__pending__';
} catch {
return '__pending__';
@@ -185,5 +163,4 @@ export class DaemonHealthService {
}
}
-// Export singleton instance
export const daemonHealthService = new DaemonHealthService();
diff --git a/app/src/services/intelligenceApi.ts b/app/src/services/intelligenceApi.ts
deleted file mode 100644
index 293ea3358..000000000
--- a/app/src/services/intelligenceApi.ts
+++ /dev/null
@@ -1,186 +0,0 @@
-import type { ActionableItemStatus } from '../types/intelligence';
-import { apiClient } from './apiClient';
-
-/**
- * Backend API response types for Intelligence system
- */
-export interface BackendActionableItem {
- id: string;
- title: string;
- description?: string;
- source: string;
- priority: string;
- status: string;
- createdAt: string;
- updatedAt: string;
- expiresAt?: string;
- snoozeUntil?: string;
- actionable: boolean;
- requiresConfirmation?: boolean;
- hasComplexAction?: boolean;
- dismissedAt?: string;
- completedAt?: string;
- reminderCount?: number;
- // Backend-specific fields
- threadId?: string;
- executionStatus?: 'idle' | 'running' | 'completed' | 'failed';
- currentSessionId?: string;
-}
-
-export interface BackendChatMessage {
- id: string;
- content: string;
- role: 'user' | 'assistant';
- timestamp: string;
- threadId: string;
-}
-
-export interface BackendThreadResponse {
- threadId: string;
- messages: BackendChatMessage[];
-}
-
-export interface BackendExecutionResponse {
- executionId: string;
- sessionId: string;
- status: 'started' | 'running' | 'completed' | 'failed';
-}
-
-export interface ConnectedTool {
- name: string;
- description: string;
- parameters: Record;
- skillId: string;
- enabled: boolean;
-}
-
-/**
- * Intelligence API Service - handles all REST API calls for the Intelligence system
- */
-export class IntelligenceApiService {
- /**
- * Get all actionable items from the backend
- */
- async getActionableItems(): Promise {
- try {
- const response = await apiClient.get<{ items: BackendActionableItem[] }>(
- '/telegram/actionable-items'
- );
- return response.items || [];
- } catch (error) {
- console.error('Failed to fetch actionable items:', error);
- throw error;
- }
- }
-
- /**
- * Update the status of an actionable item
- */
- async updateItemStatus(itemId: string, status: ActionableItemStatus): Promise {
- try {
- await apiClient.patch(`/actionable-items/${itemId}`, { status });
- } catch (error) {
- console.error('Failed to update item status:', error);
- throw error;
- }
- }
-
- /**
- * Snooze an actionable item until a specific time
- */
- async snoozeItem(itemId: string, snoozeUntil: Date): Promise {
- try {
- await apiClient.patch(`/actionable-items/${itemId}`, {
- status: 'snoozed',
- snoozeUntil: snoozeUntil.toISOString(),
- });
- } catch (error) {
- console.error('Failed to snooze item:', error);
- throw error;
- }
- }
-
- /**
- * Get or create a conversation thread for an actionable item
- */
- async getOrCreateThread(itemId: string): Promise {
- try {
- const response = await apiClient.get(`/${itemId}/thread`);
- return response;
- } catch (error) {
- console.error('Failed to get or create thread:', error);
- throw error;
- }
- }
-
- /**
- * Get chat history for a specific thread
- */
- async getChatHistory(threadId: string): Promise {
- try {
- const response = await apiClient.get<{ messages: BackendChatMessage[] }>(
- `/threads/${threadId}/messages`
- );
- return response.messages || [];
- } catch (error) {
- console.error('Failed to get chat history:', error);
- throw error;
- }
- }
-
- /**
- * Start task execution for an actionable item with connected tools
- */
- async executeTask(
- itemId: string,
- connectedTools: ConnectedTool[]
- ): Promise {
- try {
- const response = await apiClient.post(`/${itemId}/execute`, {
- connectedTools,
- });
- return response;
- } catch (error) {
- console.error('Failed to execute task:', error);
- throw error;
- }
- }
-
- /**
- * Get execution status for a specific execution ID
- */
- async getExecutionStatus(
- executionId: string
- ): Promise<{
- status: 'running' | 'completed' | 'failed';
- progress?: Array>;
- result?: unknown;
- }> {
- try {
- const response = await apiClient.get(`/executions/${executionId}/status`);
- return response as {
- status: 'running' | 'completed' | 'failed';
- progress?: Array>;
- result?: unknown;
- };
- } catch (error) {
- console.error('Failed to get execution status:', error);
- throw error;
- }
- }
-
- /**
- * Cancel a running execution
- */
- async cancelExecution(executionId: string): Promise {
- try {
- await apiClient.post(`/executions/${executionId}/cancel`);
- } catch (error) {
- console.error('Failed to cancel execution:', error);
- throw error;
- }
- }
-}
-
-// Export singleton instance
-export const intelligenceApi = new IntelligenceApiService();
diff --git a/app/src/types/agent.ts b/app/src/types/agent.ts
deleted file mode 100644
index 673500dec..000000000
--- a/app/src/types/agent.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-/**
- * Agent Tool Registry Types
- *
- * Minimal type definitions for agent tool registry functionality.
- * Based on OpenClaw compatibility requirements.
- */
-
-/**
- * Tool schema for AI provider registration (OpenAI function calling format)
- */
-export interface AgentToolSchema {
- type: 'function';
- function: {
- name: string;
- description: string;
- parameters: {
- type: 'object';
- properties: Record;
- required?: string[];
- additionalProperties?: boolean;
- };
- };
-}
-
-/**
- * Tool execution record
- */
-export interface AgentToolExecution {
- id: string;
- toolName: string;
- skillId: string;
- arguments: string; // JSON string
- status: 'running' | 'success' | 'error';
- startTime: number;
- endTime?: number;
- executionTimeMs?: number;
- result?: string;
- errorMessage?: string;
-}
-
-/**
- * Agent tool registry service interface
- */
-export interface IAgentToolRegistry {
- /**
- * Load tool schemas from the skill system
- */
- loadToolSchemas(forceReload?: boolean): Promise;
-
- /**
- * Execute a tool using the skill system
- */
- executeTool(
- skillId: string,
- toolName: string,
- toolArguments: string
- ): Promise;
-
- /**
- * Get a specific tool by name
- */
- getToolByName(toolName: string): AgentToolSchema | undefined;
-
- /**
- * Get all available tools
- */
- getAllTools(): AgentToolSchema[];
-}
diff --git a/app/src/types/intelligence-chat-api.ts b/app/src/types/intelligence-chat-api.ts
deleted file mode 100644
index b00ec90dd..000000000
--- a/app/src/types/intelligence-chat-api.ts
+++ /dev/null
@@ -1,387 +0,0 @@
-/**
- * Intelligence Chat API Types
- *
- * TypeScript definitions for backend integration
- * These types ensure consistency between frontend and backend implementations
- */
-
-// ===== Core Types =====
-
-export type ConversationFlow =
- | 'discovery'
- | 'planning'
- | 'confirmation'
- | 'execution'
- | 'completion'
- | 'auto_close';
-
-export type MessageType =
- | 'message'
- | 'plan'
- | 'progress'
- | 'completion'
- | 'discovery_question'
- | 'confirmation_request';
-
-export type TaskStatus = 'pending' | 'in_progress' | 'completed' | 'failed';
-
-export type SessionStatus = 'active' | 'executing' | 'completed' | 'failed';
-
-export type WorkflowType =
- | 'meeting_prep'
- | 'email_response'
- | 'system_task'
- | 'document_analysis'
- | 'calendar_management';
-
-// ===== Request/Response Types =====
-
-export interface CreateChatSessionRequest {
- actionable_item_id: string;
- user_id: string;
- context: { item_type: WorkflowType; metadata: Record };
-}
-
-export interface CreateChatSessionResponse {
- success: true;
- data: {
- session_id: string;
- initial_message: {
- content: string;
- type: MessageType;
- options: string[];
- context: Record;
- };
- expected_flow: ConversationFlow[];
- };
-}
-
-export interface ChatMessage {
- id: string;
- content: string;
- sender: 'ai' | 'user';
- timestamp: string; // ISO 8601
- type: MessageType;
- metadata?: Record;
-}
-
-export interface ChatSessionDetails {
- session_id: string;
- status: SessionStatus;
- current_flow: ConversationFlow;
- messages: ChatMessage[];
- context: Record;
- execution_id?: string;
-}
-
-export interface SendMessageRequest {
- content: string;
- sender: 'user';
- current_flow: ConversationFlow;
- context: Record;
-}
-
-export interface SendMessageResponse {
- success: true;
- data: {
- ai_response: {
- content: string;
- type: MessageType;
- next_flow: ConversationFlow;
- options: string[];
- metadata: Record;
- };
- should_execute: boolean;
- execution_plan?: ExecutionPlan;
- };
-}
-
-// ===== Execution Types =====
-
-export interface ExecutionStep {
- id: string;
- label: string;
- status: TaskStatus;
- estimated_duration: number;
- dependencies: string[];
- started_at?: string; // ISO 8601
- completed_at?: string; // ISO 8601
- progress_percentage?: number;
- result?: Record;
-}
-
-export interface ExecutionPlan {
- id: string;
- steps: ExecutionStep[];
- estimated_total_duration: number;
- requirements: { gmail_access?: boolean; notion_access?: boolean; calendar_access?: boolean };
-}
-
-export interface StartExecutionRequest {
- execution_plan_id: string;
- confirmed: true;
- modifications?: Record;
-}
-
-export interface StartExecutionResponse {
- success: true;
- data: {
- execution_id: string;
- status: 'started';
- estimated_duration: number;
- steps: ExecutionStep[];
- };
-}
-
-export interface ExecutionStatusResponse {
- success: true;
- data: {
- execution_id: string;
- status: 'running' | 'completed' | 'failed';
- current_step?: { id: string; label: string; status: TaskStatus; progress_percentage: number };
- completed_steps: string[];
- results?: ExecutionResults;
- };
-}
-
-export interface ExecutionResults {
- summary: string;
- artifacts: Artifact[];
- metrics: {
- total_duration: number;
- emails_processed?: number;
- documents_created?: number;
- apis_called?: number;
- };
-}
-
-export interface Artifact {
- type: 'notion_doc' | 'email_draft' | 'calendar_event' | 'document' | 'link';
- title: string;
- url: string;
- created_at: string; // ISO 8601
- metadata: Record;
-}
-
-// ===== WebSocket Event Types =====
-
-export interface WebSocketMessage {
- type: string;
- data: T;
- timestamp?: string; // ISO 8601
-}
-
-// Tool definition for chat initialization
-export interface ChatTool {
- name: string;
- description: string;
- inputSchema: { type: 'object'; properties: Record; required?: string[] };
-}
-
-// Client → Server Events
-export interface ChatInitEvent {
- type: 'chat:init';
- data: { tools: ChatTool[]; sessionId?: string; timestamp: number };
-}
-
-export interface AuthenticateEvent {
- type: 'authenticate';
- data: { token: string; session_id: string };
-}
-
-export interface JoinSessionEvent {
- type: 'join_session';
- data: { session_id: string };
-}
-
-// Server → Client Events
-export interface AuthenticatedEvent {
- type: 'authenticated';
- data: { user_id: string; session_id: string };
-}
-
-export interface ExecutionStepProgressEvent {
- type: 'execution_step_progress';
- data: {
- session_id: string;
- execution_id: string;
- step_id: string;
- status: TaskStatus;
- progress_percentage: number;
- message: string;
- timestamp: string; // ISO 8601
- };
-}
-
-export interface ExecutionCompleteEvent {
- type: 'execution_complete';
- data: {
- session_id: string;
- execution_id: string;
- status: 'completed' | 'failed';
- results: ExecutionResults;
- };
-}
-
-export interface AIThinkingEvent {
- type: 'ai_thinking';
- data: { session_id: string; message: string; estimated_time: number };
-}
-
-export interface ErrorEvent {
- type: 'error';
- data: {
- session_id: string;
- error_code: ErrorCode;
- message: string;
- details: Record;
- retry_after?: number;
- };
-}
-
-// ===== Error Types =====
-
-export type ErrorCode =
- | 'SESSION_NOT_FOUND'
- | 'EXECUTION_FAILED'
- | 'RATE_LIMITED'
- | 'INVALID_INPUT'
- | 'SOURCE_UNAVAILABLE'
- | 'AI_UNAVAILABLE'
- | 'TIMEOUT'
- | 'UNAUTHORIZED'
- | 'FORBIDDEN'
- | 'INTERNAL_ERROR';
-
-export interface APIError {
- code: ErrorCode;
- message: string;
- details: Record;
-}
-
-// ===== Standard API Response Envelope =====
-
-export interface APIResponse {
- success: boolean;
- data: T | null;
- error: APIError | null;
- meta: {
- timestamp: string; // ISO 8601
- request_id: string;
- rate_limit: {
- remaining: number;
- reset_at: string; // ISO 8601
- };
- };
-}
-
-// ===== Meeting Preparation Specific Types =====
-
-export interface MeetingPrepWorkflow {
- workflow_type: 'meeting_preparation';
- inputs: {
- meeting_title: string;
- participant: string;
- time_context: string;
- document_sources: ('gmail' | 'notion' | 'calendar')[];
- output_format: 'notion_doc' | 'email_summary' | 'both';
- };
- processing_steps: MeetingPrepStep[];
-}
-
-export interface MeetingPrepStep {
- step: 'fetch_gmail' | 'access_notion' | 'analyze_context' | 'consolidate_docs' | 'generate_link';
- action: string;
- params: Record;
-}
-
-// ===== Service Integration Types =====
-
-export interface ExternalServiceConfig {
- gmail?: { enabled: boolean; scopes: string[]; rate_limit: number };
- notion?: { enabled: boolean; workspace_id: string; rate_limit: number };
- calendar?: { enabled: boolean; calendar_ids: string[] };
-}
-
-export interface ServiceAuthStatus {
- gmail: 'connected' | 'disconnected' | 'error';
- notion: 'connected' | 'disconnected' | 'error';
- calendar: 'connected' | 'disconnected' | 'error';
-}
-
-// ===== Performance & Monitoring Types =====
-
-export interface PerformanceMetrics {
- response_time: number;
- execution_time: number;
- external_api_calls: number;
- tokens_used: number;
- cost_estimate: number;
-}
-
-export interface SessionMetrics {
- session_id: string;
- user_id: string;
- started_at: string; // ISO 8601
- ended_at?: string; // ISO 8601
- total_messages: number;
- execution_count: number;
- performance: PerformanceMetrics;
- satisfaction_score?: number;
-}
-
-// ===== Type Guards =====
-
-export function isWebSocketMessage(obj: unknown): obj is WebSocketMessage {
- if (typeof obj !== 'object' || obj === null) return false;
- const o = obj as Record;
- return typeof o.type === 'string' && 'data' in o;
-}
-
-export function isAPIResponse(obj: unknown): obj is APIResponse {
- return (
- typeof obj === 'object' &&
- obj !== null &&
- typeof (obj as { success?: unknown }).success === 'boolean' &&
- ('data' in (obj as object) || 'error' in (obj as object)) &&
- 'meta' in (obj as object)
- );
-}
-
-export function isExecutionStepProgressEvent(obj: unknown): obj is ExecutionStepProgressEvent {
- if (!isWebSocketMessage(obj) || obj.type !== 'execution_step_progress') return false;
- const d = obj.data;
- return typeof d === 'object' && d !== null && 'step_id' in d && 'progress_percentage' in d;
-}
-
-// ===== Utility Types =====
-
-export type ChatEventHandler = (event: WebSocketMessage) => void;
-
-export interface ChatClientConfig {
- websocket_url: string;
- api_base_url: string;
- auth_token: string;
- retry_attempts: number;
- timeout_ms: number;
-}
-
-// ===== Frontend State Types =====
-
-export interface ChatState {
- session: ChatSessionDetails | null;
- messages: ChatMessage[];
- currentFlow: ConversationFlow;
- isExecuting: boolean;
- executionProgress: ExecutionStep[];
- isConnected: boolean;
- lastError: APIError | null;
-}
-
-export interface ChatActions {
- sendMessage: (content: string) => Promise;
- startExecution: (planId: string) => Promise;
- connect: () => Promise;
- disconnect: () => void;
- clearError: () => void;
-}
diff --git a/app/src/types/onboarding.ts b/app/src/types/onboarding.ts
deleted file mode 100644
index 88e612edf..000000000
--- a/app/src/types/onboarding.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-// Onboarding flow types and interfaces
-
-export interface OnboardingStep {
- id: string;
- title: string;
- description: string;
- component: React.ComponentType;
-}
-
-export interface UserProfile {
- name?: string;
- phone?: string;
- countryCode?: string;
- privacySettings?: PrivacySettings;
- analyticsPreferences?: AnalyticsPreferences;
-}
-
-export interface PrivacySettings {
- enterpriseGradeSecurity: boolean;
- dataEncryption: boolean;
- secureBackups: boolean;
-}
-
-export interface AnalyticsPreferences {
- shareAnalytics: boolean;
- emailConnected: boolean;
- maximumPrivacy: boolean;
-}
-
-export interface WeatherData {
- location: string;
- temperature: number;
- condition: string;
- icon: string;
-}
-
-export interface Country {
- code: string;
- name: string;
- flag: string;
- dialCode: string;
-}
diff --git a/app/src/utils/deeplink.ts b/app/src/utils/deeplink.ts
deleted file mode 100644
index 652bc1bcd..000000000
--- a/app/src/utils/deeplink.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-export type WebLoginMethod = 'phone' | 'telegram';
-
-export interface PhoneLoginContext {
- method: 'phone';
- phoneNumber: string;
- countryCode: string;
-}
-
-// The shape of the Telegram user object is defined by Telegram.
-// We keep it as unknown here and let the backend interpret it.
-export interface TelegramLoginContext {
- method: 'telegram';
- telegramUser: unknown;
-}
-
-export type WebLoginContext = PhoneLoginContext | TelegramLoginContext;
-
-const DESKTOP_SCHEME = 'openhuman';
-
-export const buildDesktopDeeplink = (token: string): string => {
- const encoded = encodeURIComponent(token);
- return `${DESKTOP_SCHEME}://auth?token=${encoded}`;
-};
-
-export const buildPaymentSuccessDeeplink = (sessionId: string): string => {
- const encoded = encodeURIComponent(sessionId);
- return `${DESKTOP_SCHEME}://payment/success?session_id=${encoded}`;
-};
-
-export const buildPaymentCancelDeeplink = (): string => {
- return `${DESKTOP_SCHEME}://payment/cancel`;
-};
diff --git a/app/src/utils/intelligenceTransforms.ts b/app/src/utils/intelligenceTransforms.ts
deleted file mode 100644
index d6ad26136..000000000
--- a/app/src/utils/intelligenceTransforms.ts
+++ /dev/null
@@ -1,210 +0,0 @@
-import type { MCPTool } from '../lib/mcp';
-import type {
- BackendActionableItem,
- BackendChatMessage,
- ConnectedTool,
-} from '../services/intelligenceApi';
-import type {
- ActionableItem,
- ActionableItemPriority,
- ActionableItemSource,
- ActionableItemStatus,
- ChatMessage,
-} from '../types/intelligence';
-
-/**
- * Transform backend actionable item to frontend format
- */
-export function transformBackendItemToFrontend(backendItem: BackendActionableItem): ActionableItem {
- return {
- id: backendItem.id,
- title: backendItem.title,
- description: backendItem.description,
- source: backendItem.source as ActionableItemSource,
- priority: backendItem.priority as ActionableItemPriority,
- status: backendItem.status as ActionableItemStatus,
- createdAt: new Date(backendItem.createdAt),
- updatedAt: new Date(backendItem.updatedAt),
- expiresAt: backendItem.expiresAt ? new Date(backendItem.expiresAt) : undefined,
- snoozeUntil: backendItem.snoozeUntil ? new Date(backendItem.snoozeUntil) : undefined,
- actionable: backendItem.actionable,
- requiresConfirmation: backendItem.requiresConfirmation,
- hasComplexAction: backendItem.hasComplexAction,
- dismissedAt: backendItem.dismissedAt ? new Date(backendItem.dismissedAt) : undefined,
- completedAt: backendItem.completedAt ? new Date(backendItem.completedAt) : undefined,
- reminderCount: backendItem.reminderCount,
- // Backend integration fields
- threadId: backendItem.threadId,
- executionStatus: backendItem.executionStatus,
- currentSessionId: backendItem.currentSessionId,
- };
-}
-
-/**
- * Transform multiple backend items to frontend format
- */
-export function transformBackendItemsToFrontend(
- backendItems: BackendActionableItem[]
-): ActionableItem[] {
- return backendItems.map(transformBackendItemToFrontend);
-}
-
-/**
- * Transform backend chat message to frontend format
- */
-export function transformBackendMessageToFrontend(backendMessage: BackendChatMessage): ChatMessage {
- return {
- id: backendMessage.id,
- content: backendMessage.content,
- sender: backendMessage.role === 'user' ? 'user' : 'ai',
- timestamp: new Date(backendMessage.timestamp),
- };
-}
-
-/**
- * Transform multiple backend messages to frontend format
- */
-export function transformBackendMessagesToFrontend(
- backendMessages: BackendChatMessage[]
-): ChatMessage[] {
- return backendMessages.map(transformBackendMessageToFrontend);
-}
-
-/**
- * Transform frontend chat message to backend format
- */
-export function transformFrontendMessageToBackend(
- message: ChatMessage,
- threadId: string
-): Omit {
- return {
- content: message.content,
- role: message.sender === 'user' ? 'user' : 'assistant',
- timestamp: message.timestamp.toISOString(),
- threadId,
- };
-}
-
-/**
- * Transform MCP tools to connected tools format for backend
- */
-export function transformMCPToConnectedTools(mcpTools: MCPTool[]): ConnectedTool[] {
- return mcpTools.map(tool => {
- const [skillId, toolName] = tool.name.split('__');
-
- return {
- name: toolName || tool.name,
- description: tool.description,
- parameters: (tool.inputSchema || {}) as unknown as Record,
- skillId: skillId || 'unknown',
- enabled: true,
- };
- });
-}
-
-/**
- * Transform connected tools back to MCP format
- */
-export function transformConnectedToolsToMCP(connectedTools: ConnectedTool[]): MCPTool[] {
- return connectedTools.map(tool => ({
- name: `${tool.skillId}__${tool.name}`,
- description: tool.description,
- inputSchema: { type: 'object', properties: tool.parameters || {} },
- }));
-}
-
-/**
- * Validate actionable item data from backend
- */
-export function validateBackendItem(item: unknown): item is BackendActionableItem {
- if (typeof item !== 'object' || item === null) return false;
- const o = item as Record;
- return (
- typeof o.id === 'string' &&
- typeof o.title === 'string' &&
- typeof o.source === 'string' &&
- typeof o.priority === 'string' &&
- typeof o.status === 'string' &&
- typeof o.createdAt === 'string' &&
- typeof o.updatedAt === 'string' &&
- typeof o.actionable === 'boolean'
- );
-}
-
-/**
- * Create a new chat message in frontend format
- */
-export function createChatMessage(
- content: string,
- sender: 'user' | 'ai',
- id?: string
-): ChatMessage {
- return {
- id: id || `${sender}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
- content,
- sender,
- timestamp: new Date(),
- };
-}
-
-/**
- * Map execution status to user-friendly labels
- */
-export function getExecutionStatusLabel(status?: string): string {
- switch (status) {
- case 'idle':
- return 'Ready';
- case 'running':
- return 'In Progress';
- case 'completed':
- return 'Completed';
- case 'failed':
- return 'Failed';
- default:
- return 'Unknown';
- }
-}
-
-/**
- * Get priority display information
- */
-export function getPriorityInfo(priority: ActionableItemPriority): {
- label: string;
- className: string;
- color: string;
-} {
- switch (priority) {
- case 'critical':
- return { label: 'Critical', className: 'text-coral-400 bg-coral-500/10', color: 'coral' };
- case 'important':
- return { label: 'Important', className: 'text-amber-400 bg-amber-500/10', color: 'amber' };
- case 'normal':
- return { label: 'Normal', className: 'text-sage-400 bg-sage-500/10', color: 'sage' };
- }
-}
-
-/**
- * Get source display information
- */
-export function getSourceInfo(source: ActionableItemSource): {
- label: string;
- icon: string;
- className: string;
-} {
- switch (source) {
- case 'email':
- return { label: 'Email', icon: '📧', className: 'text-blue-400 bg-blue-500/10' };
- case 'calendar':
- return { label: 'Calendar', icon: '📅', className: 'text-green-400 bg-green-500/10' };
- case 'telegram':
- return { label: 'Telegram', icon: '💬', className: 'text-blue-400 bg-blue-500/10' };
- case 'ai_insight':
- return { label: 'AI Insight', icon: '🤖', className: 'text-purple-400 bg-purple-500/10' };
- case 'system':
- return { label: 'System', icon: '⚙️', className: 'text-stone-400 bg-stone-500/10' };
- case 'trading':
- return { label: 'Trading', icon: '📈', className: 'text-yellow-400 bg-yellow-500/10' };
- case 'security':
- return { label: 'Security', icon: '🔒', className: 'text-red-400 bg-red-500/10' };
- }
-}
diff --git a/app/test/e2e/globals.d.ts b/app/test/e2e/globals.d.ts
deleted file mode 100644
index 6e1dfb538..000000000
--- a/app/test/e2e/globals.d.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-// Global declarations for E2E tests (WebDriverIO globals)
-declare namespace NodeJS {
- interface Global {
- browser: any;
- }
-}
-
-declare const browser: any;
diff --git a/package.json b/package.json
index 50d5bf896..941c6f3ee 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,8 @@
"dev:app": "yarn workspace openhuman-app dev:app",
"format": "yarn workspace openhuman-app format",
"format:check": "yarn workspace openhuman-app format:check",
+ "knip": "yarn workspace openhuman-app knip",
+ "knip:production": "yarn workspace openhuman-app knip:production",
"lint": "yarn workspace openhuman-app lint",
"lint:fix": "yarn workspace openhuman-app lint:fix",
"prepare": "husky",
diff --git a/src/core/cli.rs b/src/core/cli.rs
index 9533fb9f0..a84ca5b8d 100644
--- a/src/core/cli.rs
+++ b/src/core/cli.rs
@@ -355,10 +355,11 @@ fn run_namespace_command(
};
// Domain adapters can intercept specific namespace/function combinations.
- if args.len() > 1 && is_help(&args[1]) {
- if autocomplete_cli_adapter::maybe_print_start_help(namespace, function) {
- return Ok(());
- }
+ if args.len() > 1
+ && is_help(&args[1])
+ && autocomplete_cli_adapter::maybe_print_start_help(namespace, function)
+ {
+ return Ok(());
}
if let Some(value) =
autocomplete_cli_adapter::maybe_handle_namespace_start(namespace, function, &args[1..])?
diff --git a/src/core/logging.rs b/src/core/logging.rs
index db71d4e82..4f1d15b27 100644
--- a/src/core/logging.rs
+++ b/src/core/logging.rs
@@ -49,7 +49,7 @@ where
let time_styled = Style::new().dimmed().paint(time.to_string());
write!(writer, "{time_styled}:")?;
- let tag = format!("{level}");
+ let tag = level.to_string();
let level_styled = match *meta.level() {
Level::ERROR => Style::new().fg(Color::Red).bold().paint(tag),
Level::WARN => Style::new().fg(Color::Yellow).bold().paint(tag),
@@ -59,7 +59,7 @@ where
};
write!(writer, "{level_styled}:")?;
- let scope = format!("{target}");
+ let scope = target.to_string();
let scope_styled = Style::new().fg(Color::Fixed(247)).paint(scope);
write!(writer, "{scope_styled} ")?;
} else {
diff --git a/src/core/skills_cli.rs b/src/core/skills_cli.rs
index 7309da6cd..26de13711 100644
--- a/src/core/skills_cli.rs
+++ b/src/core/skills_cli.rs
@@ -236,7 +236,7 @@ fn run_skills_list(args: &[String]) -> Result<()> {
if manifests.is_empty() {
println!("No skills found.");
} else {
- println!("{:<20} {:<10} {}", "ID", "VERSION", "NAME");
+ println!("{:<20} {:<10} NAME", "ID", "VERSION");
println!("{}", "-".repeat(60));
for m in &manifests {
println!(
diff --git a/src/openhuman/agent/harness/self_healing.rs b/src/openhuman/agent/harness/self_healing.rs
index 4ef973965..703155303 100644
--- a/src/openhuman/agent/harness/self_healing.rs
+++ b/src/openhuman/agent/harness/self_healing.rs
@@ -168,7 +168,7 @@ fn extract_command_name(error: &str) -> Option {
// Pattern: "'CMD' is not recognized"
if error.contains("is not recognized") {
- let stripped = error.replace('\'', "").replace('"', "");
+ let stripped = error.replace(['\'', '"'], "");
if let Some(cmd) = stripped.split_whitespace().next() {
if cmd.len() < 64 {
return Some(cmd.to_string());
diff --git a/src/openhuman/agent/harness/types.rs b/src/openhuman/agent/harness/types.rs
index 7eb01b36f..0e577c6c4 100644
--- a/src/openhuman/agent/harness/types.rs
+++ b/src/openhuman/agent/harness/types.rs
@@ -11,7 +11,9 @@ pub type TaskId = String;
/// Current execution status of a DAG task node.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
+#[derive(Default)]
pub enum TaskStatus {
+ #[default]
Pending,
Running,
Completed,
@@ -19,12 +21,6 @@ pub enum TaskStatus {
Cancelled,
}
-impl Default for TaskStatus {
- fn default() -> Self {
- Self::Pending
- }
-}
-
/// Request sent from the orchestrator to spawn a sub-agent.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubAgentRequest {
diff --git a/src/openhuman/autocomplete/core/engine.rs b/src/openhuman/autocomplete/core/engine.rs
index 0d7ac301a..ed06f715d 100644
--- a/src/openhuman/autocomplete/core/engine.rs
+++ b/src/openhuman/autocomplete/core/engine.rs
@@ -188,10 +188,7 @@ impl AutocompleteEngine {
state.updated_at_ms = Some(Utc::now().timestamp_millis());
// Only notify if this is a *new* error message.
- let is_new_error = state
- .last_notified_error
- .as_ref()
- .map_or(true, |prev| prev != &err);
+ let is_new_error = state.last_notified_error.as_ref() != Some(&err);
if is_new_error {
state.last_notified_error = Some(err.clone());
}
diff --git a/src/openhuman/autocomplete/core/text.rs b/src/openhuman/autocomplete/core/text.rs
index 381e0a6f7..d369166d0 100644
--- a/src/openhuman/autocomplete/core/text.rs
+++ b/src/openhuman/autocomplete/core/text.rs
@@ -29,8 +29,7 @@ pub(super) fn sanitize_suggestion(text: &str) -> String {
}
};
let cleaned = stripped_prefix
- .replace('\t', " ")
- .replace('→', " ")
+ .replace(['\t', '→'], " ")
.replace('\r', "")
.split_whitespace()
.collect::>()
diff --git a/src/openhuman/channels/bus.rs b/src/openhuman/channels/bus.rs
index 086747f5f..75a966cac 100644
--- a/src/openhuman/channels/bus.rs
+++ b/src/openhuman/channels/bus.rs
@@ -12,6 +12,12 @@ use serde_json::json;
/// sending replies back to the originating channel via the backend REST API.
pub struct ChannelInboundSubscriber;
+impl Default for ChannelInboundSubscriber {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
impl ChannelInboundSubscriber {
pub fn new() -> Self {
Self
diff --git a/src/openhuman/channels/providers/presentation.rs b/src/openhuman/channels/providers/presentation.rs
index 631a16973..769a57b30 100644
--- a/src/openhuman/channels/providers/presentation.rs
+++ b/src/openhuman/channels/providers/presentation.rs
@@ -277,8 +277,8 @@ fn split_sentences(text: &str) -> Vec {
/// Group sentences into 2-3 bubbles.
fn group_sentences(sentences: &[String]) -> Vec {
- let target_count = std::cmp::min(3, (sentences.len() + 1) / 2);
- let group_size = (sentences.len() + target_count - 1) / target_count;
+ let target_count = std::cmp::min(3, sentences.len().div_ceil(2));
+ let group_size = sentences.len().div_ceil(target_count);
let mut groups: Vec = Vec::new();
for chunk in sentences.chunks(group_size) {
diff --git a/src/openhuman/config/schema/dictation.rs b/src/openhuman/config/schema/dictation.rs
index 02fc71607..fbc3d6c7e 100644
--- a/src/openhuman/config/schema/dictation.rs
+++ b/src/openhuman/config/schema/dictation.rs
@@ -6,19 +6,15 @@ use serde::{Deserialize, Serialize};
/// Activation mode for the dictation hotkey.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
+#[derive(Default)]
pub enum DictationActivationMode {
/// Press once to start, press again to stop.
Toggle,
/// Hold to record, release to stop (push-to-talk).
+ #[default]
Push,
}
-impl Default for DictationActivationMode {
- fn default() -> Self {
- Self::Push
- }
-}
-
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct DictationConfig {
/// Whether voice dictation is enabled.
diff --git a/src/openhuman/config/schema/load.rs b/src/openhuman/config/schema/load.rs
index 9c1610d7a..7656a4d35 100644
--- a/src/openhuman/config/schema/load.rs
+++ b/src/openhuman/config/schema/load.rs
@@ -30,7 +30,7 @@ struct ActiveWorkspaceState {
}
fn default_config_dir() -> Result {
- Ok(default_root_openhuman_dir()?)
+ default_root_openhuman_dir()
}
/// Returns the root openhuman directory (`~/.openhuman`), independent of any
diff --git a/src/openhuman/config/schema/orchestrator.rs b/src/openhuman/config/schema/orchestrator.rs
index 03b9a2d7a..fbadae0b4 100644
--- a/src/openhuman/config/schema/orchestrator.rs
+++ b/src/openhuman/config/schema/orchestrator.rs
@@ -47,7 +47,7 @@ pub struct OrchestratorConfig {
/// Per-archetype configuration override.
///
/// Any field left `None` uses the archetype's built-in default.
-#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
+#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
pub struct ArchetypeConfig {
/// Model name or hint override (e.g. "coding-v1", "local:phi3").
#[serde(default)]
@@ -104,16 +104,3 @@ impl Default for OrchestratorConfig {
}
}
}
-
-impl Default for ArchetypeConfig {
- fn default() -> Self {
- Self {
- model: None,
- system_prompt: None,
- temperature: None,
- max_tool_iterations: None,
- timeout_secs: None,
- sandbox: None,
- }
- }
-}
diff --git a/src/openhuman/config/schema/tools.rs b/src/openhuman/config/schema/tools.rs
index 68670e03f..1e811a385 100644
--- a/src/openhuman/config/schema/tools.rs
+++ b/src/openhuman/config/schema/tools.rs
@@ -260,7 +260,7 @@ impl Default for IntegrationToggle {
/// location search (Google Places), and phone calls (Twilio). The backend
/// handles external API calls, billing, and rate limiting; the client only
/// forwards requests and displays results.
-#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
+#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
pub struct IntegrationsConfig {
/// Master switch — set to `true` to register integration tools.
#[serde(default)]
@@ -286,16 +286,3 @@ pub struct IntegrationsConfig {
#[serde(default)]
pub parallel: IntegrationToggle,
}
-
-impl Default for IntegrationsConfig {
- fn default() -> Self {
- Self {
- enabled: false,
- backend_url: None,
- auth_token: None,
- twilio: IntegrationToggle::default(),
- google_places: IntegrationToggle::default(),
- parallel: IntegrationToggle::default(),
- }
- }
-}
diff --git a/src/openhuman/config/schema/voice_server.rs b/src/openhuman/config/schema/voice_server.rs
index fce571258..f1eaf89b6 100644
--- a/src/openhuman/config/schema/voice_server.rs
+++ b/src/openhuman/config/schema/voice_server.rs
@@ -6,19 +6,15 @@ use serde::{Deserialize, Serialize};
/// Activation mode for the voice server hotkey.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
+#[derive(Default)]
pub enum VoiceActivationMode {
/// Single press toggles recording on/off.
Tap,
/// Hold to record, release to stop.
+ #[default]
Push,
}
-impl Default for VoiceActivationMode {
- fn default() -> Self {
- Self::Push
- }
-}
-
/// Configuration for the voice dictation server.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct VoiceServerConfig {
diff --git a/src/openhuman/local_ai/parse.rs b/src/openhuman/local_ai/parse.rs
index a7657573c..60ec6bffa 100644
--- a/src/openhuman/local_ai/parse.rs
+++ b/src/openhuman/local_ai/parse.rs
@@ -19,15 +19,8 @@ pub(crate) fn parse_suggestions(raw: &str, limit: usize) -> Vec {
fn normalize_inline_text(value: &str) -> String {
value
- .replace('\u{200B}', "")
- .replace('\u{200C}', "")
- .replace('\u{200D}', "")
- .replace('\u{FEFF}', "")
- .replace('\u{00A0}', " ")
- .replace('\u{2028}', " ")
- .replace('\u{2029}', " ")
- .replace('\t', " ")
- .replace('→', " ")
+ .replace(['\u{200B}', '\u{200C}', '\u{200D}', '\u{FEFF}'], "")
+ .replace(['\u{00A0}', '\u{2028}', '\u{2029}', '\t', '→'], " ")
}
fn trim_generation_prefixes(mut value: &str) -> &str {
@@ -37,7 +30,7 @@ fn trim_generation_prefixes(mut value: &str) -> &str {
for prefix in ["suffix:", "completion:", "result:", "output:"] {
if value
.get(..prefix.len())
- .map_or(false, |s| s.eq_ignore_ascii_case(prefix))
+ .is_some_and(|s| s.eq_ignore_ascii_case(prefix))
{
value = value.get(prefix.len()..).unwrap_or(value).trim_start();
break;
diff --git a/src/openhuman/local_ai/service/whisper_engine.rs b/src/openhuman/local_ai/service/whisper_engine.rs
index 0d94798ad..6a73a0b17 100644
--- a/src/openhuman/local_ai/service/whisper_engine.rs
+++ b/src/openhuman/local_ai/service/whisper_engine.rs
@@ -352,7 +352,7 @@ fn decode_wav_to_f32(data: &[u8]) -> Result, String> {
}
pos += 8 + chunk_size;
- if chunk_size % 2 != 0 {
+ if !chunk_size.is_multiple_of(2) {
pos += 1;
}
}
diff --git a/src/openhuman/memory/store/unified/events.rs b/src/openhuman/memory/store/unified/events.rs
index 51505da33..be7c24735 100644
--- a/src/openhuman/memory/store/unified/events.rs
+++ b/src/openhuman/memory/store/unified/events.rs
@@ -197,7 +197,7 @@ pub fn events_for_segment(
ORDER BY created_at ASC",
)?;
let rows = stmt
- .query_map(params![segment_id], |row| row_to_event(row))?
+ .query_map(params![segment_id], row_to_event)?
.collect::, _>>()?;
Ok(rows)
}
@@ -318,7 +318,7 @@ pub fn extract_events_heuristic(text: &str) -> Vec<(EventType, String)> {
// Split into sentences (rough heuristic).
let sentences: Vec<&str> = text
- .split(|c: char| c == '.' || c == '!' || c == '?' || c == '\n')
+ .split(['.', '!', '?', '\n'])
.map(str::trim)
.filter(|s| s.len() > 5)
.collect();
diff --git a/src/openhuman/memory/store/unified/profile.rs b/src/openhuman/memory/store/unified/profile.rs
index 6b8ca3d0e..ddec35e62 100644
--- a/src/openhuman/memory/store/unified/profile.rs
+++ b/src/openhuman/memory/store/unified/profile.rs
@@ -189,7 +189,7 @@ pub fn profile_load_all(conn: &Arc>) -> anyhow::Result, _>>()?;
Ok(rows)
}
@@ -208,7 +208,7 @@ pub fn profile_facets_by_type(
ORDER BY evidence_count DESC",
)?;
let rows = stmt
- .query_map(params![facet_type.as_str()], |row| row_to_facet(row))?
+ .query_map(params![facet_type.as_str()], row_to_facet)?
.collect::, _>>()?;
Ok(rows)
}
diff --git a/src/openhuman/memory/store/unified/segments.rs b/src/openhuman/memory/store/unified/segments.rs
index 2d1e143f9..3d25a89dd 100644
--- a/src/openhuman/memory/store/unified/segments.rs
+++ b/src/openhuman/memory/store/unified/segments.rs
@@ -285,7 +285,7 @@ pub fn open_segment_for_session(
ORDER BY created_at DESC
LIMIT 1",
params![session_id],
- |row| row_to_segment(row),
+ row_to_segment,
)
.optional()?;
Ok(row)
@@ -308,7 +308,7 @@ pub fn segments_by_namespace(
LIMIT ?2",
)?;
let rows = stmt
- .query_map(params![namespace, limit as i64], |row| row_to_segment(row))?
+ .query_map(params![namespace, limit as i64], row_to_segment)?
.collect::, _>>()?;
Ok(rows)
}
@@ -327,7 +327,7 @@ pub fn segment_get(
FROM conversation_segments
WHERE segment_id = ?1",
params![segment_id],
- |row| row_to_segment(row),
+ row_to_segment,
)
.optional()?;
Ok(row)
@@ -349,7 +349,7 @@ pub fn segments_pending_summary(
LIMIT ?1",
)?;
let rows = stmt
- .query_map(params![limit as i64], |row| row_to_segment(row))?
+ .query_map(params![limit as i64], row_to_segment)?
.collect::, _>>()?;
Ok(rows)
}
diff --git a/src/openhuman/skills/qjs_skill_instance/js_handlers.rs b/src/openhuman/skills/qjs_skill_instance/js_handlers.rs
index c69114555..62d1eb9a7 100644
--- a/src/openhuman/skills/qjs_skill_instance/js_handlers.rs
+++ b/src/openhuman/skills/qjs_skill_instance/js_handlers.rs
@@ -623,7 +623,7 @@ pub(crate) async fn handle_js_call(
}
// Log progress every 2 seconds so we can see if the loop is running
- if poll_count % 400 == 0 {
+ if poll_count.is_multiple_of(400) {
let elapsed = 30
- deadline
.duration_since(tokio::time::Instant::now())
diff --git a/src/openhuman/skills/quickjs_libs/qjs_ops/ops_net.rs b/src/openhuman/skills/quickjs_libs/qjs_ops/ops_net.rs
index cd2f092cd..0c97b48ae 100644
--- a/src/openhuman/skills/quickjs_libs/qjs_ops/ops_net.rs
+++ b/src/openhuman/skills/quickjs_libs/qjs_ops/ops_net.rs
@@ -185,7 +185,7 @@ pub fn register<'js>(
}
{
- let ws = ws_state.clone();
+ let _ws = ws_state.clone();
ops.set(
"ws_recv",
Function::new(ctx.clone(), move |_id: u32| {
diff --git a/src/openhuman/skills/types.rs b/src/openhuman/skills/types.rs
index 965e4068b..d6a93ab3c 100644
--- a/src/openhuman/skills/types.rs
+++ b/src/openhuman/skills/types.rs
@@ -224,7 +224,7 @@ impl ToolResult {
.iter()
.filter_map(|c| match c {
ToolContent::Text { text } => Some(text.as_str()),
- ToolContent::Json { data } => None,
+ ToolContent::Json { data: _ } => None,
})
.collect::>()
.join("\n")
diff --git a/src/openhuman/socket/schemas.rs b/src/openhuman/socket/schemas.rs
index d1f6a4104..c94acb5da 100644
--- a/src/openhuman/socket/schemas.rs
+++ b/src/openhuman/socket/schemas.rs
@@ -195,7 +195,7 @@ fn handle_state(_params: Map) -> ControllerFuture {
let mgr = require_manager()?;
let state = mgr.get_state();
log::debug!("[socket:rpc] state → {:?}", state.status);
- Ok(serde_json::to_value(state).map_err(|e| format!("serialize: {e}"))?)
+ serde_json::to_value(state).map_err(|e| format!("serialize: {e}"))
})
}
diff --git a/src/openhuman/subconscious/engine.rs b/src/openhuman/subconscious/engine.rs
index 9aef37ef3..46c20fcde 100644
--- a/src/openhuman/subconscious/engine.rs
+++ b/src/openhuman/subconscious/engine.rs
@@ -56,19 +56,18 @@ impl SubconsciousEngine {
) -> Self {
// Seed default system tasks eagerly so they show in the UI immediately,
// without waiting for the first tick.
- let seeded =
- match store::with_connection(&workspace_dir, |conn| store::seed_default_tasks(conn)) {
- Ok(count) => {
- if count > 0 {
- info!("[subconscious] seeded {count} tasks on init");
- }
- true
+ let seeded = match store::with_connection(&workspace_dir, store::seed_default_tasks) {
+ Ok(count) => {
+ if count > 0 {
+ info!("[subconscious] seeded {count} tasks on init");
}
- Err(e) => {
- warn!("[subconscious] seed on init failed: {e}");
- false
- }
- };
+ true
+ }
+ Err(e) => {
+ warn!("[subconscious] seed on init failed: {e}");
+ false
+ }
+ };
Self {
workspace_dir,
@@ -416,7 +415,7 @@ impl SubconsciousEngine {
// ── Internal methods ─────────────────────────────────────────────────────
fn seed_tasks(&self) {
- match store::with_connection(&self.workspace_dir, |conn| store::seed_default_tasks(conn)) {
+ match store::with_connection(&self.workspace_dir, store::seed_default_tasks) {
Ok(count) => {
if count > 0 {
info!("[subconscious] seeded {count} default tasks");
diff --git a/src/openhuman/subconscious/global.rs b/src/openhuman/subconscious/global.rs
index 3199d8400..23763da2a 100644
--- a/src/openhuman/subconscious/global.rs
+++ b/src/openhuman/subconscious/global.rs
@@ -93,9 +93,8 @@ pub async fn bootstrap_after_login() -> Result<(), String> {
// SubconsciousEngine::new calls seed_default_tasks() inside the
// constructor, so by the time this returns the 3 system defaults are
// present in `/subconscious/subconscious.db`.
- get_or_init_engine().await.map_err(|e| {
+ get_or_init_engine().await.inspect_err(|_e| {
BOOTSTRAPPED.store(false, Ordering::SeqCst);
- e
})?;
tracing::info!(
workspace = %config.workspace_dir.display(),
diff --git a/src/openhuman/tools/ask_clarification.rs b/src/openhuman/tools/ask_clarification.rs
index faed21176..641600d6f 100644
--- a/src/openhuman/tools/ask_clarification.rs
+++ b/src/openhuman/tools/ask_clarification.rs
@@ -10,6 +10,12 @@ use serde_json::json;
/// event channel and waits for a response before continuing.
pub struct AskClarificationTool;
+impl Default for AskClarificationTool {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
impl AskClarificationTool {
pub fn new() -> Self {
Self
diff --git a/src/openhuman/tools/spawn_subagent.rs b/src/openhuman/tools/spawn_subagent.rs
index 231d73f12..d70840fa7 100644
--- a/src/openhuman/tools/spawn_subagent.rs
+++ b/src/openhuman/tools/spawn_subagent.rs
@@ -9,6 +9,12 @@ use serde_json::json;
/// Available only to the Orchestrator archetype.
pub struct SpawnSubagentTool;
+impl Default for SpawnSubagentTool {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
impl SpawnSubagentTool {
pub fn new() -> Self {
Self
@@ -63,7 +69,7 @@ impl Tool for SpawnSubagentTool {
let prompt = args.get("prompt").and_then(|v| v.as_str()).unwrap_or("");
- let context = args.get("context").and_then(|v| v.as_str()).unwrap_or("");
+ let _context = args.get("context").and_then(|v| v.as_str()).unwrap_or("");
if prompt.is_empty() {
return Ok(ToolResult::error("prompt is required"));
diff --git a/src/openhuman/tree_summarizer/bus.rs b/src/openhuman/tree_summarizer/bus.rs
index 3d32f4ee9..26aa80245 100644
--- a/src/openhuman/tree_summarizer/bus.rs
+++ b/src/openhuman/tree_summarizer/bus.rs
@@ -9,6 +9,12 @@ use async_trait::async_trait;
/// Subscribes to tree summarizer events and logs activity.
pub struct TreeSummarizerEventSubscriber;
+impl Default for TreeSummarizerEventSubscriber {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
impl TreeSummarizerEventSubscriber {
pub fn new() -> Self {
Self
diff --git a/src/openhuman/tree_summarizer/store.rs b/src/openhuman/tree_summarizer/store.rs
index 233e88e67..6d0515527 100644
--- a/src/openhuman/tree_summarizer/store.rs
+++ b/src/openhuman/tree_summarizer/store.rs
@@ -294,8 +294,8 @@ pub fn get_tree_status(config: &Config, namespace: &str) -> Result {
// Try to parse timestamp from path
if let Some(ts) = timestamp_from_hour_path(
&name,
- &month_entry.file_name().to_string_lossy().to_string(),
- &day_entry.file_name().to_string_lossy().to_string(),
+ month_entry.file_name().to_string_lossy().as_ref(),
+ day_entry.file_name().to_string_lossy().as_ref(),
&hname,
) {
match &oldest {
@@ -672,10 +672,8 @@ fn count_md_files(dir: &Path) -> Result {
continue; // skip buffer directories
}
count += count_md_files(&entry.path())?;
- } else if ft.is_file() {
- if entry.path().extension().map(|e| e == "md").unwrap_or(false) {
- count += 1;
- }
+ } else if ft.is_file() && entry.path().extension().map(|e| e == "md").unwrap_or(false) {
+ count += 1;
}
}
Ok(count)
diff --git a/src/openhuman/tree_summarizer/types.rs b/src/openhuman/tree_summarizer/types.rs
index 04eb46cff..c5c621f4d 100644
--- a/src/openhuman/tree_summarizer/types.rs
+++ b/src/openhuman/tree_summarizer/types.rs
@@ -126,7 +126,7 @@ pub struct QueryResult {
/// Rough token estimate: ~4 characters per token.
pub fn estimate_tokens(text: &str) -> u32 {
- (text.len() as u32 + 3) / 4
+ (text.len() as u32).div_ceil(4)
}
/// Derive the parent node ID from a node ID.
diff --git a/src/openhuman/voice/audio_capture.rs b/src/openhuman/voice/audio_capture.rs
index 52f5530e6..599dfe80a 100644
--- a/src/openhuman/voice/audio_capture.rs
+++ b/src/openhuman/voice/audio_capture.rs
@@ -304,7 +304,7 @@ fn record_on_thread(
};
// If the preferred config failed, retry with the device's default config.
- let (stream, source_sample_rate, source_channels) = match stream {
+ let (stream, source_sample_rate, _source_channels) = match stream {
Ok(s) => (s, source_sample_rate, source_channels),
Err(ref preferred_err) => {
warn!(
diff --git a/src/openhuman/voice/hotkey.rs b/src/openhuman/voice/hotkey.rs
index 0b5b3bb47..b9df0e7c7 100644
--- a/src/openhuman/voice/hotkey.rs
+++ b/src/openhuman/voice/hotkey.rs
@@ -21,19 +21,15 @@ const LOG_PREFIX: &str = "[voice_hotkey]";
Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema,
)]
#[serde(rename_all = "snake_case")]
+#[derive(Default)]
pub enum ActivationMode {
/// Single press toggles recording on/off.
Tap,
/// Hold to record, release to stop.
+ #[default]
Push,
}
-impl Default for ActivationMode {
- fn default() -> Self {
- Self::Push
- }
-}
-
/// Events emitted by the hotkey listener.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HotkeyEvent {
diff --git a/src/openhuman/webhooks/bus.rs b/src/openhuman/webhooks/bus.rs
index bdf5c3d62..43a3b6a1a 100644
--- a/src/openhuman/webhooks/bus.rs
+++ b/src/openhuman/webhooks/bus.rs
@@ -27,6 +27,12 @@ fn error_body(message: &str) -> String {
/// flow: lookup tunnel → dispatch to skill/echo → emit response via socket.
pub struct WebhookRequestSubscriber;
+impl Default for WebhookRequestSubscriber {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
impl WebhookRequestSubscriber {
pub fn new() -> Self {
Self