chore: update skills submodule to latest commit and enhance SkillsGrid component

- Updated the skills submodule to the latest commit for improved functionality.
- Refactored `SkillsGrid` to better handle skill setup logic, ensuring accurate representation of skills with setup requirements.
- Cleaned up code formatting and improved readability in various components, including `SkillActionButton` and `desktopDeepLinkListener`.
This commit is contained in:
M3gA-Mind
2026-03-02 20:13:05 +05:30
parent 66b67c8482
commit fef36f963d
10 changed files with 72 additions and 72 deletions
+1 -1
Submodule skills updated: 66ad4d7e3f...66ec30016f
+3
View File
@@ -185,6 +185,9 @@ pub struct UnifiedSkillEntry {
pub description: String,
/// Tools exposed by this skill.
pub tools: Vec<ToolDefinition>,
/// Setup configuration from manifest.json (e.g. whether setup is required).
#[serde(skip_serializing_if = "Option::is_none")]
pub setup: Option<crate::runtime::manifest::SkillSetup>,
}
/// Standardized result returned by any skill execution in the unified registry.
+4
View File
@@ -91,6 +91,7 @@ impl UnifiedSkillRegistry {
version: manifest.version.clone().unwrap_or_else(|| "0.1.0".to_string()),
description: manifest.description.clone().unwrap_or_default(),
tools,
setup: manifest.setup.clone(),
});
}
}
@@ -126,6 +127,7 @@ impl UnifiedSkillRegistry {
version: skill.version.clone(),
description: skill.description.clone(),
tools,
setup: None,
});
}
@@ -208,6 +210,7 @@ impl UnifiedSkillRegistry {
version: "1.0.0".to_string(),
description: spec.description,
tools: vec![],
setup: None,
})
}
"openclaw" => {
@@ -221,6 +224,7 @@ impl UnifiedSkillRegistry {
version: "1.0.0".to_string(),
description: spec.description,
tools: vec![],
setup: None,
})
}
other => Err(format!("Unknown skill_type: '{other}'. Use 'alphahuman' or 'openclaw'.")),
+28 -21
View File
@@ -19,16 +19,24 @@ 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 Record<string, unknown> | undefined;
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),
(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: !!(setup && setup.required),
hasSetup,
skill_type: (e.skill_type as 'alphahuman' | 'openclaw') ?? 'alphahuman',
};
}
@@ -47,9 +55,7 @@ function SkillTypeBadge({ type }: { type?: string }) {
return (
<span
className={`text-[10px] font-medium px-1.5 py-0.5 rounded-md ${
isOpenclaw
? 'bg-sage-500/15 text-sage-400'
: 'bg-primary-500/15 text-primary-400'
isOpenclaw ? 'bg-sage-500/15 text-sage-400' : 'bg-primary-500/15 text-primary-400'
}`}>
{type}
</span>
@@ -149,14 +155,19 @@ export default function SkillsGrid() {
const processed: SkillListEntry[] = manifests
.filter(m => !(m.id as string).includes('_'))
.map(m => {
const setup = m.setup as Record<string, unknown> | undefined;
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: !!(setup && setup.required),
hasSetup,
skill_type: 'alphahuman' as const,
};
})
@@ -183,7 +194,6 @@ export default function SkillsGrid() {
};
detectMobile();
refreshSkills();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// Sort skills by connection status (connected first)
@@ -270,11 +280,7 @@ export default function SkillsGrid() {
}}
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">
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
@@ -296,7 +302,7 @@ export default function SkillsGrid() {
description: 'Auto-generated skill demonstrating the unified registry',
skill_type: 'alphahuman',
tool_code:
"return { message: `Hello from generated skill! args=${JSON.stringify(args)}` };",
'return { message: `Hello from generated skill! args=${JSON.stringify(args)}` };',
},
});
await refreshSkills();
@@ -312,7 +318,11 @@ export default function SkillsGrid() {
<span className="opacity-60">Generating</span>
) : (
<>
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg
className="w-3.5 h-3.5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
@@ -387,10 +397,7 @@ export default function SkillsGrid() {
{/* Self-Evolve modal */}
{selfEvolveOpen && (
<SelfEvolveModal
onClose={() => setSelfEvolveOpen(false)}
onSkillCreated={refreshSkills}
/>
<SelfEvolveModal onClose={() => setSelfEvolveOpen(false)} onSkillCreated={refreshSkills} />
)}
{/* Skills Management Modal */}
+1
View File
@@ -92,6 +92,7 @@ export function SkillActionButton({
version: '0.0.0',
description: skill.description,
runtime: 'quickjs',
setup: skill.hasSetup ? { required: true } : undefined,
});
if (skill.hasSetup) {
onOpenModal();
+7 -24
View File
@@ -9,43 +9,26 @@ const INTEGRATION_METADATA_SYNC_EVENT = 'integration:metadata-sync';
const PROVIDER_GOOGLE = 'gmail';
/** Gmail profile shape from skill state (snake_case). */
interface GmailProfileLike {
export interface GmailProfileLike {
email_address: string;
messages_total: number;
threads_total: number;
history_id: string;
}
/** Single email summary from skill state. */
interface GmailEmailSummaryLike {
id: string;
threadId: string;
snippet?: string;
subject?: string;
from?: string;
date?: string;
}
/** Gmail skill state slice we care about for metadata sync. */
export interface GmailStateForSync {
profile?: GmailProfileLike | null;
emails?: GmailEmailSummaryLike[] | null;
}
/**
* Emit `integration:metadata-sync` with Gmail profile and emails so the
* backend can merge them into the user's Google OAuth integration.
* No-op when profile is missing or not in Tauri.
*/
export function syncGmailMetadataToBackend(gmailState: GmailStateForSync | undefined): void {
if (!gmailState?.profile || typeof gmailState.profile !== 'object') return;
export function syncGmailMetadataToBackend(gmailState: GmailProfileLike | undefined): void {
if (!gmailState) return;
const profile = gmailState.profile as GmailProfileLike;
const metadata: Record<string, unknown> = {
email_address: profile.email_address,
messages_total: profile.messages_total,
threads_total: profile.threads_total,
history_id: profile.history_id,
email_address: gmailState.email_address,
messages_total: gmailState.messages_total,
threads_total: gmailState.threads_total,
history_id: gmailState.history_id,
};
// if (Array.isArray(gmailState.emails) && gmailState.emails.length > 0) {
+8 -1
View File
@@ -81,7 +81,14 @@ class SkillManager {
// Dead runtime — clean up
this.runtimes.delete(skillId);
}
// Ensure the skill is registered in Redux before dispatching status updates.
// Self-evolved skills are started directly by the Rust engine and never go
// through registerSkill(), so state.skills[skillId] is undefined. Every
// setSkillStatus / setSkillSetupComplete / setSkillTools reducer silently
// no-ops when the key is missing, making the Enable button appear broken.
if (!store.getState().skills.skills[skillId]) {
store.dispatch(addSkill({ manifest }));
}
store.dispatch(setSkillStatus({ skillId, status: "starting" }));
const runtime = new SkillRuntime(manifest);
+3 -13
View File
@@ -9,18 +9,13 @@ import { listen } from '@tauri-apps/api/event';
import { type ReactNode, useEffect, useRef } from 'react';
import {
type GmailStateForSync,
type GmailProfileLike,
syncGmailMetadataToBackend,
} from '../lib/gmail/services/metadataSync';
import { skillManager } from '../lib/skills/manager';
import type { SkillManifest } from '../lib/skills/types';
import { buildManualSentryEvent, enqueueError } from '../services/errorReportQueue';
import {
type GmailEmailSummary,
type GmailProfile,
setGmailEmails,
setGmailProfile,
} from '../store/gmailSlice';
import { type GmailProfile, setGmailProfile } from '../store/gmailSlice';
import { useAppDispatch, useAppSelector } from '../store/hooks';
import { setSkillError, setSkillState, setSkillStatus } from '../store/skillsSlice';
import { DEV_AUTO_LOAD_SKILL, IS_DEV } from '../utils/config';
@@ -88,12 +83,7 @@ function syncGmailStateToSlice(
: null
)
);
dispatch(
setGmailEmails(
Array.isArray(gmailState.emails) ? (gmailState.emails as GmailEmailSummary[]) : []
)
);
syncGmailMetadataToBackend(gmailState.profile as GmailStateForSync);
syncGmailMetadataToBackend(gmailState.profile as GmailProfileLike);
}
export default function SkillProvider({ children }: { children: ReactNode }) {
+9 -11
View File
@@ -161,8 +161,7 @@ const handleOAuthDeepLink = async (parsed: URL) => {
try {
keyForBackend = hexToBase64(trimmedHex);
} catch (e) {
const msg =
'[DeepLink] Cannot fetch integration tokens: encryption key conversion failed';
const msg = '[DeepLink] Cannot fetch integration tokens: encryption key conversion failed';
console.error(msg, { userId, encryptionKeyHex, error: e });
const err = e instanceof Error ? e : new Error(String(e));
enqueueError({
@@ -222,16 +221,15 @@ const handleOAuthDeepLink = async (parsed: URL) => {
// For Gmail, pass decrypted access token so the skill uses it instead of the proxy
let extraCredential: { accessToken?: string } | undefined;
if (skillId === 'gmail') {
try {
const decryptedJson = await decryptIntegrationTokens(response.data.encrypted, trimmedHex);
const payload = JSON.parse(decryptedJson) as IntegrationTokensPayload;
if (payload.accessToken) {
extraCredential = { accessToken: payload.accessToken };
}
} catch (e) {
console.warn('[DeepLink] Could not decrypt Gmail token for skill:', e);
try {
const decryptedJson = await decryptIntegrationTokens(response.data.encrypted, trimmedHex);
const payload = JSON.parse(decryptedJson) as IntegrationTokensPayload;
if (payload.accessToken) {
extraCredential = { accessToken: payload.accessToken };
}
} catch (e) {
console.warn('[DeepLink] Could not decrypt Gmail token for skill:', e);
}
await skillManager.notifyOAuthComplete(skillId, integrationId, undefined, extraCredential);
+8 -1
View File
@@ -27,7 +27,14 @@ export const isTauri = (): boolean => {
const isTauriEnv = coreIsTauri();
const windowTauri = typeof window !== 'undefined' ? !!window.__TAURI__ : 'undefined';
const userAgent = typeof navigator !== 'undefined' ? navigator.userAgent : 'undefined';
console.log('[TauriSocket] isTauri() check:', isTauriEnv, 'window.__TAURI__:', windowTauri, 'userAgent:', userAgent);
console.log(
'[TauriSocket] isTauri() check:',
isTauriEnv,
'window.__TAURI__:',
windowTauri,
'userAgent:',
userAgent
);
return isTauriEnv;
};