Feat/refactor UI code (#52)
* Enhance autocomplete functionality and settings panel
- Added a new AutocompletePanel component for managing inline autocomplete settings, including options for enabling/disabling, debounce timing, and style configurations.
- Integrated autocomplete status tracking and logging within the panel to provide real-time feedback on the autocomplete engine's state.
- Updated settings navigation to include the new autocomplete settings route, improving user accessibility to autocomplete features.
- Introduced new Tauri commands for managing autocomplete operations, including start, stop, and current status retrieval, enhancing interaction with the autocomplete engine.
- Refactored existing code to streamline autocomplete-related functionalities and improve overall maintainability.
* Update TypeScript configuration and add new assets
- Modified `tsconfig.json` to adjust path aliases and include directories for improved module resolution.
- Added new SVG and image assets to the public directory, enhancing the application's visual resources.
- Introduced multiple Lottie animation JSON files for dynamic UI elements, expanding the application's animation capabilities.
* Update project structure and paths for Tauri integration
- Adjusted paths in the pull request template and various workflow files to reflect the new project structure, moving Tauri-related files under the `app` directory.
- Updated commands in the build and release workflows to ensure compatibility with the new file locations.
- Enhanced the test workflow to create the necessary `.env` file in the correct directory for end-to-end testing.
- Added new markdown files for agent prompts and configuration, establishing a foundation for OpenHuman's AI capabilities.
* Refactor project paths and update configurations for Tauri integration
- Adjusted script paths in package.json to reflect the new project structure, ensuring compatibility with the updated directory layout.
- Modified tsconfig.json to correct path aliases and include directories for improved module resolution.
- Introduced a new utility for resolving development paths, enhancing the ability to locate the `rust-core/ai` directory across different project structures.
- Updated Cargo.toml and tauri.conf.json to align with the new directory structure, ensuring proper resource and dependency management.
- Added a new dev_paths module to streamline path resolution logic, improving maintainability and clarity in the codebase.
* Refactor project structure and update configurations for Tauri integration
- Adjusted paths in .gitignore, Cargo.toml, and various scripts to reflect the new directory layout, moving Tauri-related files under the `app` directory.
- Introduced a new package.json file to manage workspace scripts and dependencies effectively.
- Updated end-to-end build and run scripts to ensure compatibility with the new project structure.
- Enhanced documentation in CONTRIBUTING.md to guide contributors on the updated project organization and Tauri command usage.
* Refactor Tauri command invocations to use dedicated utility functions
- Replaced direct `invoke` calls with utility functions from `tauriCommands` for improved readability and maintainability across multiple components.
- Updated `SkillsGrid`, `Skills`, `SkillProvider`, and `SkillManager` to utilize the new command structure, enhancing consistency in Tauri command handling.
- Introduced a new `coreRpcClient` for managing core RPC relay requests, streamlining error handling and request processing.
- Added a new `core_rpc_relay` command in the Tauri backend to facilitate communication with the core service, ensuring better service management and error reporting.
* Refactor Tauri command invocations in intelligence stats and memory manager
- Replaced direct `invoke` calls with utility functions from `tauriCommands` in `useIntelligenceStats` and `MemoryManager` for improved readability and maintainability.
- Updated the `aiListMemoryFiles`, `aiReadMemoryFile`, and `aiWriteMemoryFile` functions to utilize the new command structure, enhancing consistency in Tauri command handling.
- Introduced new command handling in the Rust backend for `ai.list_memory_files`, `ai.read_memory_file`, and `ai.write_memory_file`, streamlining communication with the core service.
* Refactor SkillsGrid and remove SelfEvolveModal component
- Removed the SelfEvolveModal component to streamline the SkillsGrid functionality.
- Updated the SkillsGrid to utilize the runtimeDiscoverSkills function for loading skills, replacing the previous invoke method.
- Simplified the skill entry normalization process by integrating it directly into the skills loading logic.
- Enhanced error handling during skill loading to improve robustness and user feedback.
* Refactor Tauri command invocations to use coreRpcClient
- Replaced direct `invoke` calls with `callCoreRpc` in various components, including `useIntelligenceStats`, `MemoryManager`, `SessionManager`, and `transcript` functions, enhancing code readability and maintainability.
- Updated the Rust backend to handle new command structures for memory and session management, streamlining communication with the core service.
- Improved consistency in handling Tauri commands across the application.
* Refactor Tauri command invocations to utilize coreRpcClient
- Replaced direct `invoke` calls with `callCoreRpc` in `tauriCommands.ts` and `tauriSocket.ts`, enhancing code readability and maintainability.
- Updated the Rust backend to support new command structures for authentication and session management, streamlining communication with the core service.
- Removed legacy socket reporting methods in `tauriSocket.ts`, reflecting a shift towards event-driven socket state management.
- Improved consistency in handling Tauri commands across the application, aligning with recent refactoring efforts.
* Remove pre-commit hook and update TODO list with completed tasks and new objectives. This includes separating the binary from the Tauri codebase, integrating accessibility service installation, and removing Android/iOS support from the codebase.
* Add core server functionality with dispatch and RPC handling
- Introduced new modules for core server operations, including dispatching RPC requests and handling various AI and memory-related commands.
- Implemented a robust structure for managing authentication, configuration, and session states through the `openhuman` namespace.
- Added helper functions for loading configurations, managing memory files, and processing authentication profiles.
- Established a new routing system using Axum for handling HTTP requests, including health checks and RPC endpoints.
- Enhanced error handling and logging throughout the new functionalities to improve maintainability and user feedback.
* Implement core server CLI and modular structure
- Introduced a new CLI module for the core server, enabling various commands for server management, health checks, and configuration settings.
- Established a modular structure for core server functionalities, including dispatching RPC requests and managing settings for models, memory, and runtime.
- Added comprehensive tests to validate the functionality of accessibility and autocomplete commands, ensuring robust error handling and schema compliance.
- Enhanced the overall organization of the core server codebase, improving maintainability and readability.
* Implement AI RPC dispatch functionality
- Introduced a new `ai_rpc` module for handling various AI-related commands, including memory file operations and session management.
- Enhanced the `try_dispatch` function to support commands such as listing, reading, writing memory files, and managing session states.
- Updated the core server dispatch module to integrate the new AI RPC functionality, improving modularity and maintainability.
- Refactored existing code to ensure consistent parameter parsing and error handling across AI commands.
* Update TODO list and refactor Rust core server files
- Added new tasks to the TODO list for documentation updates and feature flag cleanup.
- Introduced `Arc` import in `cli.rs` for improved concurrency handling.
- Cleaned up imports in `helpers.rs` and added conditional compilation for `tauri-host`.
- Removed unused `value_only` function in `types.rs` and added `#[allow(dead_code)]` to `SocketConnectParams` and `SocketEmitParams`.
- Enhanced `try_dispatch` function in `dispatch/mod.rs` for non-tauri-host scenarios.
- Updated `try_dispatch` in `openhuman/platform.rs` to correctly handle session parameters.
- Modified `screen_intelligence` configuration in tests to include new properties for better session management.
* Refactor import statements and enhance code readability
- Cleaned up import statements across multiple files for improved organization and consistency.
- Reformatted code in `cli.rs`, `helpers.rs`, and various dispatch modules to enhance readability.
- Ensured consistent parameter handling in `try_dispatch` functions, improving maintainability.
- Removed unnecessary whitespace and adjusted formatting for better code clarity.
* Refactor project structure and enhance AI memory management
- Consolidated the `openhuman-core` package into a single `Cargo.toml` file, removing the previous `rust-core` directory.
- Introduced new modules for AI memory management, including filesystem-based storage and encryption functionalities.
- Added Tauri commands for initializing memory and session management, enhancing user interaction with memory files.
- Implemented JSON-based storage for memory chunks and session transcripts, improving data accessibility and organization.
- Updated dependencies and features in `Cargo.toml` to support new functionalities and ensure compatibility.
* Update build and release workflows to reflect project structure changes
- Adjusted paths in GitHub Actions workflows to accommodate the consolidation of the `openhuman-core` package into a single `Cargo.toml`.
- Updated import statements in various files to point to the new locations of markdown resources.
- Modified the Tauri configuration to reflect the new resource paths, ensuring proper access to AI prompts.
- Enhanced the staging script to build the standalone binary from the updated project structure.
* Refactor AI directory resolution and update documentation
- Updated the logic for resolving AI directory paths to reflect the new project structure, replacing references to `rust-core/ai` with `src/ai/prompts`.
- Enhanced the `find_ai_directory` function across multiple modules to utilize the new path resolution methods.
- Updated documentation comments to clarify the new directory structure and fallback mechanisms for loading AI prompts.
* Rename `openhuman-core` to `openhuman` across the project
- Updated package names in `Cargo.toml` and `Cargo.lock` to reflect the new naming convention.
- Adjusted references in GitHub Actions workflows and scripts to use the new package name.
- Modified CLI command names and error messages to align with the updated naming.
- Ensured consistency in executable file names and paths throughout the codebase.
* Refactor project commands and update package scripts
- Updated package.json to change workspace references from `openhuman` to `app` for build, compile, dev, format, lint, and test scripts.
- Removed outdated memory and chat command files to streamline the codebase and improve maintainability.
- Adjusted the `lib.rs` file to reflect changes in memory command handling, transitioning to use `callCoreRpc` for Neocortex memory operations.
- Cleaned up the commands module by removing unused imports and consolidating functionality.
* Add Tauri host support and new daemon configuration
- Introduced new modules for Tauri host functionality, including `desktop` and `daemon_host`.
- Added static variables and initialization functions for managing the desktop app handle and resource directory.
- Updated import paths for `HeartbeatEngine` to improve clarity and organization.
- Implemented configuration loading and saving for daemon UI preferences, enhancing user experience.
* Update package names in project configuration
- Changed workspace references in package.json from `app` to `openhuman-app` for consistency.
- Updated the name field in the app's package.json to reflect the new naming convention.
* Remove Tauri host feature flags from core server modules
- Eliminated conditional compilation for Tauri host in `lib.rs`, `helpers.rs`, and `dispatch` modules.
- Streamlined socket management functions and dispatch logic by removing unused code related to Tauri host.
- Improved code clarity and maintainability by consolidating socket-related functionality.
* Enhance Tauri host feature integration and update dependencies
- Added `tauri-host` as a default feature in `Cargo.toml` to streamline feature management.
- Removed explicit feature flag from `openhuman` dependency in `app/src-tauri/Cargo.toml` for cleaner configuration.
- Updated Tauri command attributes in various modules to conditionally compile with the `tauri-host` feature, improving modularity.
- Expanded TODO list to include migration support from OpenClaw, indicating future development focus.
* Refactor authentication and credential management in OpenHuman
- Introduced new modules for handling authentication profiles and tokens, including `anthropic_token`, `openai_oauth`, and `profiles`.
- Removed unused Tauri host-related code from core server modules, enhancing clarity and maintainability.
- Updated `Cargo.toml` and `Cargo.lock` to reflect the removal of the `rquickjs` dependency and other package adjustments.
- Streamlined memory client initialization in dispatch logic to utilize the new `local_memory` module.
- Enhanced code organization by consolidating credential management functionalities and improving the overall structure of the OpenHuman module.
* Enhance Rust core RPC structure and streamline helper functions
- Introduced a dedicated `rpc.rs` file for each domain in the Rust core to manage JSON-RPC and CLI behavior, improving code organization and clarity.
- Refactored helper functions to utilize `rpc_invocation_from_outcome` for consistent handling of RPC responses across various modules.
- Removed unused authentication and credential management functions from `helpers.rs`, consolidating relevant logic into the new RPC structure.
- Updated dispatch logic in multiple modules to leverage the new RPC functions, enhancing maintainability and reducing code duplication.
* Enhance Rust core RPC structure and streamline helper functions
- Introduced a dedicated `rpc.rs` file for each domain in the Rust core to manage JSON-RPC and CLI behavior, improving code organization and clarity.
- Refactored helper functions to utilize `rpc_invocation_from_outcome` for consistent handling of RPC responses across various modules.
- Removed unused authentication and credential management functions from `helpers.rs`, consolidating relevant logic into the new RPC structure.
- Updated dispatch logic in multiple modules to leverage the new RPC functions, enhancing maintainability and reducing code duplication.
* Refactor OpenHuman configuration loading and enhance onboarding RPC
- Replaced the `load_openhuman_config` function with a new `load_config_with_timeout` method to improve timeout handling during configuration loading.
- Consolidated configuration loading logic across various modules, reducing redundancy and enhancing maintainability.
- Introduced new RPC functions for applying settings related to models, memory, screen intelligence, gateway, tunnel, runtime, and browser, streamlining the update process.
- Added onboarding helpers in a new `onboard` module, including a JSON-RPC controller for model refresh operations, improving onboarding flow management.
* Refactor CLI and configuration management in OpenHuman
- Consolidated CLI-related functionality by introducing new modules for settings and credentials management, enhancing code organization.
- Removed redundant functions and streamlined the configuration loading process, improving maintainability.
- Added new CLI helpers for screenshot tools and workspace initialization, facilitating better user experience and onboarding.
- Enhanced JSON-RPC responses to be more compatible with CLI requirements, ensuring consistent output across various commands.
* Remove gateway settings and related functionality from OpenHuman
- Eliminated the GatewaySettingsUpdate interface and associated functions from the codebase, streamlining configuration management.
- Removed references to gateway settings in the CLI and configuration modules, enhancing clarity and maintainability.
- Deleted the gateway module and its related components, including rate limiting and client handling, to simplify the architecture.
- Updated Cargo.toml and Cargo.lock to reflect the removal of dependencies related to gateway functionality.
* Update documentation and improve clarity in OpenHuman
- Revised comments in the `mod.rs`, `traits.rs`, and `pairing.rs` files to enhance clarity and accuracy.
- Updated descriptions related to security policy, long-running processes, and pairing functionality for better understanding.
* Refactor loading prop in TauriCommandsPanel for cleaner code
- Simplified the loading prop assignment in the TauriCommandsPanel component by removing unnecessary line breaks, enhancing readability and maintainability.
* Add OpenSSL dependency and implement OAuth authentication features
- Added OpenSSL as a dependency in `Cargo.toml` to support cryptographic operations.
- Introduced new OAuth-related structures and parameters in `types.rs` for handling authentication flows.
- Implemented OAuth connection and integration token fetching in `auth_socket.rs`, enhancing the authentication capabilities of the OpenHuman module.
- Created new modules for managing authentication profiles and responses, improving the organization of authentication-related code.
- Removed deprecated `anthropic_token` and `openai_oauth` modules to streamline credential management.
- Updated `Cargo.lock` to reflect the addition of the OpenSSL dependency.
* Refactor OpenHuman module and update dependencies
- Added OpenHuman integration entry in the registry for improved backend inference handling.
- Updated various files to enhance code clarity and organization, including adjustments to OAuth client methods and integration tests.
- Refactored import statements and removed unnecessary line breaks for better readability.
- Updated `Cargo.lock` to reflect changes in dependencies and ensure consistency across the project.
* Implement desktop host features and refactor runtime handling
- Introduced new modules for memory management, socket handling, and command definitions to support desktop host functionality.
- Refactored QuickJS runtime initialization to log errors when the engine is not linked, improving clarity on runtime status.
- Added placeholder commands for chat and model interactions, indicating unavailability in the desktop build while maintaining structure for future integration.
- Enhanced organization of the codebase by creating dedicated files for runtime and utility functions, streamlining the development process.
- Updated documentation to reflect new modules and their purposes, ensuring better understanding for future contributors.
* Add CLI banner and print function to enhance user experience
- Introduced a new CLI banner with branding and GitHub link for user engagement.
- Implemented a `print_cli_banner` function to display the banner when running the CLI, improving visibility and user interaction.
- Updated the CLI entry point to call the new banner function, ensuring it appears at startup.
* Add API integration and update dependencies
- Introduced new API modules for handling HTTP requests and WebSocket connections to the TinyHumans backend.
- Added `ureq` dependency for simplified HTTP client functionality, updating `Cargo.toml` and `Cargo.lock` accordingly.
- Implemented configuration and JWT handling in the new `api` module, enhancing session management and API interactions.
- Refactored existing code to utilize the new API helpers, improving code organization and maintainability.
- Updated documentation to reflect new API functionalities and usage guidelines.
* Refactor settings fetching and update dependencies
- Removed the `ureq` dependency and associated functions for fetching settings, streamlining the codebase.
- Updated the `fetch_settings` method to utilize `reqwest` for HTTP requests, enhancing consistency and reliability in API interactions.
- Adjusted the `Cargo.toml` to reflect the removal of `ureq`, ensuring dependencies are up to date.
* Update `ureq` dependency to version 3.3.0 in `Cargo.lock`
- Removed the specific version constraint for `ureq`, allowing for more flexibility in dependency resolution.
- Updated the `Cargo.lock` to reflect the new version of `ureq`, ensuring compatibility with recent changes in the codebase.
* Enhance JSON-RPC logging and CLI initialization
- Introduced a new `rpc_log` module for structured logging of JSON-RPC requests and responses, including redaction of sensitive parameters.
- Updated `execute_core_cli` to initialize logging with a default level and timestamp format.
- Enhanced logging in `rpc_handler` and `dispatch` functions to provide detailed insights into method calls and their execution times.
- Improved error handling logging to capture method failures with context, aiding in debugging and monitoring.
* Refactor HTTP server setup and add integration tests
- Introduced a new `build_core_http_router` function to encapsulate the HTTP routing logic, improving code organization and readability.
- Updated the `run_server` function to utilize the new router function, streamlining server initialization.
- Added comprehensive integration tests for the JSON-RPC API, ensuring robust functionality and error handling in real-world scenarios.
* Enhance OpenHuman backend integration and refactor provider handling
- Added support for the OpenHuman backend in the TauriCommandsPanel, including default configurations and validation for API keys.
- Introduced a new REPL command in the CLI for interactive RPC communication, allowing for dynamic mode switching and message handling.
- Refactored provider creation logic to streamline the integration of the OpenHuman backend, removing deprecated provider overrides and ensuring consistent usage across the codebase.
- Updated various components to improve error handling and user feedback related to provider selection and API interactions.
* Refactor provider handling and update default model settings
- Removed provider override states from the AgentChatPanel and TauriCommandsPanel components, simplifying state management.
- Updated local storage handling to exclude provider overrides, ensuring cleaner data storage.
- Changed default model settings across various components and backend configurations to use "neocortex-mk1" as the new default model.
- Enhanced error handling and validation logic in the TauriCommandsPanel, focusing on model and temperature settings.
- Streamlined integration tests and removed deprecated provider validation logic to improve code clarity and maintainability.
* Refactor code for improved readability and consistency
- Adjusted formatting in several files to enhance code clarity, including consistent parameter passing and alignment.
- Simplified match statement syntax in the `run_models` function for better readability.
- Streamlined assertions in tests to maintain consistency in error handling checks.
- Updated default model name handling in the `AgentBuilder` for cleaner initialization.
* Refactor API URL handling and enhance error reporting
- Updated the `effective_api_url` function to improve clarity in resolving the API base URL, incorporating environment variable checks.
- Enhanced diagnostics in the configuration check to provide clearer messages regarding the API URL status.
- Introduced new error formatting functions to improve the clarity of error messages related to API transport issues.
- Refactored error handling in the OpenAiCompatibleProvider to utilize the new error formatting, ensuring consistent and informative error reporting.
* Refactor API client initialization for consistency
- Updated the instantiation of `BackendOAuthClient` to consistently pass the API URL by reference across multiple functions.
- Simplified the match statement in the `run_models_refresh` function for improved readability.
* Enhance REPL command handling and add fallback mechanisms
- Improved error handling in the REPL command processing, providing clearer feedback for command execution failures.
- Introduced a fallback mechanism for the `agent_chat` RPC call, allowing for graceful degradation to a simpler chat method or a direct backend curl transport if the primary call fails.
- Added a new `backend_chat_via_curl` function to handle chat requests using curl as a last resort, ensuring continued functionality in case of RPC issues.
- Updated the `agent_chat_simple` function to support model overrides and temperature settings, enhancing flexibility in chat interactions.
* Update default Ollama model settings for consistency
- Changed the default Ollama model and vision model to "gemma3:4b-it-qat" for improved alignment across configurations.
- Ensured consistent model naming to enhance clarity in model usage within the local AI module.
* Implement login token consumption and enhance error handling
- Added functionality to consume login tokens via a new API endpoint, returning a JWT for authenticated sessions.
- Improved error handling in the Conversations component, introducing a fallback mechanism for chat interactions when the primary method is unavailable.
- Updated UserProvider to restore session tokens automatically, enhancing user experience during authentication.
- Refactored thread API to support the new login token consumption logic, ensuring seamless integration with the backend.
* Update HTTP client configuration to use Rustls TLS
- Replaced the HTTP/1.1 only setting with Rustls TLS in the OpenAiCompatibleProvider's client builder for enhanced security.
- Ensured consistent application of the new TLS setting across multiple client instances.
* Add Local AI command support and enhance error handling
- Introduced a new `LocalAi` command in the CLI for managing local AI runtime operations, including status checks, asset downloads, and prompt handling.
- Added detailed argument structures for various local AI functionalities, improving command usability.
- Enhanced error reporting in the `LocalAiService` by including response details in error messages for better debugging and user feedback.
- Refactored existing error handling to provide clearer context on failures during API interactions.
* Add local AI module with Ollama integration and model management
- Introduced a new local AI module that includes functionality for automatic installation of the Ollama runtime across different operating systems (Windows, macOS, Linux).
- Implemented model ID resolution and management, providing default settings for various AI models and ensuring compatibility with user configurations.
- Added HTTP API structures and request handling for Ollama, enabling interaction with the local AI service for generating responses and managing assets.
- Developed utility functions for parsing model outputs and managing workspace paths, enhancing the overall structure and usability of the local AI service.
- Established a comprehensive service layer for managing local AI operations, including status tracking and error handling for improved user experience.
* Refactor local AI service structure and enhance asset management
- Simplified the local AI module by reorganizing the service structure, introducing new modules for model IDs, paths, and asset management.
- Added comprehensive asset status tracking for various AI models, including chat, vision, embedding, STT, and TTS, with improved error handling.
- Implemented methods for downloading models and assets, ensuring better management of local AI resources.
- Updated visibility of service methods to enhance encapsulation and maintainability within the local AI service.
* Enhance local AI module with new download progress tracking and unit tests
- Added new structures for tracking download progress of various AI models, including detailed status and metrics.
- Implemented unit tests for model ID resolution, parsing suggestions, and asset path resolution to ensure robust functionality.
- Refactored service methods to improve encapsulation and maintainability, enhancing the overall structure of the local AI service.
- Updated existing tests to cover new functionalities and ensure consistent behavior across the module.
* Implement new local AI download functionalities and refactor model management
- Added support for downloading all local AI assets and tracking download progress, enhancing user experience and resource management.
- Introduced new RPC methods for fetching download progress and managing asset states, improving the overall functionality of the local AI module.
- Refactored existing model management code to utilize the new model catalog, ensuring better organization and maintainability.
- Updated relevant tests to cover new functionalities and ensure consistent behavior across the local AI service.
* Add new interfaces and functions for local AI download progress tracking
- Introduced `LocalAiDownloadProgressItem` and `LocalAiDownloadsProgress` interfaces to structure download progress data for various AI models.
- Implemented `openhumanLocalAiDownloadAllAssets` and `openhumanLocalAiDownloadsProgress` functions to facilitate downloading all assets and tracking their progress.
- Enhanced error handling for Tauri environment checks in new functions, ensuring robust operation within the local AI module.
* Refactor agent loop structure and introduce modular components
- Deleted the `loop_.rs` file and reorganized the agent loop into multiple modules for better maintainability and clarity.
- Introduced new files for handling credentials, history management, tool instructions, memory context, and parsing logic.
- Implemented functions for scrubbing sensitive credentials, managing conversation history, and building tool instructions.
- Enhanced the overall structure of the agent loop to facilitate easier testing and future development.
* Refactor authentication structure and migrate to credentials module
- Moved authentication-related functionality from `auth_profiles` to a new `credentials` module for better organization and clarity.
- Updated references in the API and core server to reflect the new module structure.
- Introduced new data structures and methods for managing authentication profiles, including session support and response handling.
- Removed the obsolete `auth_profiles` module to streamline the codebase and enhance maintainability.
* Add screen intelligence module with capture and context management
- Introduced new modules for screen capture and context management, specifically targeting macOS.
- Implemented functionality to capture screen images and retrieve foreground application context.
- Added data structures for managing application context and window bounds.
- Established limits for screenshot sizes and context character counts to ensure efficient resource management.
- Enhanced helper functions for input action validation and vision summary processing.
- Set up a modular structure for better maintainability and future enhancements.
* Refactor screen intelligence module and remove obsolete components
- Deleted unused files related to screen intelligence, including context and permissions management, to streamline the codebase.
- Refactored the capture functionality to improve organization and maintainability.
- Updated function signatures for better clarity and consistency.
- Enhanced the overall structure of the screen intelligence module for future development and testing.
* Enhance autocomplete CLI functionality and refactor related code
- Added new options for the autocomplete command in the CLI, allowing users to run the autocomplete loop in the current process or spawn a detached process.
- Introduced `AutocompleteStartCliOptions` struct to encapsulate the new command-line arguments.
- Refactored the `autocomplete_start_cli` function to handle the new options and improve process management for the autocomplete service.
- Updated documentation in `CLAUDE.md` to clarify the separation of concerns between routing and controller logic in the codebase.
* Enhance autocomplete error handling and improve focused text context retrieval
- Added a new function to identify "no text candidate" errors, improving error management in the autocomplete engine.
- Refactored the `focused_text_context` and `focused_text_context_verbose` functions to enhance clarity and reliability in retrieving application context.
- Updated the return format of the `focused_text_context_verbose` function to use a separator for better data parsing.
- Added a new TODO item for allowing users to select LLM model versions based on their CPU capabilities.
* Remove Docker, Native, and WASM runtime implementations along with related traits and tests
- Deleted the DockerRuntime, NativeRuntime, and WasmRuntime implementations to streamline the codebase.
- Removed associated traits and factory functions for runtime creation.
- Eliminated all related tests to ensure a clean removal of unused components.
- This refactor aims to simplify the runtime management and prepare for future enhancements.
* Add quickjs-runtime feature and introduce runtime module
- Added a new feature flag for `quickjs-runtime` in `Cargo.toml` to enable its usage.
- Created a new `runtime.rs` module to implement `NativeRuntime` and `DockerRuntime` with associated traits for runtime management.
- Updated the `skills` module to reference the correct path for `SkillConfig`.
- Removed the obsolete `skillforge` module from the `openhuman` namespace to streamline the codebase.
- Enhanced the `skills` module with new structures and functions for managing skills, including initialization and loading logic.
* Refactor autocomplete configuration to remove legacy disabled apps
- Updated the default configuration for `AutocompleteConfig` to remove the legacy disabled apps ('terminal' and 'code'), allowing for broader usage of Codex/CLI.
- Introduced a migration function to handle legacy disabled apps during configuration loading, ensuring custom user preferences remain intact.
- This change enhances the flexibility of the autocomplete feature by preventing unnecessary restrictions on application usage.
* Update rquickjs dependencies in Cargo.lock
- Updated the rquickjs and rquickjs-core dependencies to versions 0.11.0 and 0.9.0 respectively, ensuring compatibility with the latest features and fixes.
- Added new entries for rquickjs-sys and its corresponding version 0.9.0 to the dependency list, enhancing the project's runtime capabilities.
- This update improves the overall stability and performance of the application by leveraging the latest improvements in the rquickjs ecosystem.
* Add terminal application detection to autocomplete logic
- Introduced a new function `is_terminal_app` to identify terminal applications based on their names, enhancing the autocomplete feature's context awareness.
- Updated the `focused_text_context_verbose` function to allow terminal applications to bypass text role checks when the input value is not empty, improving user experience in terminal environments.
- This change aims to provide better support for terminal-based applications in the autocomplete system.
* Add terminal input context extraction and noise line detection
- Introduced functions to identify terminal-like buffers and filter out noise lines in terminal input, enhancing the autocomplete engine's context awareness.
- Updated the `focused_text_context` logic to utilize the new terminal context extraction, improving the handling of text in terminal applications.
- Enhanced the `focused_text_context_verbose` function to better retrieve static text values from UI elements, ensuring accurate context representation in terminal environments.
- These changes aim to improve user experience and functionality for terminal-based applications in the autocomplete system.
* Enhance autocomplete engine state management and error handling
- Added new fields `last_escape_down` and `last_overlay_signature` to `EngineState` for improved state tracking.
- Implemented `try_reject_via_escape` method to handle escape key interactions, allowing users to reject suggestions more intuitively.
- Updated error handling to display notifications for different states (ready, accepted, rejected, error) using `show_overflow_badge`.
- Refactored state updates to ensure consistent management of suggestion and phase transitions, enhancing overall user experience in the autocomplete system.
* Implement periodic status logging in autocomplete service
- Added a polling mechanism to log the status of the autocomplete engine at regular intervals.
- Enhanced logging to capture changes in phase, application name, suggestions, and errors, improving visibility during service execution.
- Refactored the `autocomplete_start_cli` function to integrate the new logging functionality, ensuring a more informative user experience while the service is running.
* Refactor memory dispatch logic and remove local memory implementation
- Updated the memory dispatch functions to utilize the new `memory_rpc` module, enhancing the handling of memory operations such as document management and namespace queries.
- Removed the local memory implementation, including database interactions and related functions, to streamline the codebase and improve maintainability.
- Introduced new RPC calls for document operations (put, list, delete) and context queries, ensuring a more efficient and consistent approach to memory management.
- This refactor aims to enhance the overall architecture and performance of the memory handling system.
* Remove macOS-specific overflow badge functionality and related helper functions
- Deleted the `show_overflow_badge` and `escape_applescript_string` functions, which were specific to macOS, to streamline the codebase.
- Refactored the `show_overflow_badge` function to provide a no-op implementation for non-macOS platforms, enhancing cross-platform compatibility.
- This change simplifies the autocomplete module by removing platform-dependent code, improving maintainability and clarity.
* Enhance text application logic in autocomplete module
- Updated the `apply_text_to_focused_field` function to improve interaction with focused UI elements on macOS.
- The new implementation retrieves the current value of the focused element and appends the provided text, ensuring better handling of text input.
- Enhanced error reporting to include stderr output when applying suggestions fails, improving debugging capabilities.
- These changes aim to provide a more robust and user-friendly experience in the autocomplete functionality.
* Refactor Landlock feature configuration for Linux support
- Moved the `landlock` and `rppal` dependencies under a conditional target configuration for Linux in both `Cargo.toml` files, ensuring they are only included when building for Linux.
- Updated the `landlock.rs` module to check for both the `sandbox-landlock` feature and the Linux target OS, improving the conditional compilation logic.
- This change enhances cross-platform compatibility and ensures that Landlock functionality is only available on supported systems.
* Update dependencies and enhance Tauri integration
- Updated the Tauri dependency in `Cargo.toml` to include the `tray-icon` feature, enabling system tray support.
- Introduced a new `rust-toolchain.toml` file to pin the Rust version to 1.93.0, ensuring compatibility with the matrix-sdk.
- Modified GitHub workflows to use the specified Rust version from `rust-toolchain.toml` instead of the stable version, improving build consistency.
- Refactored Tauri commands to utilize a new `wrapCommandResult` function for better response handling.
- Added a new `tray` module in `openhuman` for managing system tray functionality, enhancing the desktop experience.
- Updated various command implementations to streamline service management and improve error handling.
* Refactor core process handling and enhance encryption features
- Removed the `openhuman` dependency from `Cargo.lock` and `Cargo.toml`, streamlining the project structure.
- Updated the core process handling to fall back to a child process when in-process execution is unavailable, improving error handling and logging.
- Introduced new encryption commands (`ai_init_encryption`, `ai_encrypt`, `ai_decrypt`) to enhance security features, utilizing AES-GCM for data protection.
- Added a new `tray` module for managing system tray functionality, improving user experience on desktop platforms.
- Refactored various command implementations to improve service management and error handling, ensuring a more robust application architecture.
* Remove unused modules and refactor daemon host configuration
- Deleted the `daemon_host_config`, `memory`, `models`, `openhuman_daemon`, `tray`, `chat`, `conscious_loop`, and `runtime` modules to streamline the codebase.
- Refactored the daemon host configuration logic into the `openhuman` module, consolidating related functionality.
- Updated command implementations to utilize the new configuration methods, ensuring consistent handling of daemon host settings.
- This cleanup enhances maintainability and reduces complexity in the project structure.
* Refactor memory management and update Tauri dependencies
- Removed the `tray-icon` feature from the Tauri dependency in `Cargo.toml` to streamline the configuration.
- Deleted the `core:tray:default` capability from the default capabilities JSON, simplifying the capabilities structure.
- Refactored memory handling in tests to utilize `UnifiedMemory` instead of `SqliteMemory`, enhancing consistency across memory operations.
- Updated memory store, recall, and forget functionalities to support a global namespace, improving memory management and retrieval processes.
- Enhanced error handling and logging in memory operations to provide clearer feedback during execution.
* Remove AI encryption commands and related functionality
- Deleted the `ai_init_encryption`, `ai_encrypt`, and `ai_decrypt` functions to streamline the codebase and remove unused features.
- Updated the command registration in the `run` function to reflect the removal of these encryption commands, enhancing maintainability and reducing complexity.
* Add OAuth integration token handling and channel connection management
- Introduced functions to fetch and encrypt integration tokens using OAuth, enhancing security for token management.
- Updated the channel connections API to support OAuth integration, including listing, connecting, and disconnecting channels.
- Implemented checks for supported channels and authentication modes, improving the robustness of channel connection handling.
- Enhanced error handling for integration token retrieval to ensure required fields are present before proceeding.
* Refactor project structure and update documentation
- Renamed the project from "Outsourced" to "OpenHuman" and revised the project summary to reflect its focus on AI-powered assistance for crypto communities.
- Restructured the repository layout, detailing the purpose of each directory and its contents.
- Updated runtime scope to clarify platform support and Tauri's desktop-only focus.
- Enhanced documentation across various files, including architecture, services, and routing, to improve clarity and usability for contributors.
- Removed outdated sections and streamlined commands for development and production builds, ensuring consistency in the documentation.
* Implement REPL session management and multimodal support
- Introduced a new REPL session management system, allowing for session-specific interactions with agents.
- Added functions for starting, chatting, resetting, and ending REPL sessions, enhancing user experience and control.
- Implemented multimodal message handling, enabling the processing of images alongside text in user messages.
- Updated the project structure to include new modules for identity and multimodal functionalities, improving organization and maintainability.
- Enhanced error handling and logging for session operations, providing clearer feedback during execution.
* Refactor memory store implementation and introduce unified memory management
- Removed the legacy memory store implementation and replaced it with a new unified memory management system.
- Introduced a `MemoryClient` for handling document storage, retrieval, and namespace management.
- Added support for key-value storage and graph data structures within the unified memory framework.
- Enhanced the `UnifiedMemory` struct with methods for document upsertion, querying, and namespace operations.
- Updated the project structure to include new modules for memory types, factories, and traits, improving organization and maintainability.
- Improved error handling and logging across memory operations for clearer feedback during execution.
* Implement QuickJS skill instance management
- Removed the previous QjsSkillInstance implementation and replaced it with a new modular structure.
- Introduced separate modules for event loop management, instance handling, JavaScript handlers, and utility functions.
- Enhanced the event loop to efficiently manage QuickJS runtime tasks, including timer callbacks and message processing.
- Added support for asynchronous tool calls and lifecycle management within the QuickJS context.
- Improved error handling and logging throughout the new implementation for better debugging and user feedback.
- Updated documentation to reflect the new structure and functionality of the QuickJS skill instance.
@@ -16,7 +16,7 @@
|
||||
## Testing
|
||||
|
||||
- [ ] `yarn -s compile`
|
||||
- [ ] `cargo check --manifest-path src-tauri/Cargo.toml`
|
||||
- [ ] `cargo check --manifest-path app/src-tauri/Cargo.toml`
|
||||
- [ ] Other checks run (list commands)
|
||||
- [ ] Manual validation completed (list scenarios)
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: dtolnay/rust-toolchain@1.93.0
|
||||
with:
|
||||
targets: x86_64-unknown-linux-gnu
|
||||
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
id: cargo-lock-fingerprint
|
||||
shell: bash
|
||||
run: |
|
||||
echo "hash=$(tail -n +8 src-tauri/Cargo.lock | openssl dgst -sha256 | awk '{print $2}')" >> "$GITHUB_OUTPUT"
|
||||
echo "hash=$(tail -n +8 Cargo.lock | openssl dgst -sha256 | awk '{print $2}')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Cache Cargo registry and git sources
|
||||
uses: actions/cache@v4
|
||||
@@ -73,16 +73,17 @@ jobs:
|
||||
run: cd skills && yarn install --frozen-lockfile
|
||||
|
||||
- name: Build sidecar core binary
|
||||
run: cargo build --manifest-path rust-core/Cargo.toml --release --target x86_64-unknown-linux-gnu --bin openhuman
|
||||
run: cargo build --manifest-path Cargo.toml --release --target x86_64-unknown-linux-gnu --bin openhuman
|
||||
|
||||
- name: Stage sidecar for Tauri bundler
|
||||
run: |
|
||||
mkdir -p src-tauri/binaries
|
||||
# Workspace root target/ (not rust-core/target/) — see root Cargo.toml [workspace]
|
||||
cp target/x86_64-unknown-linux-gnu/release/openhuman src-tauri/binaries/openhuman-x86_64-unknown-linux-gnu
|
||||
chmod +x src-tauri/binaries/openhuman-x86_64-unknown-linux-gnu
|
||||
mkdir -p app/src-tauri/binaries
|
||||
# Release artifacts for the root package land in repo root target/
|
||||
cp target/x86_64-unknown-linux-gnu/release/openhuman app/src-tauri/binaries/openhuman-x86_64-unknown-linux-gnu
|
||||
chmod +x app/src-tauri/binaries/openhuman-x86_64-unknown-linux-gnu
|
||||
|
||||
- name: Build Tauri app
|
||||
working-directory: app
|
||||
run: |
|
||||
TAURI_CONFIG_OVERRIDE='{"bundle":{"createUpdaterArtifacts":false}}'
|
||||
yarn tauri build -c "$TAURI_CONFIG_OVERRIDE" --bundles none -- --target x86_64-unknown-linux-gnu
|
||||
|
||||
@@ -79,9 +79,9 @@ jobs:
|
||||
throw new Error(`Invalid release_type: ${releaseType}`);
|
||||
}
|
||||
|
||||
const packagePath = 'package.json';
|
||||
const tauriPath = 'src-tauri/tauri.conf.json';
|
||||
const cargoPath = 'src-tauri/Cargo.toml';
|
||||
const packagePath = 'app/package.json';
|
||||
const tauriPath = 'app/src-tauri/tauri.conf.json';
|
||||
const cargoPath = 'app/src-tauri/Cargo.toml';
|
||||
|
||||
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
||||
const match = String(pkg.version || '').match(/^(\d+)\.(\d+)\.(\d+)$/);
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
`$1${nextVersion}$3`,
|
||||
);
|
||||
if (updatedCargo === cargo) {
|
||||
throw new Error('Failed to update [package].version in src-tauri/Cargo.toml');
|
||||
throw new Error('Failed to update [package].version in app/src-tauri/Cargo.toml');
|
||||
}
|
||||
fs.writeFileSync(cargoPath, updatedCargo);
|
||||
|
||||
@@ -147,7 +147,7 @@ jobs:
|
||||
VERSION: ${{ steps.bump.outputs.version }}
|
||||
TAG: ${{ steps.bump.outputs.tag }}
|
||||
run: |
|
||||
git add package.json src-tauri/tauri.conf.json src-tauri/Cargo.toml
|
||||
git add app/package.json app/src-tauri/tauri.conf.json app/src-tauri/Cargo.toml
|
||||
git commit -m "chore(release): v${VERSION}"
|
||||
git push origin main
|
||||
|
||||
@@ -244,8 +244,8 @@ jobs:
|
||||
node-version: 24.x
|
||||
cache: yarn
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Install Rust (rust-toolchain.toml)
|
||||
uses: dtolnay/rust-toolchain@1.93.0
|
||||
with:
|
||||
targets: ${{ matrix.settings.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
|
||||
|
||||
@@ -260,7 +260,7 @@ jobs:
|
||||
id: cargo-lock-fingerprint
|
||||
shell: bash
|
||||
run: |
|
||||
echo "hash=$(tail -n +8 src-tauri/Cargo.lock | openssl dgst -sha256 | awk '{print $2}')" >> "$GITHUB_OUTPUT"
|
||||
echo "hash=$(tail -n +8 Cargo.lock | openssl dgst -sha256 | awk '{print $2}')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Cache Cargo registry and git sources
|
||||
uses: actions/cache@v4
|
||||
@@ -287,7 +287,7 @@ jobs:
|
||||
env:
|
||||
BASE_URL: ${{ vars.BASE_URL }}
|
||||
UPDATER_PUBLIC_KEY: ${{ secrets.UPDATER_PUBLIC_KEY }}
|
||||
WITH_UPDATER: 'true'
|
||||
WITH_UPDATER: "true"
|
||||
with:
|
||||
script: |
|
||||
const workspacePath = process.env.GITHUB_WORKSPACE.replace(/\\/g, '/');
|
||||
@@ -313,12 +313,14 @@ jobs:
|
||||
CORE_DIR="openhuman_core"
|
||||
elif [ -f "rust-core/Cargo.toml" ]; then
|
||||
CORE_DIR="rust-core"
|
||||
elif [ -f "Cargo.toml" ] && grep -q '^name = "openhuman"' Cargo.toml; then
|
||||
CORE_DIR="."
|
||||
else
|
||||
echo "No core Cargo manifest found (expected openhuman_core/Cargo.toml or rust-core/Cargo.toml)"
|
||||
echo "No core Cargo manifest found (expected root Cargo.toml with openhuman, openhuman_core/Cargo.toml, or rust-core/Cargo.toml)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SIDE_CAR_BASE="$(node -e "const fs=require('fs');const c=JSON.parse(fs.readFileSync('src-tauri/tauri.conf.json','utf8'));const b=(c.bundle&&Array.isArray(c.bundle.externalBin)&&c.bundle.externalBin[0])||'binaries/openhuman';process.stdout.write(String(b).split('/').pop());")"
|
||||
SIDE_CAR_BASE="$(node -e "const fs=require('fs');const c=JSON.parse(fs.readFileSync('app/src-tauri/tauri.conf.json','utf8'));const b=(c.bundle&&Array.isArray(c.bundle.externalBin)&&c.bundle.externalBin[0])||'binaries/openhuman';process.stdout.write(String(b).split('/').pop());")"
|
||||
CORE_BIN_NAME="${SIDE_CAR_BASE}"
|
||||
|
||||
echo "core_dir=$CORE_DIR" >> "$GITHUB_OUTPUT"
|
||||
@@ -346,9 +348,9 @@ jobs:
|
||||
- name: Stage sidecar for Tauri bundler
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p src-tauri/binaries
|
||||
mkdir -p app/src-tauri/binaries
|
||||
SOURCE="$CORE_TARGET_DIR/$CORE_BIN_NAME"
|
||||
DEST="src-tauri/binaries/$SIDECAR_BASE-$MATRIX_TARGET"
|
||||
DEST="app/src-tauri/binaries/$SIDECAR_BASE-$MATRIX_TARGET"
|
||||
cp "$SOURCE" "$DEST"
|
||||
chmod +x "$DEST"
|
||||
env:
|
||||
@@ -370,9 +372,10 @@ jobs:
|
||||
BASE_URL: ${{ vars.BASE_URL }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY || secrets.UPDATER_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD || secrets.UPDATER_PRIVATE_KEY_PASSWORD }}
|
||||
WITH_UPDATER: 'true'
|
||||
WITH_UPDATER: "true"
|
||||
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.settings.platform == 'macos-latest' && '10.15' || '' }}
|
||||
with:
|
||||
projectPath: app
|
||||
# Tools discovery now uses a JS mock registry (no Rust discovery
|
||||
# binary target), so there is no extra helper executable for Tauri
|
||||
# to copy/sign inside release app bundles.
|
||||
@@ -391,7 +394,7 @@ jobs:
|
||||
if: matrix.settings.platform == 'macos-latest'
|
||||
shell: bash
|
||||
run: |
|
||||
APP_PATH="src-tauri/target/${{ matrix.settings.target }}/release/bundle/macos/OpenHuman.app"
|
||||
APP_PATH="target/${{ matrix.settings.target }}/release/bundle/macos/OpenHuman.app"
|
||||
echo "Inspecting bundle at: $APP_PATH"
|
||||
ls -la "$APP_PATH/Contents/MacOS"
|
||||
ls -la "$APP_PATH/Contents/Resources" | grep openhuman || true
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24.x
|
||||
cache: 'yarn'
|
||||
cache: "yarn"
|
||||
|
||||
- name: Cache node modules
|
||||
id: yarn-cache
|
||||
@@ -64,8 +64,8 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Install Rust (rust-toolchain.toml)
|
||||
uses: dtolnay/rust-toolchain@1.93.0
|
||||
with:
|
||||
targets: x86_64-unknown-linux-gnu
|
||||
|
||||
@@ -91,11 +91,11 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-rust-test-cargo-
|
||||
|
||||
- name: Test rust-core (openhuman-core)
|
||||
run: cargo test -p openhuman-core
|
||||
- name: Test rust-core (openhuman)
|
||||
run: cargo test -p openhuman
|
||||
|
||||
- name: Test src-tauri (OpenHuman)
|
||||
run: cargo test -p OpenHuman
|
||||
run: cargo test --manifest-path app/src-tauri/Cargo.toml
|
||||
|
||||
e2e-macos:
|
||||
name: E2E (macOS / Appium)
|
||||
@@ -112,10 +112,10 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24.x
|
||||
cache: 'yarn'
|
||||
cache: "yarn"
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Install Rust (rust-toolchain.toml)
|
||||
uses: dtolnay/rust-toolchain@1.93.0
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
@@ -124,7 +124,7 @@ jobs:
|
||||
run: cd skills && yarn install --frozen-lockfile
|
||||
|
||||
- name: Ensure .env exists for E2E build
|
||||
run: touch .env
|
||||
run: touch app/.env
|
||||
|
||||
- name: Install Appium and mac2 driver
|
||||
run: |
|
||||
@@ -134,7 +134,7 @@ jobs:
|
||||
- name: Build E2E app bundle
|
||||
run: yarn test:e2e:build
|
||||
env:
|
||||
E2E_SKIP_CARGO_CLEAN: '1'
|
||||
E2E_SKIP_CARGO_CLEAN: "1"
|
||||
|
||||
- name: Run all E2E flows
|
||||
run: yarn test:e2e:all:flows
|
||||
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Type check TypeScript files
|
||||
run: npx tsc
|
||||
run: yarn typecheck
|
||||
env:
|
||||
NODE_ENV: test
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
node_modules
|
||||
dist
|
||||
@@ -35,7 +34,7 @@ scripts/ci-secrets.local.json
|
||||
*.sln
|
||||
*.sw?
|
||||
references/
|
||||
src-tauri/runtime-skill-*
|
||||
app/src-tauri/runtime-skill-*
|
||||
.mypy_cache
|
||||
.ruff_cache
|
||||
.kotlin
|
||||
|
||||
@@ -1,520 +1,167 @@
|
||||
## Project Summary
|
||||
# OpenHuman
|
||||
|
||||
Cross-platform crypto community communication platform built with **Tauri v2** (React 19 + Rust). Targets desktop (Windows, macOS) and mobile (Android, iOS). Features deep Telegram integration via MTProto, real-time Socket.io communication, V8-based skill execution engine, and an MCP (Model Context Protocol) tool system for AI-driven Telegram interactions.
|
||||
**AI-powered assistant for crypto communities — React + Tauri v2 desktop app with a Rust core (JSON-RPC / CLI) and sandboxed QuickJS skills.**
|
||||
|
||||
## Runtime Scope
|
||||
This file orients contributors and coding agents. Authoritative narrative architecture: [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md). Frontend layout: [`docs/src/README.md`](docs/src/README.md). Tauri shell: [`docs/src-tauri/README.md`](docs/src-tauri/README.md).
|
||||
|
||||
- Tauri host/runtime is desktop-only.
|
||||
- Always run and validate Tauri codepaths as desktop (`windows`, `macOS`, `linux`).
|
||||
- Do not add or maintain Android/iOS/web runtime branches inside `src-tauri`.
|
||||
---
|
||||
|
||||
## App Theme & Design System
|
||||
## Repository layout
|
||||
|
||||
**Design Philosophy**: Premium, sophisticated crypto platform with calm, trustworthy aesthetic.
|
||||
| Path | Role |
|
||||
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`app/`** | Yarn workspace **`openhuman-app`**: Vite + React (`app/src/`), Tauri desktop host (`app/src-tauri/`), Vitest tests |
|
||||
| **Repo root `src/`** | Rust library **`openhuman_core`** and **`openhuman`** CLI binary (`src/bin/openhuman.rs`) — `core_server`, `openhuman::*` domains, skills runtime (QuickJS / `rquickjs`), MCP routing in the core process |
|
||||
| **`skills/`** | Skill packages (built into `skills/skills` for bundling) |
|
||||
| **`Cargo.toml`** (root) | Core crate; `cargo build --bin openhuman` produces the sidecar the UI stages via `app`’s `core:stage` |
|
||||
| **`docs/`** | Architecture and module guides (numbered pages under `docs/src/`, `docs/src-tauri/`) |
|
||||
|
||||
### Color Palette
|
||||
Commands in documentation assume the **repo root** unless noted: `yarn dev` runs the `app` workspace.
|
||||
|
||||
- **Primary**: Ocean blue (`#4A83DD`) optimized for dark backgrounds
|
||||
- **Sage**: Success green (`#4DC46F`) for growth indicators
|
||||
- **Amber**: Warning (`#E8A838`) for attention states
|
||||
- **Coral**: Error (`#F56565`) soft professional red
|
||||
- **Canvas**: Background layers (`#FAFAF9` to `#D4D4D1`) with subtle warmth
|
||||
- **Market Colors**: Bullish green, bearish red, Bitcoin orange, Ethereum purple
|
||||
---
|
||||
|
||||
### Typography
|
||||
## Runtime scope
|
||||
|
||||
- **Primary**: Inter (premium font stack)
|
||||
- **Display**: Cabinet Grotesk for headings
|
||||
- **Mono**: JetBrains Mono for code
|
||||
- **Scale**: Sophisticated sizing with negative letter spacing for elegance
|
||||
- **Shipped product**: desktop — Windows, macOS, Linux (see [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) “Platform reach”).
|
||||
- **Tauri host** (`app/src-tauri`): **desktop-only** (`compile_error!` for non-desktop targets). Do not add Android/iOS branches inside `app/src-tauri`.
|
||||
- **Core binary** (`openhuman`): spawned/staged as a **sidecar**; the Web UI talks to it over HTTP (`core_rpc_relay` + `core_rpc` client), not by re-implementing domain logic in the shell.
|
||||
|
||||
### Component System
|
||||
---
|
||||
|
||||
- **Shadows**: Glow effects, subtle to float depth levels
|
||||
- **Animations**: Fade-in, slide-in, scale-in with cubic-bezier easing
|
||||
- **Border Radius**: Smooth system from `xs` (0.25rem) to `5xl` (2rem)
|
||||
- **Spacing**: Extended scale including custom values (4.5, 13, 15, etc.)
|
||||
|
||||
### Current UI State
|
||||
|
||||
- Uses HashRouter (not BrowserRouter) as seen in `App.tsx:1`
|
||||
- 153 TypeScript files total in src/
|
||||
- Sophisticated Tailwind config with custom color system and animations
|
||||
|
||||
## Commands
|
||||
## Commands (from repository root)
|
||||
|
||||
```bash
|
||||
# Frontend dev server only (port 1420)
|
||||
# Frontend + Tauri dev (workspace delegates to app/)
|
||||
yarn dev
|
||||
|
||||
# Desktop dev with hot-reload (starts Vite + Tauri)
|
||||
# Desktop with Tauri (loads env via scripts/load-dotenv.sh)
|
||||
yarn tauri dev
|
||||
|
||||
# Desktop dev with enhanced debugging (RUST_BACKTRACE and RUST_LOG enabled)
|
||||
yarn dev:app
|
||||
# Production UI build (app workspace)
|
||||
yarn build
|
||||
|
||||
# Production build (TypeScript compile + Vite build + Tauri bundle)
|
||||
yarn tauri build
|
||||
# Typecheck / lint / format (app workspace)
|
||||
yarn typecheck
|
||||
yarn lint
|
||||
yarn format
|
||||
yarn format:check
|
||||
|
||||
# Debug build with .app bundle (required for deep link testing on macOS)
|
||||
# On macOS, openhuman:// only works when running the .app, not `tauri dev`
|
||||
yarn tauri build --debug --bundles app
|
||||
yarn macos:dev
|
||||
# Stage openhuman core binary next to Tauri resources (required for core RPC)
|
||||
cd app && yarn core:stage
|
||||
|
||||
# Android
|
||||
yarn tauri android dev
|
||||
yarn tauri android build
|
||||
# Skills (under skills/)
|
||||
yarn workspace openhuman-app skills:build
|
||||
yarn workspace openhuman-app skills:watch
|
||||
|
||||
# iOS
|
||||
yarn tauri ios dev
|
||||
yarn tauri ios build
|
||||
# Rust — core library + CLI (repo root)
|
||||
cargo check --manifest-path Cargo.toml
|
||||
cargo build --manifest-path Cargo.toml --bin openhuman
|
||||
|
||||
# Skills development
|
||||
yarn skills:build # Build skills in development mode
|
||||
yarn skills:watch # Watch skills for changes
|
||||
|
||||
# AI Configuration
|
||||
yarn tools:generate # Discover tools from V8 runtime and generate TOOLS.md
|
||||
|
||||
# Rust checks
|
||||
cargo check --manifest-path src-tauri/Cargo.toml
|
||||
cargo clippy --manifest-path src-tauri/Cargo.toml
|
||||
# Rust — Tauri shell only
|
||||
cargo check --manifest-path app/src-tauri/Cargo.toml
|
||||
```
|
||||
|
||||
No test framework is currently configured. **ESLint and Prettier are configured** with Husky pre-commit/pre-push hooks for code quality enforcement.
|
||||
**Tests**: Vitest in `app/` (`yarn test`, `yarn test:coverage`). Rust tests via `cargo test` at repo root as wired in `app/package.json`.
|
||||
|
||||
## Architecture
|
||||
**Quality**: ESLint + Prettier + Husky in the `app` workspace.
|
||||
|
||||
### Provider Chain (App.tsx)
|
||||
---
|
||||
|
||||
The app wraps in this order: `Redux Provider` → `PersistGate` → `SocketProvider` → `TelegramProvider` → `HashRouter` → `AppRoutes`. **Note**: Now uses HashRouter instead of BrowserRouter. This ordering matters because Socket.io and Telegram providers depend on Redux auth state.
|
||||
## Frontend (`app/src/`)
|
||||
|
||||
### State Management (Redux Toolkit + Persist)
|
||||
### Provider chain (`app/src/App.tsx`)
|
||||
|
||||
State lives in `src/store/` using Redux Toolkit slices:
|
||||
Order matters for auth and realtime:
|
||||
|
||||
- **authSlice** — JWT token, onboarding completion flag (persisted)
|
||||
- **userSlice** — user profile
|
||||
- **socketSlice** — connection status, socket ID
|
||||
- **telegramSlice** — connection/auth status, chats, messages, threads (selectively persisted; loading/error states excluded)
|
||||
- **aiSlice** — AI system state, memory management, session tracking
|
||||
- **skillsSlice** — skills catalog, setup status, management state, V8 runtime integration
|
||||
- **teamSlice** — team management, member invites, permissions
|
||||
`Redux Provider` → `PersistGate` → **`UserProvider`** → **`SocketProvider`** → **`AIProvider`** → **`SkillProvider`** → **`HashRouter`** → `AppRoutes`.
|
||||
|
||||
Redux Persist stores auth and telegram state (storage backend is configurable; default uses localStorage). The telegram slice has a complex nested structure in `src/store/telegram/` with separate files for types, reducers, extraReducers, and thunks.
|
||||
There is **no** `TelegramProvider` in the current tree; Telegram may appear in UI copy or legacy settings, but MTProto is not an active provider here.
|
||||
|
||||
### LocalStorage
|
||||
### State (`app/src/store/`)
|
||||
|
||||
- **Do not use `localStorage` (or `sessionStorage`) for app state or feature logic.** Use Redux (and Redux Persist where needed) instead.
|
||||
- **Remove any existing `localStorage` usage** when touching related code. User-scoped data (auth, onboarding, Telegram session, socket state) lives in Redux, keyed by user id where applicable. Telegram session is in `telegram.byUser[userId].sessionString`, not localStorage.
|
||||
- **Exceptions**: Redux-persist may use a localStorage-backed storage adapter by default; that is the persistence layer, not app logic. Any other remaining usage (e.g. deep-link `deepLinkHandled` flag) should be migrated to Redux or similar when that code is modified.
|
||||
- **General rule**: Avoid adding new `localStorage` or `sessionStorage` usage; prefer Redux and remove existing usage when you work on affected areas.
|
||||
Redux Toolkit slices include **auth**, **user**, **socket**, **ai**, **skills**, **team**, and related modules. Prefer Redux (and persist where configured) over ad hoc `localStorage` for app state; see project rules for exceptions.
|
||||
|
||||
### Service Layer (Singletons)
|
||||
### Services (`app/src/services/`)
|
||||
|
||||
- **mtprotoService** (`src/services/mtprotoService.ts`) — Telegram MTProto client via `telegram` npm package. Session stored in Redux (`telegram.byUser[userId].sessionString`), not localStorage. Auto-retries FLOOD_WAIT up to 60s.
|
||||
- **socketService** (`src/services/socketService.ts`) — Socket.io client. Auth token passed in socket `auth` object (not query string). Transports: polling first, then WebSocket. Enhanced with Rust-native Socket.io client for persistent connections.
|
||||
- **apiClient** (`src/services/apiClient.ts`) — HTTP client for REST backend.
|
||||
Singleton-style modules include **`apiClient`**, **`socketService`**, **`coreRpcClient`** (HTTP bridge to the core process), and domain **`api/*`** clients. There is **no** `mtprotoService` in this tree.
|
||||
|
||||
### MCP System (`src/lib/mcp/`)
|
||||
### MCP (`app/src/lib/mcp/`)
|
||||
|
||||
Model Context Protocol implementation for AI tool execution over Socket.io:
|
||||
Transport, validation, and types for JSON-RPC-style messaging over Socket.io — **not** a large Telegram tool pack. Tooling for agents is driven by the **skills** system and backend; see `agentToolRegistry.ts` and core RPC.
|
||||
|
||||
- `transport.ts` — Socket.io JSON-RPC 2.0 transport with 30s timeout
|
||||
- `telegram/server.ts` — TelegramMCPServer manages 99 tool definitions
|
||||
- `telegram/tools/` — Individual tool files (one per Telegram API operation)
|
||||
- Tools use `big-integer` library for Telegram's large integer IDs
|
||||
### Routing (`app/src/AppRoutes.tsx`)
|
||||
|
||||
### Routing (`src/AppRoutes.tsx`)
|
||||
Hash routes include `/`, `/onboarding`, `/mnemonic`, `/home`, `/intelligence`, `/skills`, `/conversations`, `/invites`, `/agents`, `/settings/*`, plus `DefaultRedirect`. **No** dedicated `/login` route in `AppRoutes` (auth flows use the welcome/onboarding paths).
|
||||
|
||||
```
|
||||
/ → Welcome (public)
|
||||
/login → Login (public)
|
||||
/onboarding → Onboarding (protected, requires auth, not yet onboarded)
|
||||
/home → Home (protected, requires auth + onboarded)
|
||||
* → DefaultRedirect (routes based on auth state)
|
||||
```
|
||||
### AI configuration
|
||||
|
||||
`PublicRoute` redirects authenticated users away. `ProtectedRoute` enforces auth and optionally onboarding status.
|
||||
Bundled prompts live under **`src/ai/prompts/`** at the **repository root** (also bundled via `app/src-tauri/tauri.conf.json` `resources`). Loaders under `app/src/lib/ai/` use `?raw` imports, optional remote fetch, and in Tauri **`ai_get_config` / `ai_refresh_config`** for packaged content.
|
||||
|
||||
### Deep Link Auth Flow
|
||||
---
|
||||
|
||||
Web-to-desktop handoff using `openhuman://` URL scheme:
|
||||
## Tauri shell (`app/src-tauri/`)
|
||||
|
||||
1. User authenticates in browser
|
||||
2. Browser redirects to `openhuman://auth?token=<loginToken>`
|
||||
3. Tauri catches the deep link, Rust `exchange_token` command calls backend via `reqwest` (bypasses CORS)
|
||||
4. Backend returns `sessionToken` + user object
|
||||
5. App stores session in Redux, navigates to onboarding/home
|
||||
Thin desktop host: window management, daemon health bridging, **core process lifecycle** (`core_process`, `CoreProcessHandle`), and **JSON-RPC relay** to the **`openhuman`** sidecar (`core_rpc_relay`, `core_rpc`).
|
||||
|
||||
Key file: `src/utils/desktopDeepLinkListener.ts` (lazy-loaded in `main.tsx`). Uses a `deepLinkHandled` flag to prevent infinite reload loops. Deep links do NOT work in `tauri dev` on macOS — must use built `.app` bundle.
|
||||
Registered IPC commands (see [`docs/src-tauri/02-commands.md`](docs/src-tauri/02-commands.md)) include **`greet`**, **`write_ai_config_file`**, **`ai_get_config`**, **`ai_refresh_config`**, **`core_rpc_relay`**, **window** commands, and **OpenHuman service / daemon host** helpers (`openhuman_*`).
|
||||
|
||||
### Rust Backend (`src-tauri/src/lib.rs`)
|
||||
Deep link plugin is registered where supported; behavior is platform-specific (see platform notes below).
|
||||
|
||||
Enhanced Rust backend with comprehensive skill execution and runtime management:
|
||||
---
|
||||
|
||||
**Core Commands:**
|
||||
## Rust core (repo root `src/`)
|
||||
|
||||
- `greet` — demo command
|
||||
- `exchange_token` — CORS-free HTTP POST to backend for token exchange (desktop only)
|
||||
- **`openhuman/`** — Domain logic (skills, memory, channels, config, …). RPC controllers live in **`rpc.rs`** files per domain; use **`RpcOutcome<T>`** pattern per [`AGENTS.md`](AGENTS.md) / internal rules.
|
||||
- **`src/openhuman/` module layout**: **New** functionality must live in a **dedicated subdirectory** (its own folder/module, e.g. `openhuman/my_domain/mod.rs` plus related files, or a new subfolder under an existing domain). Do **not** add new standalone `*.rs` files directly at `src/openhuman/` root; place new code in a module directory and declare it from `mod.rs` (or merge into an existing domain folder).
|
||||
- **`core_server/`** — Transport only: Axum/HTTP, JSON-RPC envelope, CLI parsing, **dispatch** (`core_server::dispatch`) — **no** heavy business logic here.
|
||||
- **Layering**: Implementation in `openhuman::<domain>/`, controllers in `openhuman::<domain>/rpc.rs`, routes in `core_server/`.
|
||||
|
||||
**Runtime Management:**
|
||||
Skills runtime uses **QuickJS** (`rquickjs`) in **`src/openhuman/skills/`** (e.g. `qjs_skill_instance.rs`, `qjs_engine.rs`), not V8/deno_core in this repository.
|
||||
|
||||
- `discover_skills` — V8 skill discovery and manifest parsing
|
||||
- `enable_skill` / `disable_skill` — skill lifecycle management
|
||||
- `get_skill_preferences` / `set_skill_preferences` — skill configuration
|
||||
- `connect_to_socket` — Rust-native Socket.io connection
|
||||
- `get_socket_status` — connection status monitoring
|
||||
---
|
||||
|
||||
**Android Support:**
|
||||
## App theme & design system
|
||||
|
||||
- `RuntimeService` — background service for skill execution
|
||||
- Notification permissions and foreground service management
|
||||
- Android logging integration with logcat
|
||||
**Design intent**: Premium, calm crypto UI — ocean primary (`#4A83DD`), sage / amber / coral semantic colors, Inter + Cabinet Grotesk + JetBrains Mono, Tailwind with custom radii/spacing/shadows. Details: [`docs/DESIGN_GUIDELINES.md`](docs/DESIGN_GUIDELINES.md).
|
||||
|
||||
Deep link plugin registered at setup. `register_all()` called only on Windows/Linux (panics on macOS).
|
||||
---
|
||||
|
||||
### V8 Runtime System (`src-tauri/src/runtime/`)
|
||||
## Environment variables (Vite)
|
||||
|
||||
Advanced JavaScript execution engine for skills using V8 (via deno_core):
|
||||
Set in `.env` for the **`app`** workspace (`VITE_*` exposed to the client):
|
||||
|
||||
**Core Components:**
|
||||
| Variable | Purpose |
|
||||
| ------------------------- | ------------------------------------------------------------------------- |
|
||||
| `VITE_BACKEND_URL` | API base (default in code: production API; see `app/src/utils/config.ts`) |
|
||||
| `VITE_SENTRY_DSN` | Optional Sentry DSN |
|
||||
| `VITE_SKILLS_GITHUB_REPO` | Skills catalog GitHub repo slug |
|
||||
|
||||
- `v8_engine.rs` — V8 JavaScript runtime initialization and management
|
||||
- `v8_skill_instance.rs` — Individual skill execution contexts and lifecycle
|
||||
- `skill_registry.rs` — Skill discovery, registration, and state management
|
||||
- `manifest.rs` — Skill manifest parsing with platform compatibility checks
|
||||
- `socket_manager.rs` — Persistent Socket.io connections with reconnection logic
|
||||
- `cron_scheduler.rs` — Scheduled task execution for time-based skills
|
||||
- `preferences.rs` — Skill configuration and settings persistence
|
||||
---
|
||||
|
||||
**Bridge System (`src-tauri/src/runtime/bridge/`):**
|
||||
## Git workflow
|
||||
|
||||
- `skills_bridge.rs` — Skill-to-skill communication and state sharing
|
||||
- `tauri_bridge.rs` — Frontend-backend IPC and environment access
|
||||
- `net.rs` — HTTP/fetch operations for skills
|
||||
- `db.rs` — Database operations and storage management
|
||||
- `store.rs` — Key-value storage for skill data
|
||||
- `log_bridge.rs` — Structured logging from skills
|
||||
- `cron_bridge.rs` — Cron job scheduling and management
|
||||
- **Public repo**; push to your working branch; PRs target **`main`**.
|
||||
- Use [`.github/pull_request_template.md`](.github/pull_request_template.md); AI-generated PR text should follow its sections and checklist.
|
||||
|
||||
**Quickjs Integration (`src-tauri/src/services/quickjs/`):**
|
||||
---
|
||||
|
||||
- `service.rs` — High-level client management with V8 integration
|
||||
- `bootstrap.js` — V8 JavaScript bootstrap environment
|
||||
- `ops/mod.rs` — Native operations for WebSocket, timers, and async handling
|
||||
- `storage.rs` — Persistent storage for sessions and data
|
||||
## Key patterns (concise)
|
||||
|
||||
**Platform Support:**
|
||||
- **`src/openhuman/`**: New features go in a **folder/module**, not new root-level `src/openhuman/*.rs` files (see Rust core section).
|
||||
- **File size**: Prefer ≤ ~500 lines per source file; split modules when growing.
|
||||
- **Pre-merge checks** (when touching code): Prettier, ESLint, `tsc --noEmit` in `app/`; `cargo fmt` + `cargo check` for changed Rust (`Cargo.toml` at root and/or `app/src-tauri/Cargo.toml` as appropriate).
|
||||
- **No dynamic imports** in app code (static `import` only); use try/catch around Tauri APIs where needed.
|
||||
- **Type-only imports**: `import type` where appropriate.
|
||||
- **Dual socket / tool sync**: If you change realtime protocol, keep **frontend** (`socketService` / MCP transport) and **core** socket behavior aligned (see [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) dual-socket section).
|
||||
|
||||
- Desktop platforms: Full V8 runtime with all features
|
||||
- Mobile platforms: Error handling with feature availability checks
|
||||
- Platform-specific skill filtering based on manifest declarations
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
## Platform notes
|
||||
|
||||
Set in `.env` (Vite exposes `VITE_*` prefixed vars):
|
||||
- **macOS deep links**: Often require a built **`.app`** bundle; not only `tauri dev`. See [`docs/telegram-login-desktop.md`](docs/telegram-login-desktop.md) if applicable.
|
||||
- **`window.__TAURI__`**: Not assumed at module load; guard Tauri usage accordingly.
|
||||
- **Core sidecar**: Must be staged/built so `core_rpc` can reach the `openhuman` binary (see `scripts/stage-core-sidecar.mjs`).
|
||||
|
||||
| Variable | Purpose |
|
||||
| --------------------------- | ------------------------------------------------------------------- |
|
||||
| `VITE_BACKEND_URL` | Backend API URL (default: `http://localhost:5005`) |
|
||||
| `VITE_SENTRY_DSN` | Sentry DSN for error reporting (optional) |
|
||||
| `VITE_DEBUG` | Debug mode flag |
|
||||
| `OPENHUMAN_DAEMON_INTERNAL` | Force internal daemon mode (default: false, uses external services) |
|
||||
---
|
||||
|
||||
Production defaults are in `src/utils/config.ts`.
|
||||
|
||||
## AI Configuration System
|
||||
|
||||
OpenHumanuses an OpenClaw-compliant AI configuration system that automatically injects persona and tool context into every user message for consistent AI behavior.
|
||||
|
||||
### Configuration Files
|
||||
|
||||
All AI configuration lives in the `/ai/` directory:
|
||||
|
||||
- **`/ai/SOUL.md`** - AI personality, voice, tone, and behavior patterns
|
||||
- **`/ai/TOOLS.md`** - Auto-generated documentation of all available tools (generated via `yarn tools:generate`)
|
||||
- **`/ai/IDENTITY.md`** - Core identity and values (TODO)
|
||||
- **`/ai/AGENTS.md`** - Agent roles and specializations (TODO)
|
||||
- **`/ai/USER.md`** - User adaptation strategies (TODO)
|
||||
- **`/ai/BOOTSTRAP.md`** - Initialization procedures (TODO)
|
||||
- **`/ai/MEMORY.md`** - Long-term knowledge and patterns (TODO)
|
||||
|
||||
### Modular Loader System
|
||||
|
||||
```typescript
|
||||
// Individual loaders with multi-layer caching
|
||||
loadSoul() → SoulConfig // Personality, voice, behavior
|
||||
loadTools() → ToolsConfig // Available tools and capabilities
|
||||
|
||||
// Unified loader
|
||||
loadAIConfig() → AIConfig // Combined SOUL + TOOLS configuration
|
||||
```
|
||||
|
||||
**Caching Strategy:**
|
||||
|
||||
- Memory cache (immediate)
|
||||
- localStorage cache (30min TTL)
|
||||
- GitHub remote (latest)
|
||||
- Bundled fallback (reliable)
|
||||
|
||||
**TODO**: Set up public AI configuration repository to eliminate 404 fallback errors
|
||||
|
||||
- Current: AI config loaders try GitHub URLs first (fail with 404), then fallback to bundled files
|
||||
- Console shows: "Failed to load resource: the server responded with a status of 404"
|
||||
- Affected: Settings → AI Configuration "Refresh Soul/Tools" buttons
|
||||
- Files: `src/lib/ai/soul/loader.ts`, `src/lib/ai/tools/loader.ts`
|
||||
|
||||
### Unified Injection System
|
||||
|
||||
Every user message automatically gets AI context injected:
|
||||
|
||||
```typescript
|
||||
// Unified injection (recommended)
|
||||
import { injectAll } from '../lib/ai/injector';
|
||||
// Individual injections (for specific needs)
|
||||
import { injectSoul, injectTools } from '../lib/ai/injector';
|
||||
|
||||
const injectedMessage = await injectAll(userMessage);
|
||||
|
||||
const soulMessage = await injectSoul(userMessage);
|
||||
const toolsMessage = await injectTools(userMessage);
|
||||
```
|
||||
|
||||
**Message Format:**
|
||||
|
||||
```
|
||||
[PERSONA_CONTEXT]
|
||||
I am OpenHuman that incredibly smart, funny friend who loves helping people get stuff done
|
||||
Personality: Curious & Enthusiastic, Witty & Engaging, Empathetic
|
||||
Voice: Conversational, Use humor naturally but don't force it
|
||||
[/PERSONA_CONTEXT]
|
||||
|
||||
[TOOLS_CONTEXT]
|
||||
4 tools across 3 skills
|
||||
Categories: Communication (2), Productivity (1), Email (1)
|
||||
Key skills: telegram, notion, gmail
|
||||
[/TOOLS_CONTEXT]
|
||||
|
||||
User message: Hello!
|
||||
```
|
||||
|
||||
### Dynamic TOOLS.md Generation
|
||||
|
||||
TOOLS.md is automatically generated from the V8 skills runtime:
|
||||
|
||||
```bash
|
||||
# Discover tools and generate documentation
|
||||
yarn tools:generate
|
||||
|
||||
# Integration in build pipeline
|
||||
yarn skills:build && yarn tools:generate && tsc && vite build
|
||||
```
|
||||
|
||||
**Process:**
|
||||
|
||||
1. **Discovery**: Spawns Tauri runtime to call `runtime_all_tools()`
|
||||
2. **Parsing**: Extracts tool definitions with JSON Schema
|
||||
3. **Formatting**: Generates OpenClaw-compliant markdown
|
||||
4. **Bundling**: Includes in app for AI context injection
|
||||
|
||||
**Generated Output:**
|
||||
|
||||
- Professional documentation with usage examples
|
||||
- Environment-specific configurations
|
||||
- Tool categorization by skill
|
||||
- Statistics and metadata
|
||||
|
||||
### Integration Points
|
||||
|
||||
AI context injection happens in 4 places:
|
||||
|
||||
1. **`src/pages/Conversations.tsx`** - Main chat interface
|
||||
2. **`src/store/threadSlice.ts`** - Redux sendMessage thunk
|
||||
3. **`src/services/api/threadApi.ts`** - API layer
|
||||
4. **`src/utils/tauriCommands.ts`** - Tauri agent chat
|
||||
|
||||
All use the unified `injectAll()` function for consistency.
|
||||
|
||||
### Settings UI
|
||||
|
||||
View and manage AI configuration in **Settings → AI Configuration**:
|
||||
|
||||
- Live SOUL personality preview
|
||||
- TOOLS statistics and categories
|
||||
- Individual refresh buttons
|
||||
- Source indicators (GitHub vs bundled)
|
||||
- Combined "Refresh All" functionality
|
||||
|
||||
## Recent Changes
|
||||
|
||||
Key updates from recent commits (cd9ebcd to current):
|
||||
|
||||
### Major Runtime Transition
|
||||
|
||||
- **V8 Runtime Migration** (`99c20ea`, `0f6a092`): Complete transition from QuickJS to V8
|
||||
- Replaced QuickJS with V8 (via deno_core) for improved JavaScript execution and WASM support
|
||||
- Enhanced skill management with V8 runtime including improved performance and compatibility
|
||||
- New V8 skill instance handling with advanced execution contexts
|
||||
- Updated dependencies and Cargo.toml to reflect V8 integration
|
||||
- Platform compatibility checks and enhanced manifest handling
|
||||
|
||||
### Android Platform Support
|
||||
|
||||
- **Full Android Integration** (`ce06cfc`, `a2578b9`): Production-ready mobile platform support
|
||||
- Complete Android project generation with MainActivity and RuntimeService
|
||||
- Background service for persistent skill execution on Android
|
||||
- Notification permission handling and foreground service management
|
||||
- Android logging integration with logcat for better debugging
|
||||
- Deep link support configuration in AndroidManifest.xml
|
||||
|
||||
### Enhanced Socket & Runtime Management
|
||||
|
||||
- **Rust-Native Socket.io Client** (`68d397e`): Persistent connection infrastructure
|
||||
- Native Rust Socket.io implementation for improved reliability
|
||||
- Enhanced socket connection handling with reconnection logic
|
||||
- Dynamic backend URL configuration support
|
||||
- Improved error handling and connection status monitoring
|
||||
|
||||
### Skills System Improvements
|
||||
|
||||
- **Advanced Skill Management** (`e841c86`, `719e6e5`): Enhanced skill lifecycle and configuration
|
||||
- Skill setup pipeline with contextual Enable/Setup/Configure/Retry buttons
|
||||
- Platform filtering for skills with manifest-based compatibility checks
|
||||
- Enhanced skill status derivation and connection indicators
|
||||
- Environment variable exposure to skills (whitelisted values)
|
||||
- Improved skill discovery and manifest processing with logging
|
||||
|
||||
### Major Additions
|
||||
|
||||
- **ESLint & Prettier Integration** (`5896966`): Complete code quality toolchain
|
||||
- ES module syntax for ESLint configuration with enhanced TypeScript support
|
||||
- Husky pre-commit/pre-push hooks for automatic formatting and linting
|
||||
- Type-only imports standardization across codebase
|
||||
- Consolidated import statements and improved code organization
|
||||
- GitHub workflows updated with Prettier and ESLint checks
|
||||
- **Advanced Skills System** (`10ec1b3`): Comprehensive skill management platform
|
||||
- Dynamic skills loading from local directory via Rust integration
|
||||
- SkillSetupModal with conditional rendering (wizard vs management panel)
|
||||
- Background GitHub sync for skills catalog updates
|
||||
- Skills table with setup status indicators and management controls
|
||||
- Enhanced skill metadata with setup hooks and descriptions
|
||||
- **Team Management Features** (`10ec1b3`): Multi-user collaboration system
|
||||
- TeamPanel, TeamMembersPanel, and TeamInvitesPanel components
|
||||
- Redux state management for teams, members, and invites
|
||||
- Team API integration with CRUD operations
|
||||
- Settings modal routing for team management paths
|
||||
- Role-based permissions and invitation system
|
||||
- **AI System Enhancements**: Advanced memory and session management
|
||||
- Hybrid search with encryption for AI memory
|
||||
- Constitution-based AI behavior with GitHub integration
|
||||
- Entity graph migration to Neo4j backend
|
||||
- Session capture and transcript management
|
||||
- Memory chunking and context formatting
|
||||
- **Enhanced CI/CD Pipeline** (`b1d7bce`): Production-ready deployment
|
||||
- XGH_TOKEN authentication for openhumanxyz/openhuman releases
|
||||
- Python sidecar setup and caching for cross-platform builds
|
||||
- Tauri configuration updates (com.openhuman.app identifier)
|
||||
- GitHub Pages deployment with optimized workflows
|
||||
- Version tagging and environment variable management
|
||||
- **Device Detection & Download System** (`9d74721`, `b5bccd2`): Enhanced multi-architecture download support
|
||||
- Optimized asset parsing using Maps for unique architecture links per platform
|
||||
- Enhanced DownloadScreen.tsx with architecture-specific download options
|
||||
- Improved device detection for Windows, macOS, Linux, and Android platforms
|
||||
- Added preference logic for more specific filenames in asset parsing
|
||||
- Support for multiple architectures (x64, aarch64) with intelligent sorting
|
||||
- **Version Bump**: Project updated to v0.20.0 (`891517c`)
|
||||
|
||||
### Design System Updates
|
||||
|
||||
- **Settings Modal UI**: Clean 520px white modal contrasting with glass morphism theme
|
||||
- **Animations**: 200ms entry animations, 250ms panel transitions, chevron hover effects
|
||||
- **Lottie Animations**: Integrated into onboarding flow (`334673e`)
|
||||
- **Connection Components**: Added Telegram and Gmail connection indicators
|
||||
- **Routing**: Switched to HashRouter for better desktop app compatibility
|
||||
- **Theme**: Implemented sophisticated color system with premium crypto aesthetic
|
||||
|
||||
### Component Structure
|
||||
|
||||
- **200+ TypeScript files** across `src/` directory with comprehensive tooling
|
||||
- **AI System Architecture** (`src/lib/ai/`): Advanced artificial intelligence platform
|
||||
- Memory management with encryption, chunking, and hybrid search
|
||||
- Constitution-based behavior with GitHub integration
|
||||
- Entity graph with Neo4j backend integration
|
||||
- Session capture, transcript management, and tool compression
|
||||
- Provider system with OpenAI integration and custom providers
|
||||
- **Skills Management System**: Dynamic skill platform with Rust integration
|
||||
- SkillsGrid.tsx - Skills catalog with setup status and management
|
||||
- SkillSetupModal.tsx - Conditional wizard/management panel rendering
|
||||
- SkillProvider.tsx - GitHub sync and local directory integration
|
||||
- Skills submodule integration with background updates
|
||||
- **Team Collaboration Features**: Multi-user workspace management
|
||||
- TeamPanel.tsx - Team overview with member management
|
||||
- TeamMembersPanel.tsx - Member roles and permissions
|
||||
- TeamInvitesPanel.tsx - Invitation system with role assignment
|
||||
- Team API integration with Redux state management
|
||||
- **Settings Modal System**: Comprehensive configuration interface
|
||||
- SettingsModal.tsx - Main container with URL routing
|
||||
- SettingsLayout.tsx - Modal wrapper with createPortal
|
||||
- Enhanced panels: Billing, Team, Connections, Privacy, Profile
|
||||
- Hooks: useSettingsNavigation.ts, useSettingsAnimation.ts
|
||||
- **Download System**: Enhanced multi-platform distribution
|
||||
- DownloadScreen.tsx - Platform detection with architecture support
|
||||
- deviceDetection.ts - Comprehensive device/architecture utilities
|
||||
- GitHub API integration for real-time release assets
|
||||
- **Code Quality Infrastructure**: ESLint, Prettier, and Husky integration
|
||||
- Pre-commit/pre-push hooks with TypeScript compilation checks
|
||||
- Standardized type-only imports and consolidated statements
|
||||
- GitHub workflow integration with automated quality checks
|
||||
|
||||
## Git Workflow
|
||||
|
||||
- **Repository visibility**: The project is public.
|
||||
- **Push target**: Pushes should go to your working branch in the public repository (or your fork if your access model requires it).
|
||||
- **PR target**: Open pull requests against the public upstream repository, targeting the **`main`** branch.
|
||||
- **PR template required**: Use `.github/pull_request_template.md` for all pull requests.
|
||||
- **AI tooling rule**: Any AI-generated PR description must follow the template sections in order and keep all checklist items.
|
||||
|
||||
## Key Patterns
|
||||
|
||||
- **MANDATORY: Pre-completion checks**: Before considering ANY task complete, ALWAYS run these checks and fix all errors:
|
||||
1. `npx prettier --check .` (formatting)
|
||||
2. `npx eslint .` (lint)
|
||||
3. `npx tsc --noEmit` (TypeScript)
|
||||
4. `cargo fmt --manifest-path src-tauri/Cargo.toml` (Rust formatting, if Rust files were changed)
|
||||
5. `cargo check --manifest-path src-tauri/Cargo.toml` (Rust compilation, if Rust files were changed)
|
||||
- **Code Quality**: ESLint and Prettier enforce code standards with Husky hooks. Use type-only imports (`import type`) and consolidate imports from same modules.
|
||||
- **Frontend scope**: The frontend (`src/`) is primarily a REST API client to `rust-core/`; Rust code in `src-tauri/` manages the core process lifecycle and bridge surface.
|
||||
- **Core feature exposure**: Any new feature added in `rust-core/` must be exposed through both the CLI and a REST API so it can be integrated into the UI without backend rewrites.
|
||||
- **No dynamic imports**: All imports must be static `import` statements at the top of the file. Do not use `await import()` or `import().then()` inside functions or code blocks. Use try/catch around Tauri API calls for non-Tauri environments instead.
|
||||
- **No localStorage**: Avoid `localStorage` and `sessionStorage`; use Redux (and persist) for app state. Remove any direct usage when working on affected code.
|
||||
- **AI System Integration**: Use `src/lib/ai/` for memory management, constitution loading, entity queries, and session capture. AI providers abstracted through interface pattern.
|
||||
- **AI Configuration System**: OpenClaw-compliant AI configuration with dynamic TOOLS.md generation. Use `loadSoul()`, `loadTools()`, `loadAIConfig()` for configuration loading, and `injectAll()` for unified SOUL + TOOLS injection into user messages.
|
||||
- **V8 Skills Runtime**: Skills execute in V8 JavaScript engine on desktop platforms. Use `SkillProvider` for GitHub sync, `SkillsGrid` for management interface, and Rust runtime commands for lifecycle management. Platform filtering ensures skills only run on supported platforms.
|
||||
- **Team Collaboration**: Team features in `src/components/settings/panels/Team*`. Use Redux `teamSlice` for state management and `teamApi` for backend operations.
|
||||
- **Device Detection**: Use `deviceDetection.ts` utilities for platform/architecture detection. Support multiple architectures per platform (x64, aarch64) with intelligent preference logic.
|
||||
- **GitHub Integration**: Fetch release assets via GitHub API (`fetchLatestRelease()`) and parse by architecture (`parseReleaseAssetsByArchitecture()`). Use Maps for efficient unique architecture tracking.
|
||||
- **Download System**: Platform-specific file type support (.exe/.msi for Windows, .dmg for macOS, .AppImage/.deb/.rpm for Linux, .apk for Android) with fallback links.
|
||||
- **Modal System**: Settings modal uses `createPortal` pattern with URL-based routing. Clean white design (not glass morphism) for system settings. Navigate with `/settings` paths for different panels.
|
||||
- **Component Reuse**: Connection management reuses `connectOptions` array and components from onboarding flow. Maintains consistent UX patterns across features.
|
||||
- **Redux Integration**: Multiple slices (auth, user, telegram, ai, skills, team) with Redux Persist. Use typed hooks and selectors. State functions accept optional `userId` param.
|
||||
- **Node polyfills**: Vite config (`vite.config.ts`) polyfills `buffer`, `process`, `util`, `os`, `crypto`, `stream` for the `telegram` package which requires Node APIs.
|
||||
- **Telegram IDs**: Use `big-integer` library, not native JS numbers (Telegram IDs exceed `Number.MAX_SAFE_INTEGER`).
|
||||
- **MCP tool files**: Each tool in `src/lib/mcp/telegram/tools/` exports a handler conforming to `TelegramMCPToolHandler` interface. Tool names are typed in `src/lib/mcp/telegram/types.ts`.
|
||||
- **Tauri IPC**: Frontend calls Rust via `invoke()` from `@tauri-apps/api/core`. Rust commands are registered in `generate_handler![]` macro. Enhanced with runtime management commands for V8 skill execution and Socket.io integration.
|
||||
- **CORS workaround**: External HTTP requests from the WebView hit CORS. Use Rust `reqwest` via Tauri commands instead of browser `fetch()`.
|
||||
- **Hash Routing**: Uses HashRouter for desktop app compatibility and deep link handling.
|
||||
- **Integration Libraries**: Each integration (Telegram, future Gmail, etc.) lives under `src/lib/<integration>/` with its own `state/`, `services/`, `api/` subdirectories. Domain-specific services belong in the integration folder, not in `src/services/` (which holds only cross-cutting services like socketService, apiClient).
|
||||
- **Unit Tests**: All unit tests live in `__tests__/` folders co-located with the code they test. Use Jest with TypeScript support.
|
||||
- **Runtime Platform Differences**: V8 runtime is desktop-only. Mobile platforms use feature detection and graceful degradation. Skills with platform restrictions are filtered during discovery.
|
||||
- **Socket Management**: Rust-native Socket.io client provides persistent connections with automatic reconnection. Use `connect_to_socket` command instead of frontend-only socket connections for reliability.
|
||||
- **Dual Socket Codebase**: Socket event handling exists in **both** the TypeScript frontend (`src/services/socketService.ts`, `src/utils/tauriSocket.ts`) and the Rust backend (`src-tauri/src/runtime/socket_manager.rs`). **Any new socket event or protocol change must be implemented in both codebases.** The web frontend handles events directly via Socket.io; the Rust backend handles them over raw WebSocket with Engine.IO/Socket.IO framing. Example: `tool:sync` is emitted from both `src/lib/skills/sync.ts` (web mode) and `socket_manager.rs` (Rust mode, on connect + skill lifecycle changes).
|
||||
|
||||
## Platform Gotchas
|
||||
|
||||
- **macOS deep links**: Require `.app` bundle (not `tauri dev`). Clear WebKit caches when debugging stale content: `rm -rf ~/Library/WebKit/com.openhuman.app ~/Library/Caches/com.openhuman.app`
|
||||
- **Cargo caching**: May serve stale frontend assets on incremental builds. Run `cargo clean --manifest-path src-tauri/Cargo.toml` if the app shows outdated UI.
|
||||
- **`window.__TAURI__`**: Not available at module load time. Use static imports and try/catch around Tauri API calls (not around imports).
|
||||
- **Android background services**: RuntimeService requires notification permissions (API 33+) and foreground service type specification (API 34+). Use Android logging (`android_logger`) for debug output in logcat.
|
||||
- **V8 runtime limitations**: V8 engine is desktop-only. Android skills should use lightweight alternatives or server-side execution patterns.
|
||||
- **Socket connections**: Persistent Socket.io connections via Rust backend work better than WebView-based connections on mobile platforms.
|
||||
_Last aligned with monorepo layout (`app/` + root `src/`), QuickJS skills in `openhuman_core`, and Tauri shell IPC as of repo state._
|
||||
|
||||
@@ -98,7 +98,7 @@ Maintainers will review and may request changes. Once approved, your PR will be
|
||||
- **Imports**: Use static `import`/`import type` at the top of the file. No dynamic `import()` for app code; use try/catch around Tauri API calls in non-Tauri environments instead.
|
||||
- **Code style**: ESLint and Prettier are authoritative. Use type-only imports where appropriate and consolidate imports from the same module.
|
||||
- **Telegram IDs**: Use the `big-integer` library; do not rely on native JavaScript numbers for Telegram IDs.
|
||||
- **Tauri**: Commands are in Rust under `src-tauri`; frontend uses `invoke()` from `@tauri-apps/api/core`. Handle missing `window.__TAURI__` where the app can run outside Tauri.
|
||||
- **Tauri**: Commands are in Rust under `app/src-tauri`; frontend uses `invoke()` from `@tauri-apps/api/core`. Handle missing `window.__TAURI__` where the app can run outside Tauri. Install JS deps from the repo root (`yarn install`) so the `app` workspace is linked; most scripts are also available as `yarn <script>` from the root.
|
||||
- **Socket events**: Behavior exists in both the TypeScript frontend and the Rust backend. Any new socket event or protocol change must be implemented in both places.
|
||||
- **Skills**: Follow the V8 runtime and skill manifest rules; respect platform compatibility and the documented bridge/API surface.
|
||||
|
||||
|
||||
@@ -1,3 +1,110 @@
|
||||
[workspace]
|
||||
members = ["rust-core", "src-tauri"]
|
||||
resolver = "2"
|
||||
[package]
|
||||
name = "openhuman"
|
||||
version = "0.49.16"
|
||||
edition = "2021"
|
||||
description = "OpenHuman core business logic and RPC server"
|
||||
autobins = true
|
||||
|
||||
[lib]
|
||||
name = "openhuman_core"
|
||||
crate-type = ["rlib"]
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2.10", features = ["tray-icon"] }
|
||||
rquickjs = { version = "0.9", features = ["futures", "parallel"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
reqwest = { version = "0.12", default-features = false, features = ["json", "blocking", "rustls-tls", "native-tls", "stream", "http2", "multipart", "socks"] }
|
||||
tokio = { version = "1", features = ["full", "sync"] }
|
||||
once_cell = "1.19"
|
||||
parking_lot = "0.12"
|
||||
log = "0.4"
|
||||
env_logger = "0.11"
|
||||
base64 = "0.22"
|
||||
aes-gcm = "0.10"
|
||||
argon2 = "0.5"
|
||||
rand = "0.9"
|
||||
dirs = "5"
|
||||
sha2 = "0.10"
|
||||
hmac = "0.12"
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
anyhow = "1.0"
|
||||
async-trait = "0.1"
|
||||
chacha20poly1305 = "0.10"
|
||||
hex = "0.4"
|
||||
tokio-util = { version = "0.7", features = ["rt"] }
|
||||
tokio-tungstenite = { version = "0.24", features = ["rustls-tls-webpki-roots"] }
|
||||
futures = "0.3"
|
||||
rusqlite = { version = "0.37", features = ["bundled"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
cron = "0.12"
|
||||
futures-util = "0.3"
|
||||
directories = "6"
|
||||
toml = "1.0"
|
||||
shellexpand = "3.1"
|
||||
schemars = "1.2"
|
||||
tracing = { version = "0.1", default-features = false }
|
||||
tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt", "ansi", "env-filter"] }
|
||||
prometheus = { version = "0.14", default-features = false }
|
||||
urlencoding = "2.1"
|
||||
thiserror = "2.0"
|
||||
ring = "0.17"
|
||||
prost = { version = "0.14", default-features = false }
|
||||
postgres = { version = "0.19", features = ["with-chrono-0_4"] }
|
||||
chrono-tz = "0.10"
|
||||
dialoguer = { version = "0.12", features = ["fuzzy-select"] }
|
||||
console = "0.16"
|
||||
glob = "0.3"
|
||||
regex = "1.10"
|
||||
hostname = "0.4.2"
|
||||
rustls = { version = "0.23", features = ["ring"] }
|
||||
rustls-pki-types = "1.14.0"
|
||||
tokio-rustls = "0.26.4"
|
||||
webpki-roots = "1.0.6"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
clap_complete = "4.5"
|
||||
lettre = { version = "0.11.19", default-features = false, features = ["builder", "smtp-transport", "rustls-tls"] }
|
||||
mail-parser = "0.11.2"
|
||||
async-imap = { version = "0.11", features = ["runtime-tokio"], default-features = false }
|
||||
axum = { version = "0.8", default-features = false, features = ["http1", "json", "tokio", "query", "ws", "macros"] }
|
||||
tower = { version = "0.5", default-features = false }
|
||||
opentelemetry = { version = "0.31", default-features = false, features = ["trace", "metrics"] }
|
||||
opentelemetry_sdk = { version = "0.31", default-features = false, features = ["trace", "metrics"] }
|
||||
opentelemetry-otlp = { version = "0.31", default-features = false, features = ["trace", "metrics", "http-proto", "reqwest-client", "reqwest-rustls-webpki-roots"] }
|
||||
tokio-stream = { version = "0.1.18", features = ["full"] }
|
||||
url = "2"
|
||||
openssl = "0.10"
|
||||
|
||||
matrix-sdk = { version = "0.16", optional = true, default-features = false, features = ["e2e-encryption", "rustls-tls", "markdown"] }
|
||||
fantoccini = { version = "0.22.0", optional = true, default-features = false, features = ["rustls-tls"] }
|
||||
serde-big-array = { version = "0.5", optional = true }
|
||||
nusb = { version = "0.2", default-features = false, optional = true }
|
||||
tokio-serial = { version = "5", default-features = false, optional = true }
|
||||
probe-rs = { version = "0.30", optional = true }
|
||||
pdf-extract = { version = "0.10", optional = true }
|
||||
wa-rs = { version = "0.2", optional = true, default-features = false }
|
||||
wa-rs-core = { version = "0.2", optional = true, default-features = false }
|
||||
wa-rs-binary = { version = "0.2", optional = true, default-features = false }
|
||||
wa-rs-proto = { version = "0.2", optional = true, default-features = false }
|
||||
wa-rs-ureq-http = { version = "0.2", optional = true }
|
||||
wa-rs-tokio-transport = { version = "0.2", optional = true, default-features = false }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
landlock = { version = "0.4", optional = true }
|
||||
rppal = { version = "0.22", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
||||
[features]
|
||||
sandbox-landlock = ["dep:landlock"]
|
||||
sandbox-bubblewrap = []
|
||||
hardware = ["dep:nusb", "dep:tokio-serial"]
|
||||
channel-matrix = ["dep:matrix-sdk"]
|
||||
peripheral-rpi = ["dep:rppal"]
|
||||
browser-native = ["dep:fantoccini"]
|
||||
fantoccini = ["browser-native"]
|
||||
landlock = ["sandbox-landlock"]
|
||||
probe = ["dep:probe-rs"]
|
||||
rag-pdf = ["dep:pdf-extract"]
|
||||
whatsapp-web = ["dep:wa-rs", "dep:wa-rs-core", "dep:wa-rs-binary", "dep:wa-rs-proto", "dep:wa-rs-ureq-http", "dep:wa-rs-tokio-transport", "serde-big-array"]
|
||||
|
||||
@@ -1,26 +1,114 @@
|
||||
{
|
||||
"name": "openhuman",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"name": "openhuman-app",
|
||||
"version": "0.49.16",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"dev:web": "vite",
|
||||
"dev:app": "source ../scripts/load-dotenv.sh && tauri dev",
|
||||
"core:stage": "node ../scripts/stage-core-sidecar.mjs",
|
||||
"build": "tsc && vite build",
|
||||
"build:app": "yarn skills:build && tsc && vite build",
|
||||
"compile": "tsc --noEmit",
|
||||
"preview": "vite preview",
|
||||
"tauri": "tauri"
|
||||
"tauri": "tauri",
|
||||
"tauri:build:ui": "tauri build -- --bin OpenHuman",
|
||||
"macos:build:intel": "source ../scripts/load-dotenv.sh && tauri build --bundles app dmg --target x86_64-apple-darwin -- --bin OpenHuman",
|
||||
"macos:build:intel:debug": "yarn macos:build:intel --debug",
|
||||
"macos:build:debug": "yarn macos:build:release --debug",
|
||||
"macos:build:release": "source ../scripts/load-dotenv.sh && tauri build --bundles app dmg -- --bin OpenHuman",
|
||||
"macos:build:release:signed": "source ../scripts/load-env.sh && tauri build --bundles app dmg -- --bin OpenHuman",
|
||||
"macos:build:sign:release": "yarn macos:build:release:signed",
|
||||
"macos:run": "open '../target/debug/bundle/macos/OpenHuman.app'",
|
||||
"macos:dev": "yarn macos:build:debug && open '../target/debug/bundle/macos/OpenHuman.app'",
|
||||
"test": "vitest run --config test/vitest.config.ts",
|
||||
"test:unit": "vitest run --config test/vitest.config.ts",
|
||||
"test:unit:watch": "vitest --config test/vitest.config.ts",
|
||||
"test:watch": "vitest --config test/vitest.config.ts",
|
||||
"test:coverage": "vitest run --config test/vitest.config.ts --coverage",
|
||||
"test:rust": "source $HOME/.cargo/env 2>/dev/null; cargo test --manifest-path ../Cargo.toml --workspace",
|
||||
"test:e2e:build": "bash ../scripts/e2e-build.sh",
|
||||
"test:e2e:login": "bash ../scripts/e2e-login.sh",
|
||||
"test:e2e:auth": "bash ../scripts/e2e-auth.sh",
|
||||
"test:e2e": "yarn test:e2e:build && yarn test:e2e:login && yarn test:e2e:auth",
|
||||
"test:e2e:all:flows": "bash ../scripts/e2e-run-all-flows.sh",
|
||||
"test:e2e:all": "yarn test:e2e:build && yarn test:e2e:all:flows",
|
||||
"test:all": "yarn test:coverage && yarn test:rust && yarn test:e2e",
|
||||
"format": "prettier --write . && cargo fmt --manifest-path ../Cargo.toml --all",
|
||||
"format:check": "prettier --check . && cargo fmt --manifest-path ../Cargo.toml --all --check",
|
||||
"lint": "eslint . --ext .ts,.tsx",
|
||||
"lint:fix": "eslint . --ext .ts,.tsx --fix",
|
||||
"skills:build": "cd skills && yarn build",
|
||||
"skills:watch": "cd skills && yarn build:watch",
|
||||
"prepare": "husky"
|
||||
},
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^2.2.0",
|
||||
"@noble/hashes": "^2.0.1",
|
||||
"@noble/secp256k1": "^3.0.0",
|
||||
"@reduxjs/toolkit": "^2.11.2",
|
||||
"@scure/bip32": "^2.0.1",
|
||||
"@scure/bip39": "^2.0.1",
|
||||
"@sentry/react": "^10.38.0",
|
||||
"@tauri-apps/api": "^2.10.0",
|
||||
"@tauri-apps/plugin-deep-link": "^2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"@tauri-apps/plugin-os": "^2.3.2",
|
||||
"@types/three": "^0.183.1",
|
||||
"buffer": "^6.0.3",
|
||||
"debug": "^4.4.3",
|
||||
"lottie-react": "^2.4.1",
|
||||
"os-browserify": "^0.3.0",
|
||||
"process": "^0.11.10",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-opener": "^2"
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router-dom": "^7.13.0",
|
||||
"redux-logger": "^3.0.6",
|
||||
"redux-persist": "^6.0.0",
|
||||
"socket.io-client": "^4.8.3",
|
||||
"three": "^0.183.2",
|
||||
"util": "^0.12.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@tailwindcss/forms": "^0.5.11",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tauri-apps/cli": "2.10.0",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
||||
"@types/debug": "^4.1.12",
|
||||
"@types/node": "^25.0.10",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@types/redux-logger": "^3.0.13",
|
||||
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^8.54.0",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
"@wdio/appium-service": "^9.24.0",
|
||||
"@wdio/cli": "^9.24.0",
|
||||
"@wdio/local-runner": "^9.24.0",
|
||||
"@wdio/mocha-framework": "^9.24.0",
|
||||
"@wdio/spec-reporter": "^9.24.0",
|
||||
"autoprefixer": "^10.4.23",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"husky": "^9.1.7",
|
||||
"jsdom": "^28.0.0",
|
||||
"msw": "^2.12.10",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.8.1",
|
||||
"tailwindcss": "^3.4.19",
|
||||
"typescript": "~5.8.3",
|
||||
"vite": "^7.0.4",
|
||||
"@tauri-apps/cli": "^2"
|
||||
"vite-plugin-node-polyfills": "^0.25.0",
|
||||
"vitest": "^4.0.18"
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 201 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 266 KiB After Width: | Height: | Size: 266 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
@@ -1,6 +1,7 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
/binaries/
|
||||
|
||||
# Generated by Tauri
|
||||
# will have schema files for capabilities auto-completion
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
[package]
|
||||
name = "openhuman"
|
||||
version = "0.1.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
name = "OpenHuman"
|
||||
version = "0.49.16"
|
||||
description = "OpenHuman - AI-powered Super Assistant"
|
||||
authors = ["OpenHuman"]
|
||||
edition = "2021"
|
||||
default-run = "OpenHuman"
|
||||
autobins = false
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -11,15 +13,162 @@ edition = "2021"
|
||||
# The `_lib` suffix may seem redundant but it is necessary
|
||||
# to make the lib name unique and wouldn't conflict with the bin name.
|
||||
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
|
||||
name = "openhuman_lib"
|
||||
name = "openhuman"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[[bin]]
|
||||
name = "OpenHuman"
|
||||
path = "src/main.rs"
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2", features = [] }
|
||||
tauri-plugin-opener = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
|
||||
[dependencies]
|
||||
# Tauri core and plugins
|
||||
tauri = { version = "2.10", features = ["macos-private-api"] }
|
||||
tauri-plugin-opener = "2"
|
||||
tauri-plugin-deep-link = "2.0.0"
|
||||
tauri-plugin-os = "2"
|
||||
# Desktop host plugins
|
||||
# Serialization
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
|
||||
# HTTP client: rustls + native-tls for desktop skill HTTP compatibility
|
||||
# (some APIs/CDNs use TLS configs that rustls can't negotiate — native-tls uses OS TLS stack)
|
||||
reqwest = { version = "0.12", default-features = false, features = ["json", "blocking", "rustls-tls", "native-tls", "stream", "http2", "multipart", "socks"] }
|
||||
|
||||
# Async runtime
|
||||
tokio = { version = "1", features = ["full", "sync"] }
|
||||
|
||||
# Keyring is used for desktop credential storage.
|
||||
|
||||
# Concurrency utilities
|
||||
once_cell = "1.19"
|
||||
parking_lot = "0.12"
|
||||
|
||||
# Logging
|
||||
log = "0.4"
|
||||
env_logger = "0.11"
|
||||
|
||||
# AI module dependencies
|
||||
base64 = "0.22"
|
||||
aes-gcm = "0.10"
|
||||
argon2 = "0.5"
|
||||
rand = "0.9"
|
||||
dirs = "5"
|
||||
sha2 = "0.10"
|
||||
hmac = "0.12"
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
|
||||
# OpenHuman agent runtime dependencies
|
||||
anyhow = "1.0"
|
||||
async-trait = "0.1"
|
||||
chacha20poly1305 = "0.10"
|
||||
hex = "0.4"
|
||||
tokio-util = { version = "0.7", features = ["rt"] }
|
||||
|
||||
# V8 JavaScript runtime is desktop-only in this host.
|
||||
|
||||
tokio-tungstenite = { version = "0.24", features = ["rustls-tls-webpki-roots"] }
|
||||
|
||||
# HTTP streaming support for fetch polyfill
|
||||
futures = "0.3"
|
||||
|
||||
# Embedded SQLite for skill databases
|
||||
rusqlite = { version = "0.37", features = ["bundled"] }
|
||||
|
||||
# Date/time utilities
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
|
||||
# Cron expression parsing for scheduled skill tasks
|
||||
cron = "0.12"
|
||||
|
||||
# Stream/Sink utilities for WebSocket and HTTP streaming
|
||||
futures-util = "0.3"
|
||||
|
||||
# Socket manager is persistent in the desktop host.
|
||||
|
||||
# OpenHuman config + observability
|
||||
directories = "6"
|
||||
toml = "1.0"
|
||||
shellexpand = "3.1"
|
||||
schemars = "1.2"
|
||||
tracing = { version = "0.1", default-features = false }
|
||||
tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt", "ansi", "env-filter"] }
|
||||
prometheus = { version = "0.14", default-features = false }
|
||||
urlencoding = "2.1"
|
||||
thiserror = "2.0"
|
||||
ring = "0.17"
|
||||
prost = { version = "0.14", default-features = false }
|
||||
postgres = { version = "0.19", features = ["with-chrono-0_4"] }
|
||||
chrono-tz = "0.10"
|
||||
dialoguer = { version = "0.12", features = ["fuzzy-select"] }
|
||||
console = "0.16"
|
||||
glob = "0.3"
|
||||
regex = "1.10"
|
||||
hostname = "0.4.2"
|
||||
rustls = { version = "0.23", features = ["ring"] }
|
||||
rustls-pki-types = "1.14.0"
|
||||
tokio-rustls = "0.26.4"
|
||||
webpki-roots = "1.0.6"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
lettre = { version = "0.11.19", default-features = false, features = ["builder", "smtp-transport", "rustls-tls"] }
|
||||
mail-parser = "0.11.2"
|
||||
async-imap = { version = "0.11", features = ["runtime-tokio"], default-features = false }
|
||||
axum = { version = "0.8", default-features = false, features = ["http1", "json", "tokio", "query", "ws", "macros"] }
|
||||
tower = { version = "0.5", default-features = false }
|
||||
tower-http = { version = "0.6", default-features = false, features = ["limit", "timeout"] }
|
||||
http-body-util = "0.1"
|
||||
opentelemetry = { version = "0.31", default-features = false, features = ["trace", "metrics"] }
|
||||
opentelemetry_sdk = { version = "0.31", default-features = false, features = ["trace", "metrics"] }
|
||||
opentelemetry-otlp = { version = "0.31", default-features = false, features = ["trace", "metrics", "http-proto", "reqwest-client", "reqwest-rustls-webpki-roots"] }
|
||||
tokio-stream = { version = "0.1.18", features = ["full"] }
|
||||
url = "2"
|
||||
|
||||
# Optional integrations
|
||||
matrix-sdk = { version = "0.16", optional = true, default-features = false, features = ["e2e-encryption", "rustls-tls", "markdown"] }
|
||||
fantoccini = { version = "0.22.0", optional = true, default-features = false, features = ["rustls-tls"] }
|
||||
serde-big-array = { version = "0.5", optional = true }
|
||||
nusb = { version = "0.2", default-features = false, optional = true }
|
||||
tokio-serial = { version = "5", default-features = false, optional = true }
|
||||
probe-rs = { version = "0.30", optional = true }
|
||||
pdf-extract = { version = "0.10", optional = true }
|
||||
wa-rs = { version = "0.2", optional = true, default-features = false }
|
||||
wa-rs-core = { version = "0.2", optional = true, default-features = false }
|
||||
wa-rs-binary = { version = "0.2", optional = true, default-features = false }
|
||||
wa-rs-proto = { version = "0.2", optional = true, default-features = false }
|
||||
wa-rs-ureq-http = { version = "0.2", optional = true }
|
||||
wa-rs-tokio-transport = { version = "0.2", optional = true, default-features = false }
|
||||
keyring = "3"
|
||||
tauri-plugin-autostart = "2"
|
||||
tauri-plugin-notification = "2"
|
||||
# QuickJS JavaScript runtime (desktop-only host)
|
||||
rquickjs = { version = "0.11", features = ["futures", "macro", "loader", "parallel"] }
|
||||
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
landlock = { version = "0.4", optional = true }
|
||||
rppal = { version = "0.22", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
||||
[features]
|
||||
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
sandbox-landlock = ["dep:landlock"]
|
||||
sandbox-bubblewrap = []
|
||||
|
||||
# OpenHuman feature flags
|
||||
hardware = ["dep:nusb", "dep:tokio-serial"]
|
||||
channel-matrix = ["dep:matrix-sdk"]
|
||||
peripheral-rpi = ["dep:rppal"]
|
||||
browser-native = ["dep:fantoccini"]
|
||||
fantoccini = ["browser-native"]
|
||||
landlock = ["sandbox-landlock"]
|
||||
probe = ["dep:probe-rs"]
|
||||
rag-pdf = ["dep:pdf-extract"]
|
||||
whatsapp-web = ["dep:wa-rs", "dep:wa-rs-core", "dep:wa-rs-binary", "dep:wa-rs-proto", "dep:wa-rs-ureq-http", "dep:wa-rs-tokio-transport", "serde-big-array"]
|
||||
|
||||
@@ -1,3 +1,34 @@
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
maybe_override_tauri_config_for_local_builds();
|
||||
tauri_build::build();
|
||||
}
|
||||
|
||||
fn maybe_override_tauri_config_for_local_builds() {
|
||||
let profile = env::var("PROFILE").unwrap_or_default();
|
||||
let skip_resources = env::var("TAURI_SKIP_RESOURCES").is_ok() || profile != "release";
|
||||
|
||||
if !skip_resources {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut merge_config = serde_json::json!({});
|
||||
if skip_resources {
|
||||
merge_config["bundle"]["resources"] = serde_json::json!([]);
|
||||
// Keep sidecars enabled for local/debug builds so the desktop host can
|
||||
// exercise the same core process launch path as packaged builds.
|
||||
}
|
||||
|
||||
match serde_json::to_string(&merge_config) {
|
||||
Ok(json) => {
|
||||
env::set_var("TAURI_CONFIG", json);
|
||||
if skip_resources {
|
||||
println!("cargo:warning=TAURI resources disabled for local build");
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
println!("cargo:warning=Failed to serialize TAURI_CONFIG override: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "Capability for the main window",
|
||||
"description": "Capability for the main window (desktop only)",
|
||||
"platforms": ["linux", "macOS", "windows"],
|
||||
"windows": ["main"],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"opener:default"
|
||||
"opener:default",
|
||||
"deep-link:default",
|
||||
"os:default",
|
||||
"autostart:default",
|
||||
"autostart:allow-enable",
|
||||
"autostart:allow-disable",
|
||||
"autostart:allow-is-enabled",
|
||||
"notification:default",
|
||||
"notification:allow-notify",
|
||||
"notification:allow-request-permission",
|
||||
"notification:allow-is-permission-granted"
|
||||
]
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 974 B After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 903 B After Width: | Height: | Size: 998 B |
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 275 KiB After Width: | Height: | Size: 275 KiB |
@@ -0,0 +1,81 @@
|
||||
use crate::core_process::CoreProcessHandle;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use tauri::Manager;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CoreRpcRelayRequest {
|
||||
pub method: String,
|
||||
#[serde(default)]
|
||||
pub params: Value,
|
||||
#[serde(default)]
|
||||
pub service_managed: bool,
|
||||
}
|
||||
|
||||
async fn invoke_core_cli_call(method: &str, params: Value) -> Result<(), String> {
|
||||
let core_bin = crate::core_process::default_core_bin()
|
||||
.ok_or_else(|| "openhuman core binary not found".to_string())?;
|
||||
let params_json =
|
||||
serde_json::to_string(¶ms).map_err(|e| format!("failed to serialize params: {e}"))?;
|
||||
|
||||
let output = tokio::process::Command::new(core_bin)
|
||||
.arg("call")
|
||||
.arg("--method")
|
||||
.arg(method)
|
||||
.arg("--params")
|
||||
.arg(params_json)
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| format!("failed to run core cli call {method}: {e}"))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
return Err(format!(
|
||||
"core cli call {method} failed with status {}. stdout: {} stderr: {}",
|
||||
output.status, stdout, stderr
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn ensure_service_managed_core_running() -> Result<(), String> {
|
||||
if crate::core_rpc::ping().await {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let _ = invoke_core_cli_call("openhuman.service_install", serde_json::json!({})).await;
|
||||
let _ = invoke_core_cli_call("openhuman.service_start", serde_json::json!({})).await;
|
||||
|
||||
for _ in 0..40 {
|
||||
if crate::core_rpc::ping().await {
|
||||
return Ok(());
|
||||
}
|
||||
tokio::time::sleep(std::time::Duration::from_millis(250)).await;
|
||||
}
|
||||
|
||||
Err(
|
||||
"OpenHuman Core daemon did not become ready. Confirm the background service is running."
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn core_rpc_relay(
|
||||
app: tauri::AppHandle,
|
||||
request: CoreRpcRelayRequest,
|
||||
) -> Result<Value, String> {
|
||||
if request.service_managed {
|
||||
ensure_service_managed_core_running().await?;
|
||||
} else {
|
||||
let core = app
|
||||
.try_state::<CoreProcessHandle>()
|
||||
.ok_or_else(|| "core process handle is not available".to_string())?;
|
||||
let handle: CoreProcessHandle = (*core).clone();
|
||||
handle.ensure_running().await?;
|
||||
}
|
||||
|
||||
crate::core_rpc::call::<Value>(&request.method, request.params).await
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
pub mod core_relay;
|
||||
pub mod openhuman;
|
||||
|
||||
#[cfg(desktop)]
|
||||
pub mod window;
|
||||
|
||||
pub use core_relay::*;
|
||||
pub use openhuman::*;
|
||||
|
||||
#[cfg(desktop)]
|
||||
pub use window::*;
|
||||
@@ -0,0 +1,129 @@
|
||||
use crate::core_process::CoreProcessHandle;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
const DAEMON_HOST_CONFIG_FILE: &str = "daemon_host_config.json";
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DaemonHostConfig {
|
||||
pub show_tray: bool,
|
||||
}
|
||||
|
||||
impl Default for DaemonHostConfig {
|
||||
fn default() -> Self {
|
||||
Self { show_tray: true }
|
||||
}
|
||||
}
|
||||
|
||||
fn daemon_host_config_path(app: &AppHandle) -> PathBuf {
|
||||
app.path()
|
||||
.app_data_dir()
|
||||
.unwrap_or_else(|_| {
|
||||
dirs::home_dir()
|
||||
.unwrap_or_else(|| PathBuf::from("."))
|
||||
.join(".openhuman")
|
||||
})
|
||||
.join(DAEMON_HOST_CONFIG_FILE)
|
||||
}
|
||||
|
||||
async fn load_daemon_host_config(app: &AppHandle) -> DaemonHostConfig {
|
||||
let path = daemon_host_config_path(app);
|
||||
let Ok(contents) = tokio::fs::read_to_string(path).await else {
|
||||
return DaemonHostConfig::default();
|
||||
};
|
||||
serde_json::from_str::<DaemonHostConfig>(&contents).unwrap_or_default()
|
||||
}
|
||||
|
||||
async fn save_daemon_host_config(app: &AppHandle, config: &DaemonHostConfig) -> Result<(), String> {
|
||||
let path = daemon_host_config_path(app);
|
||||
if let Some(parent) = path.parent() {
|
||||
tokio::fs::create_dir_all(parent)
|
||||
.await
|
||||
.map_err(|e| format!("failed to create daemon host config directory: {e}"))?;
|
||||
}
|
||||
let bytes = serde_json::to_vec_pretty(config)
|
||||
.map_err(|e| format!("failed to serialize daemon host config: {e}"))?;
|
||||
tokio::fs::write(path, bytes)
|
||||
.await
|
||||
.map_err(|e| format!("failed to write daemon host config: {e}"))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum ServiceState {
|
||||
Running,
|
||||
Stopped,
|
||||
NotInstalled,
|
||||
Unknown(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServiceStatus {
|
||||
pub state: ServiceState,
|
||||
pub unit_path: Option<std::path::PathBuf>,
|
||||
pub label: String,
|
||||
pub details: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct RpcCommandResponse<T> {
|
||||
result: T,
|
||||
}
|
||||
|
||||
async fn ensure_core_running(app: &AppHandle) -> Result<(), String> {
|
||||
let core = app
|
||||
.try_state::<CoreProcessHandle>()
|
||||
.ok_or_else(|| "core process handle is not available".to_string())?;
|
||||
let handle: CoreProcessHandle = (*core).clone();
|
||||
handle.ensure_running().await
|
||||
}
|
||||
|
||||
async fn call_service_method(app: &AppHandle, method: &str) -> Result<ServiceStatus, String> {
|
||||
ensure_core_running(app).await?;
|
||||
let response =
|
||||
crate::core_rpc::call::<RpcCommandResponse<ServiceStatus>>(method, serde_json::json!({}))
|
||||
.await?;
|
||||
Ok(response.result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn openhuman_get_daemon_host_config(app: AppHandle) -> Result<DaemonHostConfig, String> {
|
||||
Ok(load_daemon_host_config(&app).await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn openhuman_set_daemon_host_config(
|
||||
app: AppHandle,
|
||||
show_tray: bool,
|
||||
) -> Result<DaemonHostConfig, String> {
|
||||
let mut cfg = load_daemon_host_config(&app).await;
|
||||
cfg.show_tray = show_tray;
|
||||
save_daemon_host_config(&app, &cfg).await?;
|
||||
Ok(cfg)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn openhuman_service_install(app: AppHandle) -> Result<ServiceStatus, String> {
|
||||
call_service_method(&app, "openhuman.service_install").await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn openhuman_service_start(app: AppHandle) -> Result<ServiceStatus, String> {
|
||||
call_service_method(&app, "openhuman.service_start").await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn openhuman_service_stop(app: AppHandle) -> Result<ServiceStatus, String> {
|
||||
call_service_method(&app, "openhuman.service_stop").await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn openhuman_service_status(app: AppHandle) -> Result<ServiceStatus, String> {
|
||||
call_service_method(&app, "openhuman.service_status").await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn openhuman_service_uninstall(app: AppHandle) -> Result<ServiceStatus, String> {
|
||||
call_service_method(&app, "openhuman.service_uninstall").await
|
||||
}
|
||||
@@ -46,19 +46,32 @@ impl CoreProcessHandle {
|
||||
|
||||
match self.run_mode {
|
||||
CoreRunMode::InProcess => {
|
||||
let mut guard = self.task.lock().await;
|
||||
log::warn!(
|
||||
"[core] in-process core mode is unavailable in host-only build; falling back to child process"
|
||||
);
|
||||
let mut guard = self.child.lock().await;
|
||||
if guard.is_none() {
|
||||
let port = self.port;
|
||||
log::info!("[core] launching in-process core server on port {}", port);
|
||||
let task = tokio::spawn(async move {
|
||||
if let Err(err) = openhuman_core::core_server::run_server(Some(port)).await
|
||||
{
|
||||
log::error!("[core] in-process core server exited with error: {err}");
|
||||
} else {
|
||||
log::warn!("[core] in-process core server exited");
|
||||
let mut cmd = if let Some(core_bin) = &self.core_bin {
|
||||
let mut cmd = Command::new(core_bin);
|
||||
if is_current_exe_path(core_bin) {
|
||||
cmd.arg("core");
|
||||
}
|
||||
});
|
||||
*guard = Some(task);
|
||||
cmd.arg("run").arg("--port").arg(self.port.to_string());
|
||||
cmd
|
||||
} else {
|
||||
let exe = std::env::current_exe()
|
||||
.map_err(|e| format!("failed to resolve current executable: {e}"))?;
|
||||
let mut cmd = Command::new(exe);
|
||||
cmd.arg("core")
|
||||
.arg("run")
|
||||
.arg("--port")
|
||||
.arg(self.port.to_string());
|
||||
cmd
|
||||
};
|
||||
let child = cmd
|
||||
.spawn()
|
||||
.map_err(|e| format!("failed to spawn core process: {e}"))?;
|
||||
*guard = Some(child);
|
||||
}
|
||||
}
|
||||
CoreRunMode::ChildProcess => {
|
||||
@@ -150,21 +163,6 @@ impl CoreProcessHandle {
|
||||
|
||||
Err("core process did not become ready".to_string())
|
||||
}
|
||||
|
||||
pub async fn shutdown(&self) {
|
||||
let mut child_guard = self.child.lock().await;
|
||||
if let Some(child) = child_guard.as_mut() {
|
||||
let _ = child.kill().await;
|
||||
}
|
||||
*child_guard = None;
|
||||
drop(child_guard);
|
||||
|
||||
let mut task_guard = self.task.lock().await;
|
||||
if let Some(task) = task_guard.take() {
|
||||
task.abort();
|
||||
let _ = task.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_current_exe_path(candidate: &std::path::Path) -> bool {
|
||||
@@ -217,10 +215,29 @@ pub fn default_core_bin() -> Option<PathBuf> {
|
||||
}
|
||||
}
|
||||
|
||||
// Dev ergonomics: in debug builds, prefer spawning this same executable with
|
||||
// `core run` so Cargo recompiles core logic changes as part of tauri dev.
|
||||
// Sidecar discovery remains enabled for packaged/release builds.
|
||||
// Dev ergonomics: allow an explicit staged sidecar from src-tauri/binaries in
|
||||
// debug builds before falling back to self-subcommand spawning.
|
||||
if cfg!(debug_assertions) {
|
||||
let binaries_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("binaries");
|
||||
if let Ok(entries) = std::fs::read_dir(&binaries_dir) {
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if !path.is_file() {
|
||||
continue;
|
||||
}
|
||||
let Some(file_name) = path.file_name().and_then(|n| n.to_str()) else {
|
||||
continue;
|
||||
};
|
||||
#[cfg(windows)]
|
||||
let matches = file_name.starts_with("openhuman-") && file_name.ends_with(".exe");
|
||||
#[cfg(not(windows))]
|
||||
let matches = file_name.starts_with("openhuman-");
|
||||
if matches {
|
||||
return Some(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -237,9 +254,9 @@ pub fn default_core_bin() -> Option<PathBuf> {
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
let legacy_standalone = exe_dir.join("openhuman-core.exe");
|
||||
let legacy_standalone = exe_dir.join("openhuman.exe");
|
||||
#[cfg(not(windows))]
|
||||
let legacy_standalone = exe_dir.join("openhuman-core");
|
||||
let legacy_standalone = exe_dir.join("openhuman");
|
||||
|
||||
if legacy_standalone.exists() && !same_executable_path(&legacy_standalone, &exe) {
|
||||
return Some(legacy_standalone);
|
||||
@@ -270,10 +287,10 @@ pub fn default_core_bin() -> Option<PathBuf> {
|
||||
|
||||
#[cfg(windows)]
|
||||
let matches = (file_name.starts_with("openhuman-") && file_name.ends_with(".exe"))
|
||||
|| (file_name.starts_with("openhuman-core-") && file_name.ends_with(".exe"));
|
||||
|| (file_name.starts_with("openhuman-") && file_name.ends_with(".exe"));
|
||||
#[cfg(not(windows))]
|
||||
let matches =
|
||||
file_name.starts_with("openhuman-") || file_name.starts_with("openhuman-core-");
|
||||
file_name.starts_with("openhuman-") || file_name.starts_with("openhuman-");
|
||||
|
||||
if matches && !same_executable_path(&path, &exe) {
|
||||
return Some(path);
|
||||
@@ -1,14 +1,656 @@
|
||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
||||
//! OpenHuman Desktop Application
|
||||
//!
|
||||
//! This is the Rust backend for the cross-platform crypto community platform.
|
||||
//! It provides deep link handling, core process RPC relay, window management,
|
||||
//! and AI configuration helpers.
|
||||
|
||||
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
|
||||
compile_error!("src-tauri host is desktop-only. Non-desktop targets are not supported.");
|
||||
|
||||
mod commands;
|
||||
mod core_process;
|
||||
mod core_rpc;
|
||||
mod utils;
|
||||
|
||||
use aes_gcm::aead::{Aead, KeyInit};
|
||||
use aes_gcm::{Aes256Gcm, Key, Nonce};
|
||||
use base64::{engine::general_purpose::STANDARD as B64, Engine as _};
|
||||
use commands::*;
|
||||
use rand::TryRngCore;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use tauri::{AppHandle, Emitter, Manager, RunEvent};
|
||||
use tokio::{
|
||||
fs,
|
||||
time::{interval, Duration},
|
||||
};
|
||||
|
||||
#[cfg(any(windows, target_os = "linux"))]
|
||||
use tauri_plugin_deep_link::DeepLinkExt;
|
||||
|
||||
/// Demo command - can be removed in production
|
||||
#[tauri::command]
|
||||
fn greet(name: &str) -> String {
|
||||
format!("Hello, {}! You've been greeted from Rust!", name)
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
fn derive_key(password: &str) -> [u8; 32] {
|
||||
use sha2::{Digest, Sha256};
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(password.as_bytes());
|
||||
let hash = hasher.finalize();
|
||||
let mut key = [0u8; 32];
|
||||
key.copy_from_slice(&hash[..32]);
|
||||
key
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct AIPreview {
|
||||
soul: AIPreviewSoul,
|
||||
tools: AIPreviewTools,
|
||||
metadata: AIPreviewMetadata,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct AIPreviewSoul {
|
||||
raw: String,
|
||||
name: String,
|
||||
description: String,
|
||||
personality_preview: Vec<String>,
|
||||
safety_rules_preview: Vec<String>,
|
||||
loaded_at: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct AIPreviewTools {
|
||||
raw: String,
|
||||
total_tools: usize,
|
||||
active_skills: usize,
|
||||
skills_preview: Vec<String>,
|
||||
loaded_at: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct AIPreviewMetadata {
|
||||
loaded_at: i64,
|
||||
loading_duration: i64,
|
||||
has_fallbacks: bool,
|
||||
sources: AIPreviewSources,
|
||||
errors: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct AIPreviewSources {
|
||||
soul: String,
|
||||
tools: String,
|
||||
}
|
||||
|
||||
fn now_ms() -> i64 {
|
||||
chrono::Utc::now().timestamp_millis()
|
||||
}
|
||||
|
||||
fn extract_section(raw: &str, heading: &str) -> String {
|
||||
let marker = format!("## {heading}");
|
||||
let Some(start) = raw.find(&marker) else {
|
||||
return String::new();
|
||||
};
|
||||
let body = &raw[start + marker.len()..];
|
||||
if let Some(next_idx) = body.find("\n## ") {
|
||||
body[..next_idx].trim().to_string()
|
||||
} else {
|
||||
body.trim().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_soul_preview(raw: String, loaded_at: i64) -> AIPreviewSoul {
|
||||
let name = raw
|
||||
.lines()
|
||||
.find_map(|line| line.strip_prefix("# ").map(|s| s.trim().to_string()))
|
||||
.unwrap_or_else(|| "OpenHuman".to_string());
|
||||
|
||||
let description = raw
|
||||
.lines()
|
||||
.map(str::trim)
|
||||
.find(|line| !line.is_empty() && !line.starts_with('#'))
|
||||
.unwrap_or("AI assistant")
|
||||
.to_string();
|
||||
|
||||
let personality_preview = extract_section(&raw, "Personality")
|
||||
.lines()
|
||||
.filter_map(|line| line.trim().strip_prefix("- **"))
|
||||
.filter_map(|line| {
|
||||
let mut parts = line.splitn(2, "**:");
|
||||
let trait_name = parts.next()?.trim();
|
||||
let detail = parts.next().unwrap_or("").trim();
|
||||
Some(format!("{trait_name}: {detail}"))
|
||||
})
|
||||
.take(3)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let safety_rules_preview = extract_section(&raw, "Safety Rules")
|
||||
.lines()
|
||||
.filter_map(|line| {
|
||||
let trimmed = line.trim();
|
||||
let dot_idx = trimmed.find('.')?;
|
||||
let (prefix, rest) = trimmed.split_at(dot_idx);
|
||||
if prefix.chars().all(|c| c.is_ascii_digit()) {
|
||||
Some(rest.trim_start_matches('.').trim().to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.take(3)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
AIPreviewSoul {
|
||||
raw,
|
||||
name,
|
||||
description,
|
||||
personality_preview,
|
||||
safety_rules_preview,
|
||||
loaded_at,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_tools_preview(raw: String, loaded_at: i64) -> AIPreviewTools {
|
||||
let mut current_skill = "General".to_string();
|
||||
let mut skill_counts: HashMap<String, usize> = HashMap::new();
|
||||
let mut total_tools = 0usize;
|
||||
|
||||
for line in raw.lines() {
|
||||
let trimmed = line.trim();
|
||||
if let Some(title) = trimmed.strip_prefix("### ") {
|
||||
if let Some(skill_title) = title.strip_suffix(" Tools") {
|
||||
current_skill = skill_title.trim().to_string();
|
||||
skill_counts.entry(current_skill.clone()).or_insert(0);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if trimmed.starts_with("#### ") {
|
||||
total_tools += 1;
|
||||
*skill_counts.entry(current_skill.clone()).or_insert(0) += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let mut skills = skill_counts.into_iter().collect::<Vec<_>>();
|
||||
let active_skills = skills.len();
|
||||
skills.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
|
||||
let skills_preview = skills
|
||||
.into_iter()
|
||||
.take(6)
|
||||
.map(|(name, count)| format!("{name} ({count})"))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
AIPreviewTools {
|
||||
raw,
|
||||
total_tools,
|
||||
active_skills,
|
||||
skills_preview,
|
||||
loaded_at,
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_ai_directory(app: &tauri::AppHandle) -> Option<(PathBuf, &'static str)> {
|
||||
if let Ok(resource_dir) = app.path().resource_dir() {
|
||||
if let Some(ai_dir) = utils::dev_paths::bundled_openclaw_prompts_dir(&resource_dir) {
|
||||
return Some((ai_dir, "bundled"));
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(cwd) = std::env::current_dir() {
|
||||
if let Some(path) = utils::dev_paths::repo_ai_prompts_dir(&cwd) {
|
||||
return Some((path, "bundled"));
|
||||
}
|
||||
|
||||
let fallback = cwd.join("ai");
|
||||
if fallback.is_dir() {
|
||||
return Some((fallback, "bundled"));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn build_ai_preview(app: &tauri::AppHandle) -> AIPreview {
|
||||
let started = now_ms();
|
||||
let loaded_at = now_ms();
|
||||
let mut errors = Vec::new();
|
||||
let mut soul_raw = String::new();
|
||||
let mut tools_raw = String::new();
|
||||
let mut source = "bundled".to_string();
|
||||
|
||||
if let Some((ai_dir, resolved_source)) = resolve_ai_directory(app) {
|
||||
source = resolved_source.to_string();
|
||||
let soul_path = ai_dir.join("SOUL.md");
|
||||
let tools_path = ai_dir.join("TOOLS.md");
|
||||
soul_raw = std::fs::read_to_string(&soul_path).unwrap_or_else(|e| {
|
||||
errors.push(format!("Failed to read SOUL.md: {e}"));
|
||||
String::new()
|
||||
});
|
||||
tools_raw = std::fs::read_to_string(&tools_path).unwrap_or_else(|e| {
|
||||
errors.push(format!("Failed to read TOOLS.md: {e}"));
|
||||
String::new()
|
||||
});
|
||||
} else {
|
||||
errors.push("AI config directory not found".to_string());
|
||||
}
|
||||
|
||||
let soul = parse_soul_preview(soul_raw, loaded_at);
|
||||
let tools = parse_tools_preview(tools_raw, loaded_at);
|
||||
let done = now_ms();
|
||||
|
||||
AIPreview {
|
||||
soul,
|
||||
tools,
|
||||
metadata: AIPreviewMetadata {
|
||||
loaded_at: done,
|
||||
loading_duration: done - started,
|
||||
has_fallbacks: false,
|
||||
sources: AIPreviewSources {
|
||||
soul: source.clone(),
|
||||
tools: source,
|
||||
},
|
||||
errors,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn ai_get_config(app: tauri::AppHandle) -> Result<AIPreview, String> {
|
||||
Ok(build_ai_preview(&app))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn ai_refresh_config(app: tauri::AppHandle) -> Result<AIPreview, String> {
|
||||
Ok(build_ai_preview(&app))
|
||||
}
|
||||
|
||||
/// Write AI configuration files to `src/ai/prompts` in the repo (dev resolution from cwd).
|
||||
#[tauri::command]
|
||||
async fn write_ai_config_file(filename: String, content: String) -> Result<bool, String> {
|
||||
use std::env;
|
||||
|
||||
// Determine runtime working directory
|
||||
let current_dir =
|
||||
env::current_dir().map_err(|e| format!("Failed to get current directory: {e}"))?;
|
||||
|
||||
// Ensure filename is safe (only allow .md files)
|
||||
if !filename.ends_with(".md") {
|
||||
return Err("Only .md files are allowed".to_string());
|
||||
}
|
||||
|
||||
// Prevent path traversal by checking for dangerous characters
|
||||
if filename.contains("..") || filename.contains("/") || filename.contains("\\") {
|
||||
return Err("Invalid filename: path traversal not allowed".to_string());
|
||||
}
|
||||
|
||||
let ai_dir = utils::dev_paths::repo_ai_prompts_dir(¤t_dir)
|
||||
.unwrap_or_else(|| current_dir.join("src").join("ai").join("prompts"));
|
||||
let file_path = ai_dir.join(&filename);
|
||||
|
||||
// Ensure ai directory exists
|
||||
std::fs::create_dir_all(&ai_dir).map_err(|e| format!("Failed to create ai directory: {e}"))?;
|
||||
|
||||
// Write the file
|
||||
std::fs::write(&file_path, content)
|
||||
.map_err(|e| format!("Failed to write file {}: {e}", filename))?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn is_daemon_mode() -> bool {
|
||||
std::env::args().any(|arg| arg == "daemon" || arg == "--daemon")
|
||||
}
|
||||
|
||||
/// Watch daemon health file and bridge changes to frontend Tauri events
|
||||
async fn watch_daemon_health_file(app_handle: AppHandle, data_dir: PathBuf) {
|
||||
let state_file = data_dir.join("daemon_state.json");
|
||||
let mut interval = interval(Duration::from_secs(2));
|
||||
let mut last_modified: Option<std::time::SystemTime> = None;
|
||||
|
||||
log::info!(
|
||||
"[openhuman] Watching daemon health file: {}",
|
||||
state_file.display()
|
||||
);
|
||||
|
||||
loop {
|
||||
interval.tick().await;
|
||||
|
||||
// Check if file exists and was modified
|
||||
if let Ok(metadata) = fs::metadata(&state_file).await {
|
||||
if let Ok(modified) = metadata.modified() {
|
||||
if last_modified.map_or(true, |last| modified > last) {
|
||||
last_modified = Some(modified);
|
||||
|
||||
// Read and parse health data
|
||||
if let Ok(content) = fs::read_to_string(&state_file).await {
|
||||
if let Ok(json_value) = serde_json::from_str::<serde_json::Value>(&content)
|
||||
{
|
||||
log::debug!(
|
||||
"[openhuman] Broadcasting health event from file: {:?}",
|
||||
json_value
|
||||
);
|
||||
|
||||
// Emit Tauri event to frontend (same as internal daemon)
|
||||
if let Err(e) = app_handle.emit("openhuman:health", &json_value) {
|
||||
log::error!(
|
||||
"[openhuman] Failed to emit health event from file: {}",
|
||||
e
|
||||
);
|
||||
} else {
|
||||
log::debug!(
|
||||
"[openhuman] Health event emitted successfully from file"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
log::debug!(
|
||||
"[openhuman] Failed to parse health file as JSON: {}",
|
||||
state_file.display()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
log::debug!(
|
||||
"[openhuman] Failed to read health file: {}",
|
||||
state_file.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// File doesn't exist yet - external daemon may not be writing yet
|
||||
log::debug!(
|
||||
"[openhuman] Health file not found yet: {}",
|
||||
state_file.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
if let Err(err) = rustls::crypto::ring::default_provider().install_default() {
|
||||
log::warn!(
|
||||
"[app] rustls crypto provider not installed (already set?): {:?}",
|
||||
err
|
||||
);
|
||||
} else {
|
||||
log::info!("[app] rustls crypto provider installed (ring)");
|
||||
}
|
||||
|
||||
let daemon_mode = is_daemon_mode();
|
||||
|
||||
// Initialize logger
|
||||
{
|
||||
use env_logger::fmt::style::{AnsiColor, Style};
|
||||
use std::io::Write;
|
||||
|
||||
let default_filter = std::env::var("RUST_LOG")
|
||||
.unwrap_or_else(|_| "info,tungstenite=warn,tokio_tungstenite=warn,reqwest=warn,rusqlite=warn,hyper=warn,h2=warn".to_string());
|
||||
|
||||
let write_style = std::env::var("RUST_LOG_STYLE")
|
||||
.map(|v| match v.as_str() {
|
||||
"never" => env_logger::fmt::WriteStyle::Never,
|
||||
_ => env_logger::fmt::WriteStyle::Always,
|
||||
})
|
||||
.unwrap_or(env_logger::fmt::WriteStyle::Always);
|
||||
|
||||
let _ = env_logger::Builder::new()
|
||||
.parse_filters(&default_filter)
|
||||
.write_style(write_style)
|
||||
.format(|buf, record| {
|
||||
let timestamp = buf.timestamp_millis()
|
||||
.to_string();
|
||||
// Strip the date prefix, keep only HH:MM:SS.mmm
|
||||
let time_only = timestamp.split('T')
|
||||
.nth(1)
|
||||
.and_then(|t| t.strip_suffix('Z'))
|
||||
.unwrap_or(×tamp);
|
||||
let level = record.level();
|
||||
|
||||
// Level colors
|
||||
let level_style = match level {
|
||||
log::Level::Error => Style::new().fg_color(Some(AnsiColor::Red.into())).bold(),
|
||||
log::Level::Warn => Style::new().fg_color(Some(AnsiColor::Yellow.into())).bold(),
|
||||
log::Level::Info => Style::new().fg_color(Some(AnsiColor::Green.into())),
|
||||
log::Level::Debug => Style::new().fg_color(Some(AnsiColor::BrightBlack.into())),
|
||||
log::Level::Trace => Style::new().fg_color(Some(AnsiColor::BrightBlack.into())),
|
||||
};
|
||||
|
||||
let msg = format!("{}", record.args());
|
||||
|
||||
// Extract tag from message (e.g. "[socket-mgr]", "[skill:x]")
|
||||
let (tag, rest) = if msg.starts_with('[') {
|
||||
if let Some(end) = msg.find(']') {
|
||||
let tag = &msg[..=end];
|
||||
let rest = msg[end + 1..].trim_start();
|
||||
(Some(tag.to_string()), rest.to_string())
|
||||
} else {
|
||||
(None, msg)
|
||||
}
|
||||
} else {
|
||||
(None, msg)
|
||||
};
|
||||
|
||||
// Tag-based colors
|
||||
let tag_style = if let Some(ref t) = tag {
|
||||
let t_lower = t.to_lowercase();
|
||||
if t_lower.contains("socket") {
|
||||
Style::new().fg_color(Some(AnsiColor::Blue.into())).bold()
|
||||
} else if t_lower.contains("runtime") {
|
||||
Style::new().fg_color(Some(AnsiColor::Cyan.into())).bold()
|
||||
} else if t_lower.contains("skill") {
|
||||
Style::new().fg_color(Some(AnsiColor::Green.into())).bold()
|
||||
} else if t_lower.contains("ping") || t_lower.contains("cron") {
|
||||
Style::new().fg_color(Some(AnsiColor::Yellow.into())).bold()
|
||||
} else if t_lower.contains("app") {
|
||||
Style::new().fg_color(Some(AnsiColor::White.into())).bold()
|
||||
} else if t_lower.contains("ai") {
|
||||
Style::new().fg_color(Some(AnsiColor::BrightMagenta.into())).bold()
|
||||
} else {
|
||||
Style::new().fg_color(Some(AnsiColor::BrightBlack.into()))
|
||||
}
|
||||
} else {
|
||||
Style::new()
|
||||
};
|
||||
|
||||
let dim = Style::new().fg_color(Some(AnsiColor::BrightBlack.into()));
|
||||
|
||||
if let Some(ref t) = tag {
|
||||
writeln!(
|
||||
buf,
|
||||
"{dim}{time_only}{dim:#} {level_style}{level:<5}{level_style:#} {tag_style}{t}{tag_style:#} {rest}"
|
||||
)
|
||||
} else {
|
||||
writeln!(
|
||||
buf,
|
||||
"{dim}{time_only}{dim:#} {level_style}{level:<5}{level_style:#} {rest}"
|
||||
)
|
||||
}
|
||||
})
|
||||
.try_init();
|
||||
}
|
||||
|
||||
let mut builder = tauri::Builder::default()
|
||||
// Plugins
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.invoke_handler(tauri::generate_handler![greet])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
.plugin(tauri_plugin_deep_link::init())
|
||||
.plugin(tauri_plugin_os::init());
|
||||
|
||||
// Add desktop-only plugins (autostart, notification)
|
||||
#[cfg(desktop)]
|
||||
{
|
||||
builder = builder
|
||||
.plugin(tauri_plugin_autostart::init(
|
||||
tauri_plugin_autostart::MacosLauncher::LaunchAgent,
|
||||
Some(vec!["--daemon"]),
|
||||
))
|
||||
.plugin(tauri_plugin_notification::init());
|
||||
}
|
||||
|
||||
builder
|
||||
// Setup
|
||||
.setup(move |app| {
|
||||
// Register deep link handlers (Windows/Linux)
|
||||
#[cfg(any(windows, target_os = "linux"))]
|
||||
{
|
||||
app.deep_link().register_all()?;
|
||||
}
|
||||
|
||||
// macOS-specific: Handle window close event to minimize to tray
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let app_handle = app.handle().clone();
|
||||
window.on_window_event(move |event| {
|
||||
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
|
||||
// Prevent the window from closing, hide it instead
|
||||
api.prevent_close();
|
||||
if let Some(win) = app_handle.get_webview_window("main") {
|
||||
let _ = win.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Bridge external daemon health file and ensure core background service.
|
||||
{
|
||||
let data_dir = app.path().app_data_dir().unwrap_or_else(|_| {
|
||||
dirs::home_dir()
|
||||
.unwrap_or_else(|| std::path::PathBuf::from("."))
|
||||
.join(".openhuman")
|
||||
});
|
||||
let app_handle_for_watcher = app.handle().clone();
|
||||
let data_dir_clone = data_dir.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
watch_daemon_health_file(app_handle_for_watcher, data_dir_clone).await;
|
||||
});
|
||||
tauri::async_runtime::spawn(async move {
|
||||
match commands::core_relay::ensure_service_managed_core_running().await {
|
||||
Ok(()) => {
|
||||
log::info!("[openhuman] Core background service ensured via core RPC");
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!(
|
||||
"[openhuman] Failed to ensure core background service: {e}"
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Start/ensure standalone core process for business logic RPC.
|
||||
{
|
||||
let core_run_mode = core_process::default_core_run_mode(daemon_mode);
|
||||
let core_bin = if matches!(core_run_mode, core_process::CoreRunMode::ChildProcess) {
|
||||
core_process::default_core_bin()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let core_handle = core_process::CoreProcessHandle::new(
|
||||
core_process::default_core_port(),
|
||||
core_bin,
|
||||
core_run_mode,
|
||||
);
|
||||
std::env::set_var("OPENHUMAN_CORE_RPC_URL", core_handle.rpc_url());
|
||||
app.manage(core_handle.clone());
|
||||
tauri::async_runtime::spawn(async move {
|
||||
if let Err(err) = core_handle.ensure_running().await {
|
||||
log::error!("[core] failed to start core process: {err}");
|
||||
} else {
|
||||
log::info!("[core] core process ready");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if daemon_mode {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.hide();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
// Register all commands (desktop build lists handlers explicitly below).
|
||||
.invoke_handler({
|
||||
#[cfg(desktop)]
|
||||
{
|
||||
tauri::generate_handler![
|
||||
greet,
|
||||
// AI config file writing
|
||||
write_ai_config_file,
|
||||
ai_get_config,
|
||||
ai_refresh_config,
|
||||
core_rpc_relay,
|
||||
show_window,
|
||||
hide_window,
|
||||
toggle_window,
|
||||
is_window_visible,
|
||||
minimize_window,
|
||||
maximize_window,
|
||||
close_window,
|
||||
set_window_title,
|
||||
// OpenHuman local host commands (core RPC uses core_rpc_relay)
|
||||
openhuman_get_daemon_host_config,
|
||||
openhuman_set_daemon_host_config,
|
||||
openhuman_service_install,
|
||||
openhuman_service_start,
|
||||
openhuman_service_stop,
|
||||
openhuman_service_status,
|
||||
openhuman_service_uninstall,
|
||||
]
|
||||
}
|
||||
})
|
||||
.build({
|
||||
let mut context = tauri::generate_context!();
|
||||
if daemon_mode {
|
||||
context.config_mut().app.windows.clear();
|
||||
}
|
||||
context
|
||||
})
|
||||
.expect("error while building tauri application")
|
||||
.run(move |app_handle, event| {
|
||||
match event {
|
||||
// Handle macOS Dock icon click (reopen event)
|
||||
#[cfg(target_os = "macos")]
|
||||
RunEvent::Reopen { .. } => {
|
||||
if !daemon_mode {
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
let _ = window.show();
|
||||
let _ = window.unminimize();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Gracefully shut down background services before process exit.
|
||||
RunEvent::Exit => {
|
||||
log::info!("[app] Exit event received, shutting down");
|
||||
|
||||
let _ = app_handle;
|
||||
}
|
||||
|
||||
_ => {
|
||||
let _ = app_handle;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn run_core_from_args(args: &[String]) -> anyhow::Result<()> {
|
||||
let core_bin = crate::core_process::default_core_bin()
|
||||
.ok_or_else(|| anyhow::anyhow!("openhuman core binary not found"))?;
|
||||
let status = std::process::Command::new(core_bin)
|
||||
.args(args)
|
||||
.status()
|
||||
.map_err(|e| anyhow::anyhow!("failed to execute core binary: {e}"))?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("core binary exited with status {status}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2,5 +2,14 @@
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
openhuman_lib::run()
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.get(1).map(String::as_str) == Some("core") {
|
||||
if let Err(err) = openhuman::run_core_from_args(&args[2..]) {
|
||||
eprintln!("core process failed: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
openhuman::run()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
//! Dev-time path resolution for bundled / repo AI prompts (desktop host).
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Best-effort path to repo `src/ai/prompts` when running from a checkout.
|
||||
pub fn repo_ai_prompts_dir(cwd: &Path) -> Option<PathBuf> {
|
||||
let prompts = cwd.join("src").join("ai").join("prompts");
|
||||
if prompts.is_dir() {
|
||||
return Some(prompts);
|
||||
}
|
||||
let app_prompts = cwd.join("app").join("src").join("ai").join("prompts");
|
||||
if app_prompts.is_dir() {
|
||||
return Some(app_prompts);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Bundled OpenClaw-style prompts inside the packaged app resource dir.
|
||||
pub fn bundled_openclaw_prompts_dir(resource_dir: &Path) -> Option<PathBuf> {
|
||||
let candidates = [
|
||||
resource_dir.join("ai").join("prompts"),
|
||||
resource_dir.join("_up_").join("ai").join("prompts"),
|
||||
];
|
||||
for p in candidates {
|
||||
if p.is_dir() {
|
||||
return Some(p);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod dev_paths;
|
||||
@@ -1,25 +1,29 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "openhuman",
|
||||
"version": "0.1.0",
|
||||
"identifier": "com.tinyhumansai.openhuman",
|
||||
"productName": "OpenHuman",
|
||||
"version": "0.49.16",
|
||||
"identifier": "com.openhuman.app",
|
||||
"build": {
|
||||
"beforeDevCommand": "yarn dev",
|
||||
"beforeDevCommand": "npm run core:stage && npm run dev",
|
||||
"devUrl": "http://localhost:1420",
|
||||
"beforeBuildCommand": "yarn build",
|
||||
"beforeBuildCommand": "npm run build:app && npm run core:stage",
|
||||
"frontendDist": "../dist"
|
||||
},
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"title": "openhuman",
|
||||
"label": "main",
|
||||
"title": "OpenHuman",
|
||||
"width": 800,
|
||||
"height": 600
|
||||
"height": 720,
|
||||
"visible": false,
|
||||
"decorations": true,
|
||||
"resizable": true,
|
||||
"center": true
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": null
|
||||
}
|
||||
"security": { "csp": null },
|
||||
"macOSPrivateApi": true
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
@@ -30,6 +34,22 @@
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
]
|
||||
],
|
||||
"resources": ["../../skills/skills", "../../src/ai/prompts"],
|
||||
"externalBin": ["binaries/openhuman"],
|
||||
"createUpdaterArtifacts": true,
|
||||
"macOS": {
|
||||
"minimumSystemVersion": "10.15",
|
||||
"dmg": { "background": "./images/background-dmg.png" }
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"updater": {
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDA1NkI1NEQzNjVDMjA0NDgKUldSSUJNSmwwMVJyQmJCTnQrQWxPSmhZQmVyWU1NMXdwRlRCSHR0ZEdlbkRncCtpQnhaZU5rUEQK",
|
||||
"endpoints": [
|
||||
"https://github.com/tinyhumansai/openhuman/releases/latest/download/latest.json"
|
||||
]
|
||||
},
|
||||
"deep-link": { "desktop": { "schemes": ["openhuman"] } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
line-height: 24px;
|
||||
font-weight: 400;
|
||||
|
||||
color: #0f0f0f;
|
||||
background-color: #f6f6f6;
|
||||
color: #fff;
|
||||
background-color: #000;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
|
||||
@@ -1,50 +1,65 @@
|
||||
import { useState } from "react";
|
||||
import reactLogo from "./assets/react.svg";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import "./App.css";
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { HashRouter as Router } from 'react-router-dom';
|
||||
import { PersistGate } from 'redux-persist/integration/react';
|
||||
|
||||
import AppRoutes from './AppRoutes';
|
||||
import ErrorFallbackScreen from './components/ErrorFallbackScreen';
|
||||
import MiniSidebar from './components/MiniSidebar';
|
||||
import AIProvider from './providers/AIProvider';
|
||||
import SkillProvider from './providers/SkillProvider';
|
||||
import SocketProvider from './providers/SocketProvider';
|
||||
import UserProvider from './providers/UserProvider';
|
||||
import { tagErrorSource } from './services/errorReportQueue';
|
||||
import { persistor, store } from './store';
|
||||
import { syncMemoryClientToken } from './utils/tauriCommands';
|
||||
|
||||
function App() {
|
||||
const [greetMsg, setGreetMsg] = useState("");
|
||||
const [name, setName] = useState("");
|
||||
|
||||
async function greet() {
|
||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
||||
setGreetMsg(await invoke("greet", { name }));
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="container">
|
||||
<h1>Welcome to Tauri + React</h1>
|
||||
|
||||
<div className="row">
|
||||
<a href="https://vite.dev" target="_blank">
|
||||
<img src="/vite.svg" className="logo vite" alt="Vite logo" />
|
||||
</a>
|
||||
<a href="https://tauri.app" target="_blank">
|
||||
<img src="/tauri.svg" className="logo tauri" alt="Tauri logo" />
|
||||
</a>
|
||||
<a href="https://react.dev" target="_blank">
|
||||
<img src={reactLogo} className="logo react" alt="React logo" />
|
||||
</a>
|
||||
</div>
|
||||
<p>Click on the Tauri, Vite, and React logos to learn more.</p>
|
||||
|
||||
<form
|
||||
className="row"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
greet();
|
||||
}}
|
||||
>
|
||||
<input
|
||||
id="greet-input"
|
||||
onChange={(e) => setName(e.currentTarget.value)}
|
||||
placeholder="Enter a name..."
|
||||
/>
|
||||
<button type="submit">Greet</button>
|
||||
</form>
|
||||
<p>{greetMsg}</p>
|
||||
</main>
|
||||
<Sentry.ErrorBoundary
|
||||
fallback={({ error, componentStack, resetError }) => (
|
||||
<ErrorFallbackScreen error={error} componentStack={componentStack} onReset={resetError} />
|
||||
)}
|
||||
onError={(_error, componentStack, eventId) => {
|
||||
tagErrorSource(eventId, 'react', componentStack);
|
||||
}}>
|
||||
<Provider store={store}>
|
||||
<PersistGate
|
||||
loading={null}
|
||||
persistor={persistor}
|
||||
onBeforeLift={async () => {
|
||||
const token = store.getState().auth.token;
|
||||
console.info('[memory] PersistGate onBeforeLift: token_present=%s', !!token);
|
||||
if (token) await syncMemoryClientToken(token);
|
||||
}}>
|
||||
<UserProvider>
|
||||
<SocketProvider>
|
||||
<AIProvider>
|
||||
<SkillProvider>
|
||||
<Router>
|
||||
<div className="relative h-screen flex flex-col overflow-hidden">
|
||||
<div className="flex-1 flex overflow-hidden">
|
||||
<MiniSidebar />
|
||||
<div className="flex flex-col flex-1 relative overflow-hidden">
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<AppRoutes />
|
||||
</div>
|
||||
<div className="pointer-events-none flex-shrink-0 flex justify-center z-50">
|
||||
<div className="w-full px-3 py-1.5 text-[9px] uppercase tracking-[0.18em] text-white/40 text-center bg-[#000]">
|
||||
OpenHuman is in early beta
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Router>
|
||||
</SkillProvider>
|
||||
</AIProvider>
|
||||
</SocketProvider>
|
||||
</UserProvider>
|
||||
</PersistGate>
|
||||
</Provider>
|
||||
</Sentry.ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 644 B After Width: | Height: | Size: 644 B |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
@@ -1,4 +1,3 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
@@ -6,7 +5,7 @@ import { deriveConnectionStatus, useSkillConnectionStatus } from '../lib/skills/
|
||||
import { deriveSkillSyncSummaryText } from '../pages/skillsSyncUi';
|
||||
import { useAppSelector } from '../store/hooks';
|
||||
import { IS_DEV } from '../utils/config';
|
||||
import SelfEvolveModal from './skills/SelfEvolveModal';
|
||||
import { runtimeDiscoverSkills } from '../utils/tauriCommands';
|
||||
import {
|
||||
DefaultIcon,
|
||||
SKILL_ICONS,
|
||||
@@ -16,30 +15,6 @@ import {
|
||||
} from './skills/shared';
|
||||
import SkillSetupModal from './skills/SkillSetupModal';
|
||||
|
||||
/** Normalize a raw unified registry entry into a SkillListEntry for display. */
|
||||
function normalizeUnifiedEntry(e: Record<string, unknown>): SkillListEntry {
|
||||
const setup = e.setup as { required?: boolean; oauth?: unknown } | undefined;
|
||||
// Treat both interactive setup steps and OAuth-only flows as "has setup"
|
||||
// so that clicking a skill (e.g. Gmail) opens the connection/setup wizard
|
||||
// instead of jumping straight to the management panel.
|
||||
const hasSetup =
|
||||
!!setup &&
|
||||
(setup.required === true ||
|
||||
// OAuth config means we still need a connection step in the wizard
|
||||
!!setup.oauth);
|
||||
|
||||
return {
|
||||
id: e.id as string,
|
||||
name:
|
||||
(e.name as string) || (e.id as string).charAt(0).toUpperCase() + (e.id as string).slice(1),
|
||||
description: (e.description as string) || '',
|
||||
icon: SKILL_ICONS[e.id as string],
|
||||
ignoreInProduction: (e.ignoreInProduction as boolean) ?? false,
|
||||
hasSetup,
|
||||
skill_type: (e.skill_type as 'openhuman' | 'openclaw') ?? 'openhuman',
|
||||
};
|
||||
}
|
||||
|
||||
interface SkillRowProps {
|
||||
skillId: string;
|
||||
name: string;
|
||||
@@ -115,8 +90,6 @@ export default function SkillsGrid() {
|
||||
const navigate = useNavigate();
|
||||
const [skillsList, setSkillsList] = useState<SkillListEntry[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [generating, setGenerating] = useState(false);
|
||||
const [selfEvolveOpen, setSelfEvolveOpen] = useState(false);
|
||||
const [setupModalOpen, setSetupModalOpen] = useState(false);
|
||||
const [activeSkillId, setActiveSkillId] = useState<string | null>(null);
|
||||
const [activeSkillName, setActiveSkillName] = useState<string>('');
|
||||
@@ -124,21 +97,16 @@ export default function SkillsGrid() {
|
||||
const [activeSkillHasSetup, setActiveSkillHasSetup] = useState(false);
|
||||
const [activeSkillType, setActiveSkillType] = useState<'openhuman' | 'openclaw'>('openhuman');
|
||||
|
||||
// Get Redux state for sorting
|
||||
const skillsState = useAppSelector(state => state.skills.skills);
|
||||
const skillStates = useAppSelector(state => state.skills.skillStates);
|
||||
const syncStatsBySkill = useAppSelector(state => state.skills.syncStatsBySkill);
|
||||
|
||||
// Load skills from the unified registry (covers both openhuman and openclaw types).
|
||||
// Extracted so it can be called after skill creation (e.g. from SelfEvolveModal).
|
||||
const refreshSkills = async () => {
|
||||
try {
|
||||
// Try unified registry first — it merges both skill types.
|
||||
const entries = await invoke<Array<Record<string, unknown>>>('unified_list_skills');
|
||||
|
||||
const processed: SkillListEntry[] = entries
|
||||
.filter(e => {
|
||||
const id = e.id as string;
|
||||
const manifests = await runtimeDiscoverSkills();
|
||||
const processed: SkillListEntry[] = manifests
|
||||
.filter(m => {
|
||||
const id = m.id as string;
|
||||
if (id.includes('_')) {
|
||||
console.warn(
|
||||
`Skill "${id}" contains underscore and will be skipped. Skill IDs cannot contain underscores.`
|
||||
@@ -147,38 +115,27 @@ export default function SkillsGrid() {
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map(normalizeUnifiedEntry)
|
||||
.map(m => {
|
||||
const setup = m.setup as { required?: boolean; oauth?: unknown } | undefined;
|
||||
const hasSetup =
|
||||
!!setup &&
|
||||
(setup.required === true ||
|
||||
// OAuth-only skills still need a setup/connect flow
|
||||
!!setup.oauth);
|
||||
return {
|
||||
id: m.id as string,
|
||||
name: (m.name as string) || (m.id as string),
|
||||
description: (m.description as string) || '',
|
||||
icon: SKILL_ICONS[m.id as string],
|
||||
ignoreInProduction: (m.ignoreInProduction as boolean) ?? false,
|
||||
hasSetup,
|
||||
skill_type: 'openhuman' as const,
|
||||
};
|
||||
})
|
||||
.filter(s => IS_DEV || !s.ignoreInProduction);
|
||||
|
||||
setSkillsList(processed);
|
||||
} catch {
|
||||
// Fallback to legacy runtime_discover_skills if unified registry isn't available.
|
||||
try {
|
||||
const manifests = await invoke<Array<Record<string, unknown>>>('runtime_discover_skills');
|
||||
const processed: SkillListEntry[] = manifests
|
||||
.filter(m => !(m.id as string).includes('_'))
|
||||
.map(m => {
|
||||
const setup = m.setup as { required?: boolean; oauth?: unknown } | undefined;
|
||||
const hasSetup =
|
||||
!!setup &&
|
||||
(setup.required === true ||
|
||||
// OAuth-only skills still need a setup/connect flow
|
||||
!!setup.oauth);
|
||||
return {
|
||||
id: m.id as string,
|
||||
name: (m.name as string) || (m.id as string),
|
||||
description: (m.description as string) || '',
|
||||
icon: SKILL_ICONS[m.id as string],
|
||||
ignoreInProduction: (m.ignoreInProduction as boolean) ?? false,
|
||||
hasSetup,
|
||||
skill_type: 'openhuman' as const,
|
||||
};
|
||||
})
|
||||
.filter(s => IS_DEV || !s.ignoreInProduction);
|
||||
setSkillsList(processed);
|
||||
} catch (err) {
|
||||
console.warn('Could not load skills:', err);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Could not load skills:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -188,7 +145,6 @@ export default function SkillsGrid() {
|
||||
refreshSkills();
|
||||
}, []);
|
||||
|
||||
// Sort skills by connection status (connected first)
|
||||
const sortedSkillsList = useMemo(() => {
|
||||
return [...skillsList]
|
||||
.sort((a, b) => {
|
||||
@@ -203,7 +159,6 @@ export default function SkillsGrid() {
|
||||
const priorityA = STATUS_PRIORITY[statusA] ?? 999;
|
||||
const priorityB = STATUS_PRIORITY[statusB] ?? 999;
|
||||
|
||||
// If same priority, sort alphabetically by name
|
||||
if (priorityA === priorityB) {
|
||||
return a.name.localeCompare(b.name);
|
||||
}
|
||||
@@ -212,7 +167,7 @@ export default function SkillsGrid() {
|
||||
})
|
||||
.filter(s => IS_DEV || !s.ignoreInProduction);
|
||||
}, [skillsList, skillsState, skillStates]);
|
||||
// If loading or no skills on desktop, don't render
|
||||
|
||||
if (loading || skillsList.length === 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -231,70 +186,6 @@ export default function SkillsGrid() {
|
||||
<div className="animate-fade-up mt-4 mb-8 relative">
|
||||
<div className="flex items-center justify-between mb-3 px-1">
|
||||
<h3 className="text-sm font-semibold text-white opacity-80">Available Skills</h3>
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Auto-Generate button — opens the self-evolving skill modal */}
|
||||
<button
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
setSelfEvolveOpen(true);
|
||||
}}
|
||||
className="text-xs text-primary-400 hover:text-primary-300 transition-colors flex items-center gap-1">
|
||||
{/* Sparkle / robot icon */}
|
||||
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M5 3l1.5 4.5L11 9l-4.5 1.5L5 15l-1.5-4.5L-1 9l4.5-1.5L5 3zM19 11l1 3 3 1-3 1-1 3-1-3-3-1 3-1 1-3z"
|
||||
/>
|
||||
</svg>
|
||||
Auto-Generate
|
||||
</button>
|
||||
{/* Generate button — quick scaffold */}
|
||||
<button
|
||||
onClick={async e => {
|
||||
e.stopPropagation();
|
||||
setGenerating(true);
|
||||
try {
|
||||
await invoke('unified_generate_skill', {
|
||||
spec: {
|
||||
name: `generated-demo-${Date.now()}`,
|
||||
description: 'Auto-generated skill demonstrating the unified registry',
|
||||
skill_type: 'openhuman',
|
||||
tool_code:
|
||||
'return { message: `Hello from generated skill! args=${JSON.stringify(args)}` };',
|
||||
},
|
||||
});
|
||||
await refreshSkills();
|
||||
} catch (err) {
|
||||
console.warn('Failed to generate skill:', err);
|
||||
} finally {
|
||||
setGenerating(false);
|
||||
}
|
||||
}}
|
||||
className="text-xs text-primary-400 hover:text-primary-300 transition-colors flex items-center gap-1 disabled:opacity-50"
|
||||
disabled={generating}>
|
||||
{generating ? (
|
||||
<span className="opacity-60">Generating…</span>
|
||||
) : (
|
||||
<>
|
||||
<svg
|
||||
className="w-3.5 h-3.5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M12 4v16m8-8H4"
|
||||
/>
|
||||
</svg>
|
||||
Generate
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="glass rounded-xl overflow-hidden skills-table-container relative cursor-pointer"
|
||||
@@ -345,14 +236,12 @@ export default function SkillsGrid() {
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/* Hover overlay */}
|
||||
<div className="skills-table-overlay absolute inset-0 bg-black/80 flex items-center justify-center rounded-xl opacity-0 transition-opacity duration-200 pointer-events-none">
|
||||
<span className="text-sm font-medium text-white">View all skills</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Setup modal */}
|
||||
{setupModalOpen && activeSkillId && (
|
||||
<SkillSetupModal
|
||||
skillId={activeSkillId}
|
||||
@@ -366,11 +255,6 @@ export default function SkillsGrid() {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Self-Evolve modal */}
|
||||
{selfEvolveOpen && (
|
||||
<SelfEvolveModal onClose={() => setSelfEvolveOpen(false)} onSkillCreated={refreshSkills} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||