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.
This commit is contained in:
Steven Enamakel
2026-03-30 15:36:21 -07:00
parent 9261561f25
commit 33421503a9
3 changed files with 305 additions and 49 deletions
+49 -48
View File
@@ -1698,31 +1698,29 @@ export async function openhumanAutocompleteClearHistory(): Promise<
}
export async function runtimeListSkills(): Promise<SkillSnapshot[]> {
if (!isTauri()) {
throw new Error('Not running in Tauri');
}
return await invoke('runtime_list_skills');
return await callCoreRpc<SkillSnapshot[]>({
method: 'openhuman.skills_list',
});
}
export async function runtimeDiscoverSkills(): Promise<RuntimeDiscoveredSkill[]> {
if (!isTauri()) {
throw new Error('Not running in Tauri');
}
return await invoke('runtime_discover_skills');
return await callCoreRpc<RuntimeDiscoveredSkill[]>({
method: 'openhuman.skills_discover',
});
}
export async function runtimeStartSkill(skillId: string): Promise<SkillSnapshot> {
if (!isTauri()) {
throw new Error('Not running in Tauri');
}
return await invoke('runtime_start_skill', { skill_id: skillId });
return await callCoreRpc<SkillSnapshot>({
method: 'openhuman.skills_start',
params: { skill_id: skillId },
});
}
export async function runtimeStopSkill(skillId: string): Promise<void> {
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<T = unknown>(
@@ -1730,17 +1728,18 @@ export async function runtimeRpc<T = unknown>(
method: string,
params: Record<string, unknown> = {}
): Promise<T> {
if (!isTauri()) {
throw new Error('Not running in Tauri');
}
return await invoke<T>('runtime_rpc', { skill_id: skillId, method, params });
return await callCoreRpc<T>({
method: 'openhuman.skills_rpc',
params: { skill_id: skillId, method, params },
});
}
export async function runtimeSkillDataRead(skillId: string, filename: string): Promise<string> {
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<void> {
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<string> {
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<RuntimeSkillOption[]> {
@@ -1785,29 +1785,30 @@ export async function runtimeSetSkillOption(
}
export async function runtimeIsSkillEnabled(skillId: string): Promise<boolean> {
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<void> {
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<void> {
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<RuntimeSkillDataStats> {
if (!isTauri()) {
throw new Error('Not running in Tauri');
}
return await invoke('runtime_skill_data_stats', { skill_id: skillId });
return await callCoreRpc<RuntimeSkillDataStats>({
method: 'openhuman.skills_status',
params: { skill_id: skillId },
});
}
+1 -1
View File
@@ -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,
+255
View File
@@ -32,6 +32,14 @@ pub fn all_controller_schemas() -> Vec<ControllerSchema> {
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<RegisteredController> {
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<String, Value>) -> ControllerFuture {
})
}
fn handle_skills_discover(_params: Map<String, Value>) -> 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<String, Value>) -> 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<String, Value>) -> 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<String, Value>) -> 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<String, Value>) -> 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<String, Value>) -> 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<String, Value>) -> 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<String, Value>) -> 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::*;