From 63b17ca55c3b8719aa5c2251a6f335512497a3d0 Mon Sep 17 00:00:00 2001
From: Steven Enamakel <31011319+senamakel@users.noreply.github.com>
Date: Thu, 9 Apr 2026 16:41:27 -0700
Subject: [PATCH] refactor: remove dead frontend modules and obsolete API
layers (#469)
* refactor: remove unused socket, agent tool registry, daemon health, and API service files
- Deleted the `useSocket` hook, `AgentToolRegistry`, `DaemonHealthService`, and various API service files including `actionableItemsApi`, `apiKeysApi`, `feedbackApi`, `inferenceApi`, `managedDmApi`, and `settingsApi`.
- This cleanup reduces code complexity and improves maintainability by removing obsolete components that are no longer in use.
* feat: add knip configuration and commands to package.json
- Introduced new scripts for running knip in development and production modes in both package.json files.
- Added a knip.json configuration file to specify entry points and project files for dependency analysis.
- Updated yarn.lock to include new dependencies related to knip, enhancing the project's dependency management capabilities.
This commit improves the project's tooling for managing dependencies and ensures better code quality through automated checks.
* refactor: remove unused components and clean up dependencies
- Deleted several unused components related to intelligence features, including ActionPanel, InputGroup, SectionCard, and ValidatedField, to streamline the codebase.
- Removed mock data and country data files that are no longer in use, enhancing maintainability.
- Cleaned up the package.json by removing the @heroicons/react dependency, which is no longer required.
- This commit improves the overall project structure and reduces complexity by eliminating obsolete code.
* refactor: remove IntelligenceApiService and redefine ConnectedTool interface
- Deleted the `IntelligenceApiService` class and its associated backend API methods to streamline the codebase.
- Introduced a local definition of the `ConnectedTool` interface in `useIntelligenceApiFallback.ts` for better encapsulation and clarity.
- This refactor enhances maintainability by eliminating unused code and consolidating relevant types within the appropriate context.
* style: format knip config
* chore(dependencies): update OpenHuman to version 0.52.0 in Cargo.lock
* feat: implement Daemon Health Service for polling and state management
- Introduced the `DaemonHealthService` class to poll the Rust core health snapshot and synchronize the frontend daemon store.
- Added methods for setting up a health listener, parsing health snapshots, and updating the daemon store based on health data.
- Implemented a timeout mechanism to handle disconnection scenarios, enhancing the reliability of the daemon's health monitoring.
- This addition improves the application's ability to maintain an accurate representation of the daemon's health status in real-time.
* chore(knip): update entry points in knip configuration
- Modified the `entry` field in `knip.json` to include `src/main.tsx` alongside existing test specifications.
- This change ensures that the main application file is included in dependency analysis, improving project structure and tooling.
* refactor: streamline code and enhance readability across multiple modules
- Consolidated multiple `replace` calls into single calls using arrays for improved efficiency in text processing.
- Simplified default implementations for several structs, removing redundant code.
- Updated query mapping in database interactions to enhance clarity and maintainability.
- Improved logging and error handling by refining how state and error messages are processed.
- Enhanced the readability of various functions by restructuring conditional checks and simplifying logic.
These changes collectively improve code maintainability and performance across the application.
* Merge remote-tracking branch 'origin/fix/cleanup' into fix/cleanup
* fix: update error handling in bootstrap_after_login function
- Changed the error parameter in the inspect_err closure to an underscore to indicate it is unused.
- This minor adjustment improves code clarity and adheres to Rust conventions for unused variables.
---
app/knip.json | 27 ++
app/package.json | 4 +-
app/src/__tests__/setup.ts | 28 --
app/src/assets/icons/ExchangeIcon.tsx | 9 -
app/src/assets/icons/GmailIcon.tsx | 10 -
app/src/assets/icons/WalletIcon.tsx | 9 -
app/src/components/DesignSystemShowcase.tsx | 394 ------------------
.../components/GmailConnectionIndicator.tsx | 34 --
app/src/components/MiniSidebar.tsx | 242 -----------
app/src/components/PrivacyFeatureCard.tsx | 19 -
.../TelegramConnectionIndicator.tsx | 45 --
app/src/components/TypewriterGreeting.tsx | 78 ----
app/src/components/WalletInfoSection.tsx | 236 -----------
.../daemon/DaemonHealthIndicator.tsx | 108 -----
.../components/daemon/DaemonHealthPanel.tsx | 322 --------------
app/src/components/intelligence/index.ts | 19 -
app/src/components/intelligence/mockData.ts | 311 --------------
.../panels/components/ActionPanel.tsx | 98 -----
.../settings/panels/components/InputGroup.tsx | 91 ----
.../panels/components/SectionCard.tsx | 90 ----
.../panels/components/ValidatedField.tsx | 187 ---------
app/src/data/countries.ts | 24 --
app/src/hooks/useIntelligenceApiFallback.ts | 9 +-
app/src/hooks/useLocalModelStatus.ts | 44 --
app/src/hooks/useSocket.ts | 75 ----
app/src/hooks/useToolsUpdates.ts | 49 ---
app/src/lib/skills/tool-bridge.ts | 58 ---
app/src/lib/tools/file-watcher.ts | 16 -
app/src/lib/webhooks/urls.ts | 12 -
app/src/overlay/parentCoreRpc.ts | 118 ------
.../pages/onboarding/steps/AnalyticsStep.tsx | 128 ------
.../pages/onboarding/steps/ConnectStep.tsx | 176 --------
.../pages/onboarding/steps/InviteCodeStep.tsx | 99 -----
.../pages/onboarding/steps/MnemonicStep.tsx | 337 ---------------
app/src/pages/onboarding/steps/ToolsStep.tsx | 101 -----
.../__tests__/agentToolRegistry.test.ts | 363 ----------------
app/src/services/agentToolRegistry.ts | 297 -------------
.../api/__tests__/managedDmApi.test.ts | 40 --
app/src/services/api/actionableItemsApi.ts | 115 -----
app/src/services/api/apiKeysApi.ts | 53 ---
app/src/services/api/authApi.ts | 115 -----
app/src/services/api/feedbackApi.ts | 68 ---
app/src/services/api/inferenceApi.ts | 117 ------
app/src/services/api/managedDmApi.ts | 100 -----
app/src/services/api/settingsApi.ts | 38 --
app/src/services/daemonHealthService.ts | 63 +--
app/src/services/intelligenceApi.ts | 186 ---------
app/src/types/agent.ts | 68 ---
app/src/types/intelligence-chat-api.ts | 387 -----------------
app/src/types/onboarding.ts | 42 --
app/src/utils/deeplink.ts | 32 --
app/src/utils/intelligenceTransforms.ts | 210 ----------
app/test/e2e/globals.d.ts | 8 -
package.json | 2 +
src/core/cli.rs | 9 +-
src/core/logging.rs | 4 +-
src/core/skills_cli.rs | 2 +-
src/openhuman/agent/harness/self_healing.rs | 2 +-
src/openhuman/agent/harness/types.rs | 8 +-
src/openhuman/autocomplete/core/engine.rs | 5 +-
src/openhuman/autocomplete/core/text.rs | 3 +-
src/openhuman/channels/bus.rs | 6 +
.../channels/providers/presentation.rs | 4 +-
src/openhuman/config/schema/dictation.rs | 8 +-
src/openhuman/config/schema/load.rs | 2 +-
src/openhuman/config/schema/orchestrator.rs | 15 +-
src/openhuman/config/schema/tools.rs | 15 +-
src/openhuman/config/schema/voice_server.rs | 8 +-
src/openhuman/local_ai/parse.rs | 13 +-
.../local_ai/service/whisper_engine.rs | 2 +-
src/openhuman/memory/store/unified/events.rs | 4 +-
src/openhuman/memory/store/unified/profile.rs | 4 +-
.../memory/store/unified/segments.rs | 8 +-
.../skills/qjs_skill_instance/js_handlers.rs | 2 +-
.../skills/quickjs_libs/qjs_ops/ops_net.rs | 2 +-
src/openhuman/skills/types.rs | 2 +-
src/openhuman/socket/schemas.rs | 2 +-
src/openhuman/subconscious/engine.rs | 25 +-
src/openhuman/subconscious/global.rs | 3 +-
src/openhuman/tools/ask_clarification.rs | 6 +
src/openhuman/tools/spawn_subagent.rs | 8 +-
src/openhuman/tree_summarizer/bus.rs | 6 +
src/openhuman/tree_summarizer/store.rs | 10 +-
src/openhuman/tree_summarizer/types.rs | 2 +-
src/openhuman/voice/audio_capture.rs | 2 +-
src/openhuman/voice/hotkey.rs | 8 +-
src/openhuman/webhooks/bus.rs | 6 +
87 files changed, 149 insertions(+), 5968 deletions(-)
create mode 100644 app/knip.json
delete mode 100644 app/src/__tests__/setup.ts
delete mode 100644 app/src/assets/icons/ExchangeIcon.tsx
delete mode 100644 app/src/assets/icons/GmailIcon.tsx
delete mode 100644 app/src/assets/icons/WalletIcon.tsx
delete mode 100644 app/src/components/DesignSystemShowcase.tsx
delete mode 100644 app/src/components/GmailConnectionIndicator.tsx
delete mode 100644 app/src/components/MiniSidebar.tsx
delete mode 100644 app/src/components/PrivacyFeatureCard.tsx
delete mode 100644 app/src/components/TelegramConnectionIndicator.tsx
delete mode 100644 app/src/components/TypewriterGreeting.tsx
delete mode 100644 app/src/components/WalletInfoSection.tsx
delete mode 100644 app/src/components/daemon/DaemonHealthIndicator.tsx
delete mode 100644 app/src/components/daemon/DaemonHealthPanel.tsx
delete mode 100644 app/src/components/intelligence/index.ts
delete mode 100644 app/src/components/intelligence/mockData.ts
delete mode 100644 app/src/components/settings/panels/components/ActionPanel.tsx
delete mode 100644 app/src/components/settings/panels/components/InputGroup.tsx
delete mode 100644 app/src/components/settings/panels/components/SectionCard.tsx
delete mode 100644 app/src/components/settings/panels/components/ValidatedField.tsx
delete mode 100644 app/src/data/countries.ts
delete mode 100644 app/src/hooks/useLocalModelStatus.ts
delete mode 100644 app/src/hooks/useSocket.ts
delete mode 100644 app/src/hooks/useToolsUpdates.ts
delete mode 100644 app/src/lib/skills/tool-bridge.ts
delete mode 100644 app/src/lib/tools/file-watcher.ts
delete mode 100644 app/src/lib/webhooks/urls.ts
delete mode 100644 app/src/overlay/parentCoreRpc.ts
delete mode 100644 app/src/pages/onboarding/steps/AnalyticsStep.tsx
delete mode 100644 app/src/pages/onboarding/steps/ConnectStep.tsx
delete mode 100644 app/src/pages/onboarding/steps/InviteCodeStep.tsx
delete mode 100644 app/src/pages/onboarding/steps/MnemonicStep.tsx
delete mode 100644 app/src/pages/onboarding/steps/ToolsStep.tsx
delete mode 100644 app/src/services/__tests__/agentToolRegistry.test.ts
delete mode 100644 app/src/services/agentToolRegistry.ts
delete mode 100644 app/src/services/api/__tests__/managedDmApi.test.ts
delete mode 100644 app/src/services/api/actionableItemsApi.ts
delete mode 100644 app/src/services/api/apiKeysApi.ts
delete mode 100644 app/src/services/api/feedbackApi.ts
delete mode 100644 app/src/services/api/inferenceApi.ts
delete mode 100644 app/src/services/api/managedDmApi.ts
delete mode 100644 app/src/services/api/settingsApi.ts
delete mode 100644 app/src/services/intelligenceApi.ts
delete mode 100644 app/src/types/agent.ts
delete mode 100644 app/src/types/intelligence-chat-api.ts
delete mode 100644 app/src/types/onboarding.ts
delete mode 100644 app/src/utils/deeplink.ts
delete mode 100644 app/src/utils/intelligenceTransforms.ts
delete mode 100644 app/test/e2e/globals.d.ts
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