From 33421503a932e3da8d82425d26840205a3b7a29f Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Mon, 30 Mar 2026 15:36:21 -0700 Subject: [PATCH] feat(skills): refactor skill management functions to use new RPC methods - Replaced direct Tauri invocations with a unified `callCoreRpc` function for skill management operations, enhancing code consistency and maintainability. - Updated functions for listing, discovering, starting, stopping, and managing skill data to utilize the new RPC schema. - Added new RPC handlers for skill discovery, listing, data reading, writing, enabling, disabling, and checking if a skill is enabled, expanding the skills management capabilities. - Enhanced the SkillManifest struct to support serialization, improving data handling for skill manifests. --- app/src/utils/tauriCommands.ts | 97 ++++++------ src/openhuman/skills/manifest.rs | 2 +- src/openhuman/skills/schemas.rs | 255 +++++++++++++++++++++++++++++++ 3 files changed, 305 insertions(+), 49 deletions(-) diff --git a/app/src/utils/tauriCommands.ts b/app/src/utils/tauriCommands.ts index 07ca1135a..3158fb137 100644 --- a/app/src/utils/tauriCommands.ts +++ b/app/src/utils/tauriCommands.ts @@ -1698,31 +1698,29 @@ export async function openhumanAutocompleteClearHistory(): Promise< } export async function runtimeListSkills(): Promise { - if (!isTauri()) { - throw new Error('Not running in Tauri'); - } - return await invoke('runtime_list_skills'); + return await callCoreRpc({ + method: 'openhuman.skills_list', + }); } export async function runtimeDiscoverSkills(): Promise { - if (!isTauri()) { - throw new Error('Not running in Tauri'); - } - return await invoke('runtime_discover_skills'); + return await callCoreRpc({ + method: 'openhuman.skills_discover', + }); } export async function runtimeStartSkill(skillId: string): Promise { - if (!isTauri()) { - throw new Error('Not running in Tauri'); - } - return await invoke('runtime_start_skill', { skill_id: skillId }); + return await callCoreRpc({ + method: 'openhuman.skills_start', + params: { skill_id: skillId }, + }); } export async function runtimeStopSkill(skillId: string): Promise { - if (!isTauri()) { - throw new Error('Not running in Tauri'); - } - await invoke('runtime_stop_skill', { skill_id: skillId }); + await callCoreRpc({ + method: 'openhuman.skills_stop', + params: { skill_id: skillId }, + }); } export async function runtimeRpc( @@ -1730,17 +1728,18 @@ export async function runtimeRpc( method: string, params: Record = {} ): Promise { - if (!isTauri()) { - throw new Error('Not running in Tauri'); - } - return await invoke('runtime_rpc', { skill_id: skillId, method, params }); + return await callCoreRpc({ + method: 'openhuman.skills_rpc', + params: { skill_id: skillId, method, params }, + }); } export async function runtimeSkillDataRead(skillId: string, filename: string): Promise { - if (!isTauri()) { - throw new Error('Not running in Tauri'); - } - return await invoke('runtime_skill_data_read', { skill_id: skillId, filename }); + const result = await callCoreRpc<{ content: string }>({ + method: 'openhuman.skills_data_read', + params: { skill_id: skillId, filename }, + }); + return result.content; } export async function runtimeSkillDataWrite( @@ -1748,17 +1747,18 @@ export async function runtimeSkillDataWrite( filename: string, content: string ): Promise { - if (!isTauri()) { - throw new Error('Not running in Tauri'); - } - await invoke('runtime_skill_data_write', { skill_id: skillId, filename, content }); + await callCoreRpc({ + method: 'openhuman.skills_data_write', + params: { skill_id: skillId, filename, content }, + }); } export async function runtimeSkillDataDir(skillId: string): Promise { - if (!isTauri()) { - throw new Error('Not running in Tauri'); - } - return await invoke('runtime_skill_data_dir', { skill_id: skillId }); + const result = await callCoreRpc<{ path: string }>({ + method: 'openhuman.skills_data_dir', + params: { skill_id: skillId }, + }); + return result.path; } export async function runtimeListSkillOptions(skillId: string): Promise { @@ -1785,29 +1785,30 @@ export async function runtimeSetSkillOption( } export async function runtimeIsSkillEnabled(skillId: string): Promise { - if (!isTauri()) { - throw new Error('Not running in Tauri'); - } - return await invoke('runtime_is_skill_enabled', { skill_id: skillId }); + const result = await callCoreRpc<{ enabled: boolean }>({ + method: 'openhuman.skills_is_enabled', + params: { skill_id: skillId }, + }); + return result.enabled; } export async function runtimeEnableSkill(skillId: string): Promise { - if (!isTauri()) { - throw new Error('Not running in Tauri'); - } - await invoke('runtime_enable_skill', { skill_id: skillId }); + await callCoreRpc({ + method: 'openhuman.skills_enable', + params: { skill_id: skillId }, + }); } export async function runtimeDisableSkill(skillId: string): Promise { - if (!isTauri()) { - throw new Error('Not running in Tauri'); - } - await invoke('runtime_disable_skill', { skill_id: skillId }); + await callCoreRpc({ + method: 'openhuman.skills_disable', + params: { skill_id: skillId }, + }); } export async function runtimeSkillDataStats(skillId: string): Promise { - if (!isTauri()) { - throw new Error('Not running in Tauri'); - } - return await invoke('runtime_skill_data_stats', { skill_id: skillId }); + return await callCoreRpc({ + method: 'openhuman.skills_status', + params: { skill_id: skillId }, + }); } diff --git a/src/openhuman/skills/manifest.rs b/src/openhuman/skills/manifest.rs index 6255a0283..e9eddeeb6 100644 --- a/src/openhuman/skills/manifest.rs +++ b/src/openhuman/skills/manifest.rs @@ -18,7 +18,7 @@ pub struct SkillSetup { } /// Raw manifest as it appears on disk. -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, serde::Serialize)] pub struct SkillManifest { /// Unique skill identifier (e.g., "price-tracker"). pub id: String, diff --git a/src/openhuman/skills/schemas.rs b/src/openhuman/skills/schemas.rs index 15896cb34..cc906dc71 100644 --- a/src/openhuman/skills/schemas.rs +++ b/src/openhuman/skills/schemas.rs @@ -32,6 +32,14 @@ pub fn all_controller_schemas() -> Vec { skills_schema("sync"), skills_schema("call_tool"), skills_schema("rpc"), + skills_schema("discover"), + skills_schema("list"), + skills_schema("data_read"), + skills_schema("data_write"), + skills_schema("data_dir"), + skills_schema("enable"), + skills_schema("disable"), + skills_schema("is_enabled"), ] } @@ -112,6 +120,38 @@ pub fn all_registered_controllers() -> Vec { schema: skills_schema("rpc"), handler: handle_skills_rpc, }, + RegisteredController { + schema: skills_schema("discover"), + handler: handle_skills_discover, + }, + RegisteredController { + schema: skills_schema("list"), + handler: handle_skills_list, + }, + RegisteredController { + schema: skills_schema("data_read"), + handler: handle_skills_data_read, + }, + RegisteredController { + schema: skills_schema("data_write"), + handler: handle_skills_data_write, + }, + RegisteredController { + schema: skills_schema("data_dir"), + handler: handle_skills_data_dir, + }, + RegisteredController { + schema: skills_schema("enable"), + handler: handle_skills_enable, + }, + RegisteredController { + schema: skills_schema("disable"), + handler: handle_skills_disable, + }, + RegisteredController { + schema: skills_schema("is_enabled"), + handler: handle_skills_is_enabled, + }, ] } @@ -391,6 +431,124 @@ fn skills_schema(function: &str) -> ControllerSchema { required: true, }], }, + "discover" => ControllerSchema { + namespace: "skills", + function: "discover", + description: "Discover all available skill manifests from source and workspace directories.", + inputs: vec![], + outputs: vec![FieldSchema { + name: "result", + ty: TypeSchema::Json, + comment: "Array of skill manifests.", + required: true, + }], + }, + "list" => ControllerSchema { + namespace: "skills", + function: "list", + description: "List all registered (running or stopped) skill snapshots.", + inputs: vec![], + outputs: vec![FieldSchema { + name: "result", + ty: TypeSchema::Json, + comment: "Array of skill snapshots.", + required: true, + }], + }, + "data_read" => ControllerSchema { + namespace: "skills", + function: "data_read", + description: "Read a file from a skill's data directory.", + inputs: vec![ + skill_id_input("The skill ID."), + FieldSchema { + name: "filename", + ty: TypeSchema::String, + comment: "Filename to read.", + required: true, + }, + ], + outputs: vec![FieldSchema { + name: "content", + ty: TypeSchema::String, + comment: "File content.", + required: true, + }], + }, + "data_write" => ControllerSchema { + namespace: "skills", + function: "data_write", + description: "Write a file to a skill's data directory.", + inputs: vec![ + skill_id_input("The skill ID."), + FieldSchema { + name: "filename", + ty: TypeSchema::String, + comment: "Filename to write.", + required: true, + }, + FieldSchema { + name: "content", + ty: TypeSchema::String, + comment: "Content to write.", + required: true, + }, + ], + outputs: vec![FieldSchema { + name: "result", + ty: TypeSchema::Json, + comment: "Write acknowledgment.", + required: true, + }], + }, + "data_dir" => ControllerSchema { + namespace: "skills", + function: "data_dir", + description: "Get the data directory path for a skill.", + inputs: vec![skill_id_input("The skill ID.")], + outputs: vec![FieldSchema { + name: "path", + ty: TypeSchema::String, + comment: "Absolute path to the skill's data directory.", + required: true, + }], + }, + "enable" => ControllerSchema { + namespace: "skills", + function: "enable", + description: "Enable a skill (set preference and start it).", + inputs: vec![skill_id_input("The skill ID to enable.")], + outputs: vec![FieldSchema { + name: "result", + ty: TypeSchema::Json, + comment: "Enable acknowledgment.", + required: true, + }], + }, + "disable" => ControllerSchema { + namespace: "skills", + function: "disable", + description: "Disable a skill (set preference and stop it).", + inputs: vec![skill_id_input("The skill ID to disable.")], + outputs: vec![FieldSchema { + name: "result", + ty: TypeSchema::Json, + comment: "Disable acknowledgment.", + required: true, + }], + }, + "is_enabled" => ControllerSchema { + namespace: "skills", + function: "is_enabled", + description: "Check whether a skill is enabled in user preferences.", + inputs: vec![skill_id_input("The skill ID to check.")], + outputs: vec![FieldSchema { + name: "enabled", + ty: TypeSchema::Bool, + comment: "Whether the skill is enabled.", + required: true, + }], + }, _ => ControllerSchema { namespace: "skills", function: "unknown", @@ -616,6 +774,103 @@ fn handle_skills_rpc(params: Map) -> ControllerFuture { }) } +fn handle_skills_discover(_params: Map) -> ControllerFuture { + Box::pin(async move { + let engine = require_engine()?; + let manifests = engine.discover_skills().await?; + serde_json::to_value(&manifests).map_err(|e| e.to_string()) + }) +} + +fn handle_skills_list(_params: Map) -> ControllerFuture { + Box::pin(async move { + let engine = require_engine()?; + let skills = engine.list_skills(); + serde_json::to_value(&skills).map_err(|e| e.to_string()) + }) +} + +fn handle_skills_data_read(params: Map) -> ControllerFuture { + Box::pin(async move { + let skill_id = params + .get("skill_id") + .and_then(|v| v.as_str()) + .ok_or("missing skill_id")? + .to_string(); + let filename = params + .get("filename") + .and_then(|v| v.as_str()) + .ok_or("missing filename")? + .to_string(); + let engine = require_engine()?; + let content = engine.data_read(&skill_id, &filename)?; + Ok(serde_json::json!({ "content": content })) + }) +} + +fn handle_skills_data_write(params: Map) -> ControllerFuture { + Box::pin(async move { + let skill_id = params + .get("skill_id") + .and_then(|v| v.as_str()) + .ok_or("missing skill_id")? + .to_string(); + let filename = params + .get("filename") + .and_then(|v| v.as_str()) + .ok_or("missing filename")? + .to_string(); + let content = params + .get("content") + .and_then(|v| v.as_str()) + .ok_or("missing content")? + .to_string(); + let engine = require_engine()?; + engine.data_write(&skill_id, &filename, &content)?; + Ok(serde_json::json!({ "ok": true })) + }) +} + +fn handle_skills_data_dir(params: Map) -> ControllerFuture { + Box::pin(async move { + let p: SkillIdParams = + serde_json::from_value(Value::Object(params)).map_err(|e| e.to_string())?; + let engine = require_engine()?; + let path = engine.skill_data_dir(&p.skill_id); + Ok(serde_json::json!({ "path": path.display().to_string() })) + }) +} + +fn handle_skills_enable(params: Map) -> ControllerFuture { + Box::pin(async move { + let p: SkillIdParams = + serde_json::from_value(Value::Object(params)).map_err(|e| e.to_string())?; + let engine = require_engine()?; + engine.enable_skill(&p.skill_id).await?; + Ok(serde_json::json!({ "ok": true })) + }) +} + +fn handle_skills_disable(params: Map) -> ControllerFuture { + Box::pin(async move { + let p: SkillIdParams = + serde_json::from_value(Value::Object(params)).map_err(|e| e.to_string())?; + let engine = require_engine()?; + engine.disable_skill(&p.skill_id).await?; + Ok(serde_json::json!({ "ok": true })) + }) +} + +fn handle_skills_is_enabled(params: Map) -> ControllerFuture { + Box::pin(async move { + let p: SkillIdParams = + serde_json::from_value(Value::Object(params)).map_err(|e| e.to_string())?; + let engine = require_engine()?; + let enabled = engine.is_skill_enabled(&p.skill_id); + Ok(serde_json::json!({ "enabled": enabled })) + }) +} + #[cfg(test)] mod tests { use super::*;