refactor(tauri): reduce desktop host to sidecar lifecycle + deep-link (#84)

* feat(onboarding): introduce DEV_FORCE_ONBOARDING flag to control onboarding flow

- Added DEV_FORCE_ONBOARDING configuration to bypass onboarding checks during development.
- Updated onboarding logic in AppRoutes to respect the new flag, ensuring onboarding is always shown when enabled.
- Introduced new utility functions for managing onboarding state based on the flag.
- Updated Cargo.lock and Cargo.toml to reflect dependency changes and version updates.
- Removed deprecated openhuman command module and refactored related functionalities into daemon_host module for better organization.

* feat(daemon): refactor daemon health monitoring and window management

- Updated DaemonHealthService to use polling via `openhuman.health_snapshot` instead of event listening for health updates.
- Introduced a new focusMainWindow function to centralize window focus logic across deep link handling and Tauri commands.
- Replaced `invoke` calls with direct window management methods for showing, hiding, and toggling the main window.
- Removed deprecated core_rpc module and related commands for improved organization and clarity.
- Updated Cargo.toml and Cargo.lock to reflect dependency changes.

* refactor(daemonHealthService): streamline imports and simplify tool object structure

- Moved the import of `callCoreRpc` to a more appropriate location in DaemonHealthService.
- Simplified the structure of the tools object in the aiGetConfig function for better readability.
- Ensured consistency in Tauri command invocations by removing unnecessary line breaks.
This commit is contained in:
Steven Enamakel
2026-03-30 11:41:29 -07:00
committed by GitHub
parent 6aeea8ba49
commit 36fc5bdfa6
20 changed files with 279 additions and 2795 deletions
+4
View File
@@ -5,5 +5,9 @@ export const CORE_RPC_URL =
export const IS_DEV = import.meta.env.DEV;
/** Dev only: skip `.skip_onboarding` workspace check and ignore onboarded state so `/onboarding` always shows. Set `VITE_DEV_FORCE_ONBOARDING=true` in `.env.local`. */
export const DEV_FORCE_ONBOARDING =
import.meta.env.DEV && import.meta.env.VITE_DEV_FORCE_ONBOARDING === 'true';
export const SKILLS_GITHUB_REPO =
import.meta.env.VITE_SKILLS_GITHUB_REPO || 'tinyhumansai/openhuman-skills';
+16 -16
View File
@@ -1,4 +1,5 @@
import { isTauri as coreIsTauri, invoke } from '@tauri-apps/api/core';
import { isTauri as coreIsTauri } from '@tauri-apps/api/core';
import { getCurrentWindow } from '@tauri-apps/api/window';
import { getCurrent, onOpenUrl } from '@tauri-apps/plugin-deep-link';
import { skillManager } from '../lib/skills/manager';
@@ -35,6 +36,17 @@ function getCurrentUserId(): string | null {
}
}
const focusMainWindow = async () => {
try {
const window = getCurrentWindow();
await window.show();
await window.unminimize();
await window.setFocus();
} catch (err) {
console.warn('[DeepLink] Failed to focus window:', err);
}
};
/**
* Handle an `openhuman://auth?token=...` deep link for login.
*/
@@ -48,11 +60,7 @@ const handleAuthDeepLink = async (parsed: URL) => {
console.log('[DeepLink] Received auth token', token);
try {
await invoke('show_window');
} catch (err) {
console.warn('[DeepLink] Failed to show window:', err);
}
await focusMainWindow();
if (key === 'auth') {
store.dispatch(setToken(token));
@@ -72,11 +80,7 @@ const handleAuthDeepLink = async (parsed: URL) => {
const handlePaymentDeepLink = async (parsed: URL) => {
const path = parsed.pathname.replace(/^\/+/, '');
try {
await invoke('show_window');
} catch {
// Not fatal
}
await focusMainWindow();
if (path === 'success') {
const sessionId = parsed.searchParams.get('session_id');
@@ -110,11 +114,7 @@ const handleOAuthDeepLink = async (parsed: URL) => {
// pathname is "/success" or "/error" (hostname is "oauth")
const path = parsed.pathname.replace(/^\/+/, '');
try {
await invoke('show_window');
} catch {
// Not fatal
}
await focusMainWindow();
if (path === 'success') {
const integrationId = parsed.searchParams.get('integrationId');
+55 -40
View File
@@ -4,6 +4,7 @@
* Helper functions for invoking Tauri commands from the frontend.
*/
import { isTauri as coreIsTauri, invoke } from '@tauri-apps/api/core';
import { getCurrentWindow } from '@tauri-apps/api/window';
import { callCoreRpc } from '../services/coreRpcClient';
@@ -86,7 +87,10 @@ export async function showWindow(): Promise<void> {
return;
}
await invoke('show_window');
const window = getCurrentWindow();
await window.show();
await window.unminimize();
await window.setFocus();
}
/**
@@ -97,7 +101,7 @@ export async function hideWindow(): Promise<void> {
return;
}
await invoke('hide_window');
await getCurrentWindow().hide();
}
/**
@@ -108,7 +112,15 @@ export async function toggleWindow(): Promise<void> {
return;
}
await invoke('toggle_window');
const window = getCurrentWindow();
const visible = await window.isVisible();
if (visible) {
await window.hide();
return;
}
await window.show();
await window.unminimize();
await window.setFocus();
}
/**
@@ -119,7 +131,7 @@ export async function isWindowVisible(): Promise<boolean> {
return true; // In browser, window is always visible
}
return await invoke('is_window_visible');
return await getCurrentWindow().isVisible();
}
/**
@@ -130,7 +142,7 @@ export async function minimizeWindow(): Promise<void> {
return;
}
await invoke('minimize_window');
await getCurrentWindow().minimize();
}
/**
@@ -141,7 +153,8 @@ export async function maximizeWindow(): Promise<void> {
return;
}
await invoke('maximize_window');
const window = getCurrentWindow();
await window.toggleMaximize();
}
/**
@@ -152,7 +165,7 @@ export async function closeWindow(): Promise<void> {
return;
}
await invoke('close_window');
await getCurrentWindow().close();
}
/**
@@ -164,7 +177,7 @@ export async function setWindowTitle(title: string): Promise<void> {
return;
}
await invoke('set_window_title', { title });
await getCurrentWindow().setTitle(title);
}
// --- Memory Commands ---
@@ -348,10 +361,6 @@ export interface CommandResponse<T> {
logs: string[];
}
function wrapCommandResult<T>(result: T): CommandResponse<T> {
return { result, logs: [] };
}
export interface SkillSnapshot {
skill_id: string;
name: string;
@@ -1254,17 +1263,28 @@ export async function openhumanLocalAiDownloadAsset(
}
export async function aiGetConfig(): Promise<AIPreview> {
if (!isTauri()) {
throw new Error('Not running in Tauri');
}
return await invoke('ai_get_config');
return {
soul: {
raw: '',
name: 'OpenHuman',
description: 'AI assistant',
personalityPreview: [],
safetyRulesPreview: [],
loadedAt: Date.now(),
},
tools: { raw: '', totalTools: 0, activeSkills: 0, skillsPreview: [], loadedAt: Date.now() },
metadata: {
loadedAt: Date.now(),
loadingDuration: 0,
hasFallbacks: true,
sources: { soul: 'frontend', tools: 'frontend' },
errors: ['AI prompt preview has been moved out of the Tauri host.'],
},
};
}
export async function aiRefreshConfig(): Promise<AIPreview> {
if (!isTauri()) {
throw new Error('Not running in Tauri');
}
return await invoke('ai_refresh_config');
return aiGetConfig();
}
export async function openhumanEncryptSecret(plaintext: string): Promise<CommandResponse<string>> {
@@ -1344,44 +1364,36 @@ export async function openhumanServiceInstall(): Promise<CommandResponse<Service
if (!isTauri()) {
throw new Error('Not running in Tauri');
}
return await invoke<CommandResponse<ServiceStatus>>('core_rpc_relay', {
request: { method: 'openhuman.service_install', params: {} },
});
return await callCoreRpc<CommandResponse<ServiceStatus>>({ method: 'openhuman.service_install' });
}
export async function openhumanServiceStart(): Promise<CommandResponse<ServiceStatus>> {
if (!isTauri()) {
throw new Error('Not running in Tauri');
}
return await invoke<CommandResponse<ServiceStatus>>('core_rpc_relay', {
request: { method: 'openhuman.service_start', params: {} },
});
return await callCoreRpc<CommandResponse<ServiceStatus>>({ method: 'openhuman.service_start' });
}
export async function openhumanServiceStop(): Promise<CommandResponse<ServiceStatus>> {
if (!isTauri()) {
throw new Error('Not running in Tauri');
}
return await invoke<CommandResponse<ServiceStatus>>('core_rpc_relay', {
request: { method: 'openhuman.service_stop', params: {} },
});
return await callCoreRpc<CommandResponse<ServiceStatus>>({ method: 'openhuman.service_stop' });
}
export async function openhumanServiceStatus(): Promise<CommandResponse<ServiceStatus>> {
if (!isTauri()) {
throw new Error('Not running in Tauri');
}
return await invoke<CommandResponse<ServiceStatus>>('core_rpc_relay', {
request: { method: 'openhuman.service_status', params: {} },
});
return await callCoreRpc<CommandResponse<ServiceStatus>>({ method: 'openhuman.service_status' });
}
export async function openhumanServiceUninstall(): Promise<CommandResponse<ServiceStatus>> {
if (!isTauri()) {
throw new Error('Not running in Tauri');
}
return await invoke<CommandResponse<ServiceStatus>>('core_rpc_relay', {
request: { method: 'openhuman.service_uninstall', params: {} },
return await callCoreRpc<CommandResponse<ServiceStatus>>({
method: 'openhuman.service_uninstall',
});
}
@@ -1389,8 +1401,8 @@ export async function openhumanAgentServerStatus(): Promise<CommandResponse<Agen
if (!isTauri()) {
throw new Error('Not running in Tauri');
}
return await invoke<CommandResponse<AgentServerStatus>>('core_rpc_relay', {
request: { method: 'openhuman.agent_server_status', params: {} },
return await callCoreRpc<CommandResponse<AgentServerStatus>>({
method: 'openhuman.agent_server_status',
});
}
@@ -1398,8 +1410,9 @@ export async function openhumanGetDaemonHostConfig(): Promise<CommandResponse<Da
if (!isTauri()) {
throw new Error('Not running in Tauri');
}
const result = await invoke<DaemonHostConfig>('openhuman_get_daemon_host_config');
return wrapCommandResult(result);
return await callCoreRpc<CommandResponse<DaemonHostConfig>>({
method: 'openhuman.service_daemon_host_get',
});
}
export async function openhumanSetDaemonHostConfig(
@@ -1408,8 +1421,10 @@ export async function openhumanSetDaemonHostConfig(
if (!isTauri()) {
throw new Error('Not running in Tauri');
}
const result = await invoke<DaemonHostConfig>('openhuman_set_daemon_host_config', { showTray });
return wrapCommandResult(result);
return await callCoreRpc<CommandResponse<DaemonHostConfig>>({
method: 'openhuman.service_daemon_host_set',
params: { show_tray: showTray },
});
}
export async function openhumanAccessibilityStatus(): Promise<