mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-29 14:02:19 +00:00
* Implement data synchronization feature in SkillManagementPanel - Added a new `handleSync` function to trigger manual synchronization for skills, enhancing user control over data updates. - Integrated skill state management to track synchronization progress, errors, and completion status. - Updated the UI to display synchronization status, including progress indicators and error messages, improving user feedback during sync operations. - Enhanced the SkillManager and SkillRuntime classes to support the new sync functionality, ensuring proper handling of sync requests and lifecycle hooks. Updated subproject commit reference in the skills directory. * Implement Conversations UI with two-panel thread layout and resizable sidebar Add full Conversations feature: thread types, API service, Redux slice, and a two-panel page with a draggable resize handle. Uncomment Conversations nav item in MiniSidebar and enable prefix-based active state matching. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add Intelligence page and integrate intelligence stats hook - Introduced a new Intelligence page with a skill management interface, allowing users to view and manage skills. - Implemented the `useIntelligenceStats` hook to fetch and display session statistics, memory files, and entity counts. - Enhanced the SkillsGrid component to utilize shared skill icons and status displays, improving UI consistency. - Added functionality for skill synchronization and management, including action buttons for skill setup and syncing. - Updated the Conversations page to support sending messages with optimistic UI updates, enhancing user experience. This commit lays the groundwork for a more interactive and informative intelligence management feature. * Update subproject commit reference in skills directory and enhance fetch handling in bootstrap.js - Updated the subproject commit reference in the skills directory to the latest version. - Modified the fetch implementation in bootstrap.js to ensure options are sent as a JSON string and to parse the JSON response, improving data handling and error management. * Update subproject commit reference in skills directory and comment out Conversations nav item in MiniSidebar - Updated the subproject commit reference in the skills directory to the latest version. - Commented out the Conversations navigation item in MiniSidebar for future implementation. --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
229 lines
6.0 KiB
TypeScript
229 lines
6.0 KiB
TypeScript
/**
|
|
* Skill runtime — higher-level wrapper around SkillTransport
|
|
* for managing a single skill's lifecycle.
|
|
*
|
|
* Skills are managed by the Rust QuickJS runtime engine.
|
|
* This class wraps the transport layer to provide the same API
|
|
* that the SkillManager expects.
|
|
*/
|
|
|
|
import { invoke } from "@tauri-apps/api/core";
|
|
import { SkillTransport, type ReverseRpcHandler } from "./transport";
|
|
import type {
|
|
SkillManifest,
|
|
SetupStep,
|
|
SetupResult,
|
|
SkillToolDefinition,
|
|
SkillOptionDefinition,
|
|
PingResult,
|
|
} from "./types";
|
|
|
|
export class SkillRuntime {
|
|
private transport: SkillTransport;
|
|
private manifest: SkillManifest;
|
|
private _started = false;
|
|
|
|
constructor(manifest: SkillManifest) {
|
|
this.transport = new SkillTransport();
|
|
this.manifest = manifest;
|
|
}
|
|
|
|
/**
|
|
* Set a handler for reverse RPC calls from the skill process.
|
|
* Reverse RPC is handled by bridge globals, so this
|
|
* is kept for API compatibility.
|
|
*/
|
|
onReverseRpc(handler: ReverseRpcHandler): void {
|
|
this.transport.onReverseRpc(handler);
|
|
}
|
|
|
|
|
|
/**
|
|
* Start the skill in the QuickJS runtime engine.
|
|
* The Rust engine handles process management, so we just tell it to start
|
|
* and then initialize the transport for RPC routing.
|
|
*/
|
|
async start(): Promise<void> {
|
|
// Start the skill in the Rust QuickJS runtime
|
|
await invoke("runtime_start_skill", { skillId: this.manifest.id });
|
|
|
|
// Initialize the transport for RPC routing
|
|
await this.transport.start(this.manifest.id);
|
|
this._started = true;
|
|
}
|
|
|
|
/**
|
|
* Send skill/load with manifest + data dir.
|
|
* Loading is handled by the Rust engine during start_skill,
|
|
* so this sends a no-op skill/load RPC for protocol compatibility.
|
|
*/
|
|
async load(additionalParams?: Record<string, unknown>): Promise<void> {
|
|
const dataDir = await invoke<string>("runtime_skill_data_dir", {
|
|
skillId: this.manifest.id,
|
|
});
|
|
await this.transport.request("skill/load", {
|
|
manifest: this.manifest,
|
|
dataDir,
|
|
...(additionalParams || {}),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Start the setup flow. Returns the first SetupStep.
|
|
* Returns null if the skill does not implement setup/start (e.g. OAuth-only skills).
|
|
*/
|
|
async setupStart(): Promise<SetupStep | null> {
|
|
console.log("[SkillRuntime] setupStart", this.skillId);
|
|
const result = await this.transport.request<{ step: SetupStep } | null>(
|
|
"setup/start"
|
|
);
|
|
console.log("[SkillRuntime] setupStart result", this.skillId, result);
|
|
if (!result || !result.step) {
|
|
return null;
|
|
}
|
|
return result.step;
|
|
}
|
|
|
|
/**
|
|
* Submit a setup step. Returns SetupResult with next/error/complete.
|
|
*/
|
|
async setupSubmit(
|
|
stepId: string,
|
|
values: Record<string, unknown>
|
|
): Promise<SetupResult> {
|
|
return this.transport.request<SetupResult>("setup/submit", {
|
|
stepId,
|
|
values,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Cancel the setup flow.
|
|
*/
|
|
async setupCancel(): Promise<void> {
|
|
await this.transport.request("setup/cancel");
|
|
}
|
|
|
|
/**
|
|
* List available tools.
|
|
*/
|
|
async listTools(): Promise<SkillToolDefinition[]> {
|
|
const result = await this.transport.request<{
|
|
tools: SkillToolDefinition[];
|
|
}>("tools/list");
|
|
return result.tools;
|
|
}
|
|
|
|
/**
|
|
* List runtime-configurable options with current values.
|
|
*/
|
|
async listOptions(): Promise<SkillOptionDefinition[]> {
|
|
const result = await this.transport.request<{
|
|
options: SkillOptionDefinition[];
|
|
}>("options/list");
|
|
return result.options;
|
|
}
|
|
|
|
/**
|
|
* Set a single option value.
|
|
*/
|
|
async setOption(name: string, value: unknown): Promise<void> {
|
|
await this.transport.request("options/set", { name, value });
|
|
}
|
|
|
|
/**
|
|
* Call a tool by name with arguments.
|
|
*/
|
|
async callTool(
|
|
name: string,
|
|
args: Record<string, unknown>
|
|
): Promise<{
|
|
content: Array<{ type: string; text: string }>;
|
|
isError: boolean;
|
|
}> {
|
|
return this.transport.request("tools/call", { name, arguments: args });
|
|
}
|
|
|
|
/**
|
|
* Ping the skill to verify its external service connection is healthy.
|
|
* Returns null if the skill doesn't implement onPing (backward compatible).
|
|
*/
|
|
async ping(): Promise<PingResult | null> {
|
|
return this.transport.request<PingResult | null>("skill/ping");
|
|
}
|
|
|
|
/**
|
|
* Trigger the skill's onSync lifecycle hook.
|
|
* Progress updates flow via published state fields, not the RPC response.
|
|
*/
|
|
async triggerSync(): Promise<unknown> {
|
|
return this.transport.request("skill/sync");
|
|
}
|
|
|
|
/**
|
|
* Trigger periodic tick.
|
|
*/
|
|
async tick(): Promise<void> {
|
|
await this.transport.request("skill/tick");
|
|
}
|
|
|
|
/**
|
|
* Notify skill of session start.
|
|
*/
|
|
async sessionStart(sessionId: string): Promise<void> {
|
|
await this.transport.request("skill/sessionStart", { sessionId });
|
|
}
|
|
|
|
/**
|
|
* Notify skill of session end.
|
|
*/
|
|
async sessionEnd(sessionId: string): Promise<void> {
|
|
await this.transport.request("skill/sessionEnd", { sessionId });
|
|
}
|
|
|
|
/**
|
|
* Notify the skill that OAuth completed successfully.
|
|
* Sets the credential on the bridge and calls onOAuthComplete.
|
|
*/
|
|
async oauthComplete(args: {
|
|
credentialId: string;
|
|
provider: string;
|
|
grantedScopes?: string[];
|
|
accountLabel?: string;
|
|
}): Promise<void> {
|
|
await this.transport.request("oauth/complete", args as unknown as Record<string, unknown>);
|
|
}
|
|
|
|
/**
|
|
* Notify the skill that an OAuth credential was revoked.
|
|
*/
|
|
async oauthRevoked(args: {
|
|
credentialId: string;
|
|
reason: string;
|
|
}): Promise<void> {
|
|
await this.transport.request("oauth/revoked", args as unknown as Record<string, unknown>);
|
|
}
|
|
|
|
/**
|
|
* Unload and stop the skill.
|
|
*/
|
|
async stop(): Promise<void> {
|
|
if (!this._started) return;
|
|
try {
|
|
await this.transport.request("skill/shutdown");
|
|
} catch {
|
|
// Skill may already be stopped
|
|
}
|
|
await this.transport.kill();
|
|
this._started = false;
|
|
}
|
|
|
|
get isRunning(): boolean {
|
|
return this._started && this.transport.isRunning;
|
|
}
|
|
|
|
get skillId(): string {
|
|
return this.manifest.id;
|
|
}
|
|
}
|