From ec865593825f1621dcdeea2533aae29bc399af76 Mon Sep 17 00:00:00 2001 From: M3gA-Mind Date: Wed, 25 Mar 2026 17:19:37 +0530 Subject: [PATCH] Update @tauri-apps/api to version 2.10.0 in package.json and yarn.lock; add detailed documentation for memory inference flow in new markdown file; refactor Login component to remove unnecessary isWeb prop; enhance Skills component with improved skill filtering and synchronization UI. --- .../rules/17-skills-memory-inference-flow.md | 275 ++++++++++++++++++ package.json | 2 +- src-tauri/src/commands/chat.rs | 63 +++- src-tauri/src/memory/mod.rs | 118 ++++++-- src-tauri/src/runtime/qjs_skill_instance.rs | 45 +-- src/AppRoutes.tsx | 4 +- src/pages/Login.tsx | 6 +- src/pages/Skills.tsx | 17 +- yarn.lock | 38 +-- 9 files changed, 456 insertions(+), 112 deletions(-) create mode 100644 .claude/rules/17-skills-memory-inference-flow.md diff --git a/.claude/rules/17-skills-memory-inference-flow.md b/.claude/rules/17-skills-memory-inference-flow.md new file mode 100644 index 000000000..984017d4f --- /dev/null +++ b/.claude/rules/17-skills-memory-inference-flow.md @@ -0,0 +1,275 @@ +# Skills → Memory Layer → Agent Inference: Full Flow + +## Overview + +This document traces the complete data flow from skill discovery and OAuth/sync events, through +the TinyHumans Neocortex memory layer (tinyhumansai SDK), and into the Rust-side agentic +inference loop — showing exactly how skill data is written to and read from memory and how it +reaches the LLM context at inference time. + +--- + +## 1. Skill Discovery & Lifecycle (SkillProvider.tsx) + +**File**: `src/providers/SkillProvider.tsx` + +On app mount (when a JWT token is present) `SkillProvider` calls +`invoke('runtime_discover_skills')` → the Rust runtime scans +`skills/skills/{skill-id}/manifest.json` from the git submodule and returns a manifest list. + +``` +Token present + → discoverSkills() + → invoke('runtime_discover_skills') // Rust: qjs_engine.rs:184 + → reads skills/skills/*/manifest.json + → filters: is_javascript() && supports_current_platform() + → returns manifests[] + → skillManager.registerSkill(manifest) // in-memory registry + → for each manifest with setupComplete: + skillManager.startSkill(manifest) // starts V8/QuickJS instance +``` + +Two Tauri event listeners run continuously: + +| Event | What it does | +|---|---| +| `skill-state-changed` | Dispatches `setSkillState` into Redux `skillsSlice.skillStates[skillId]` | +| `runtime:skill-status-changed` | Updates `skillsSlice.skills[skillId].status`; surfaces errors | + +--- + +## 2. Memory Client Initialisation + +**File**: `src-tauri/src/memory/mod.rs` +**Tauri command**: `init_memory_client` (called by frontend after auth) + +The `MemoryClient` wraps the `TinyHumansMemoryClient` from the `tinyhumansai` Rust crate. +It is constructed with the user's JWT (`authSlice.token`) and stored as `Arc` +in `MemoryState` (a `Mutex>`). + +Base URL resolution (in priority order): +1. `ALPHAHUMAN_BASE_URL` env var +2. `TINYHUMANS_BASE_URL` env var +3. SDK default + +--- + +## 3. Skill → Memory Sync: Two Write Paths + +Both trigger inside `src-tauri/src/runtime/qjs_skill_instance.rs`. + +### 3a. OAuth Completion (`skill/oauth-complete`) + +When a user connects a skill via OAuth, the JS runtime calls back with `skill/oauth-complete`. +After the OAuth flow completes the skill's **ops state** (data published via `state.set()`) is +snapshotted and stored to memory: + +``` +skill/oauth-complete handler + → handle_js_call(rt, ctx, "onOAuthComplete", params) // runs skill JS + → ops_state.read().data.clone() // snapshot skill state + → tokio::spawn (fire-and-forget): + MemoryClient::store_skill_sync( + skill_id = e.g. "gmail" + integration_id = params["integrationId"] // e.g. user email + title = "{skill} OAuth sync — {integrationId}" + content = JSON snapshot of ops state + namespace = "skill:{skill_id}:{integration_id}" + ) + → tinyhumansai::TinyHumansMemoryClient::insert_memory(InsertMemoryParams { ... }) + → HTTP POST to TinyHumans Neocortex API +``` + +### 3b. Periodic Sync (`skill/sync`) + +The runtime fires `skill/sync` events on a cron schedule. The flow is identical to OAuth +completion but uses `integration_id = "default"` and title `"{skill} periodic sync"`: + +``` +skill/sync handler + → handle_js_call(rt, ctx, "onSync", "{}") + → ops_state.read().data.clone() + → tokio::spawn: + MemoryClient::store_skill_sync( + skill_id = e.g. "notion" + integration_id = "default" + namespace = "skill:notion:default" + content = JSON snapshot + ) +``` + +### Namespace Pattern + +All skill memories are stored under `skill:{skill_id}:{integration_id}`. +Examples: `skill:gmail:user@example.com`, `skill:notion:default`. + +--- + +## 4. Memory Operations Reference + +**File**: `src-tauri/src/memory/mod.rs` + +| Method | SDK call | When used | +|---|---|---| +| `store_skill_sync(...)` | `insert_memory` | OAuth complete, periodic sync | +| `query_skill_context(...)` | `query_memory` | RAG query — fetch relevant chunks for a user question | +| `recall_skill_context(...)` | `recall_memory` | Recall synthesised summary from Master node | +| `clear_skill_memory(...)` | `delete_memory` | OAuth revoke / disconnect | + +--- + +## 5. Conversation → Inference: Two Code Paths + +**File**: `src/pages/Conversations.tsx` + +`useRustChat()` returns `true` when running in Tauri (desktop). This selects between two paths: + +``` +handleSendMessage(text) + ├─ rustChat == true → Rust path (invoke chat_send) + └─ rustChat == false → Web path (handleSendMessageWeb — TypeScript loop) +``` + +### 5a. Rust Path (Desktop) + +``` +chatSend({ threadId, message, model, authToken, backendUrl, messages, notionContext }) + → invoke('chat_send') // src-tauri/src/commands/chat.rs:359 + → spawns background task + → chat_send_inner(...) +``` + +Completion events flow back over Tauri events: + +| Event | Frontend handler | +|---|---| +| `chat:tool_call` | shows active tool indicator | +| `chat:tool_result` | clears tool indicator | +| `chat:done` | `dispatch(addInferenceResponse(...))` | +| `chat:error` | shows error, clears loading state | + +### 5b. Web/Fallback Path + +Used when not running in Tauri (browser). Runs the agentic loop entirely in TypeScript: +- Calls `inferenceApi.createChatCompletion(request)` directly +- Executes tools via `skillManager.callTool(skillId, toolName, args)` +- Both paths share the same 5-round `MAX_TOOL_ROUNDS` limit and `{skillId}__{toolName}` naming convention + +--- + +## 6. Rust Agentic Loop in Detail + +**File**: `src-tauri/src/commands/chat.rs` — `chat_send_inner()` + +``` +Step 1: Load OpenClaw context + → load_openclaw_context(app) + → reads ai/SOUL.md, IDENTITY.md, AGENTS.md, USER.md, BOOTSTRAP.md, MEMORY.md, TOOLS.md + → cached in static AI_CONFIG_CACHE (cleared on restart) + → truncated to MAX_CONTEXT_CHARS (20,000 chars) + +Step 2: Recall memory context + → MemoryClient::recall_skill_context("conversations", thread_id, 10) + → tinyhumansai recall_memory(namespace="skill:conversations:{thread_id}") + → returns synthesised summary string or None + +Step 3: Build processed user message + processed = user_message + if openclaw_context → prepend as "## Project Context\n...\n\nUser message: {processed}" + if memory_context → prepend as "[MEMORY_CONTEXT]\n{mem}\n[/MEMORY_CONTEXT]\n\n{processed}" + if notion_context → prepend as "{notionContext}\n\n{processed}" + +Step 4: Build messages array + → history (ChatMessagePayload[]) + processed user message + +Step 5: Discover tools + → engine.all_tools() + → returns all tools from running skills + → namespaced: "{skill_id}__{tool_name}" + → formatted as OpenAI function-calling schema + +Step 6: Agentic loop (max 5 rounds) + for round in 0..MAX_TOOL_ROUNDS: + POST {backend_url}/openai/v1/chat/completions + body: { model, messages, tools, tool_choice: "auto" } + timeout: 120s + auth: Bearer {auth_token} + + if finish_reason == "tool_calls": + emit chat:tool_call + engine.call_tool(skill_id, tool_name, args) // 60s timeout + → QuickJS/V8 runtime executes skill JS tool handler + emit chat:tool_result + append tool result to messages + continue loop + + else (finish_reason == "stop"): + emit chat:done { full_response, rounds_used, token_counts } + return Ok(()) +``` + +--- + +## 7. Tool Execution: Rust → Skill JS + +**File**: `src-tauri/src/runtime/qjs_engine.rs` — `call_tool(skill_id, tool_name, args)` + +The Rust runtime routes `call_tool` into the running QuickJS/V8 skill instance: +- Serialises `args` as JSON +- Calls the JS tool handler registered by the skill +- Returns `ToolCallResult { content: Vec, is_error: bool }` +- Text content is extracted and appended as the `tool` role message in the loop + +--- + +## 8. End-to-End Flow Summary + +``` +User types message (Conversations.tsx) + │ + ├─ [Web path only] invoke('recall_memory') → TinyHumans API (recall) + │ returns synthesised context + ├─ buildNotionContext() → from Redux skillStates.notion + │ + ▼ +invoke('chat_send') [Rust/desktop path] + │ + ▼ +Rust: chat_send_inner() + ├─ load_openclaw_context() → ai/*.md files (cached) + ├─ recall_skill_context("conversations", tid) → TinyHumans API (recall) + ├─ Build prompt: [OpenClaw] + [MEMORY] + [Notion] + user_message + ├─ discover_tools() → all running skill tools + │ + └─ Agentic loop (≤5 rounds): + POST /openai/v1/chat/completions → Backend LLM (neocortex-mk1) + │ + ├─ tool_calls → engine.call_tool() → QuickJS/V8 skill instance + │ └─ JS tool handler + │ └─ returns result string + │ └─ appended as tool message + │ + └─ stop → emit chat:done + └─ Frontend: dispatch(addInferenceResponse(...)) + +Separately (async, fire-and-forget): + Skill onSync() / onOAuthComplete() + → MemoryClient::store_skill_sync() + → TinyHumans insert_memory (namespace: skill:{id}:{integrationId}) +``` + +--- + +## Key Files Reference + +| File | Role | +|---|---| +| `src/providers/SkillProvider.tsx` | Discovery, lifecycle, Redux state sync | +| `src/pages/Conversations.tsx` | Message send, both code paths, Notion context | +| `src/services/chatService.ts` | `chatSend()`, `chatCancel()`, `useRustChat()` | +| `src-tauri/src/commands/chat.rs` | Rust agentic loop, context assembly, tool dispatch | +| `src-tauri/src/commands/memory.rs` | `recall_memory` Tauri command | +| `src-tauri/src/memory/mod.rs` | `MemoryClient` wrapping tinyhumansai SDK | +| `src-tauri/src/runtime/qjs_skill_instance.rs` | Skill sync → memory write triggers | +| `src-tauri/src/runtime/qjs_engine.rs` | `discover_skills()`, `call_tool()`, `all_tools()` | +| `skills/skills/*/manifest.json` | Skill metadata (git submodule) | diff --git a/package.json b/package.json index 926e99bf3..aac5eab27 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "@scure/bip32": "^2.0.1", "@scure/bip39": "^2.0.1", "@sentry/react": "^10.38.0", - "@tauri-apps/api": "2.9.1", + "@tauri-apps/api": "^2.10.0", "@tauri-apps/plugin-deep-link": "^2", "@tauri-apps/plugin-opener": "^2", "@tauri-apps/plugin-os": "^2.3.2", diff --git a/src-tauri/src/commands/chat.rs b/src-tauri/src/commands/chat.rs index 4104f2933..728d469dd 100644 --- a/src-tauri/src/commands/chat.rs +++ b/src-tauri/src/commands/chat.rs @@ -525,21 +525,76 @@ async fn chat_send_inner( let openclaw_context = load_openclaw_context(app); // ── Step 2: Recall memory context ─────────────────────────────────── + log::info!("[chat] Recalling conversation memory (thread_id={thread_id})"); let memory_context: Option = if let Some(ref mem) = memory_client { match mem .recall_skill_context("conversations", thread_id, 10) .await { - Ok(ctx) => ctx, + Ok(ctx) => { + log::info!( + "[chat] Conversation memory recall: has_data={}, len={}", + ctx.is_some(), + ctx.as_deref().map(|s| s.len()).unwrap_or(0) + ); + if let Some(ref data) = ctx { + log::debug!("[chat] Conversation memory content:\n{}", data); + } + ctx + } Err(e) => { - log::warn!("[chat] Memory recall failed: {}", e); + log::warn!("[chat] Conversation memory recall failed: {}", e); None } } } else { + log::info!("[chat] No memory client — skipping conversation memory recall"); None }; + // ── Step 2b: Recall skill contexts ────────────────────────────────── + let skill_ids: std::collections::HashSet = engine + .all_tools() + .into_iter() + .map(|(skill_id, _)| skill_id) + .collect(); + + log::info!("[chat] Recalling skill contexts for {} skill(s): {:?}", skill_ids.len(), skill_ids); + + let mut skill_contexts: Vec = Vec::new(); + for sid in &skill_ids { + if let Some(ref mem) = memory_client { + log::info!("[chat] Recalling memory for skill={sid}"); + match mem.recall_skill_context(sid, sid, 10).await { + Ok(Some(ctx)) => { + log::info!( + "[chat] Skill memory recall ok: skill={sid}, len={}", + ctx.len() + ); + log::debug!("[chat] Skill memory content (skill={sid}):\n{}", ctx); + skill_contexts.push(format!( + "[{}_CONTEXT]\n{}\n[/{}_CONTEXT]", + sid.to_uppercase(), + ctx, + sid.to_uppercase() + )); + } + Ok(None) => { + log::info!("[chat] Skill memory recall: no data for skill={sid}"); + } + Err(e) => { + log::warn!("[chat] Skill memory recall failed for skill={sid}: {}", e); + } + } + } + } + + log::info!( + "[chat] Context assembly: conversation_memory={}, skill_contexts={}", + memory_context.is_some(), + skill_contexts.len() + ); + // ── Step 3: Build processed user message ──────────────────────────── let mut processed = user_message.to_string(); @@ -554,6 +609,10 @@ async fn chat_send_inner( ); } + if !skill_contexts.is_empty() { + processed = format!("{}\n\n{}", skill_contexts.join("\n\n"), processed); + } + if let Some(ref notion) = notion_context { processed = format!("{}\n\n{}", notion, processed); } diff --git a/src-tauri/src/memory/mod.rs b/src-tauri/src/memory/mod.rs index ba44bf9ad..6a625c9cf 100644 --- a/src-tauri/src/memory/mod.rs +++ b/src-tauri/src/memory/mod.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use tinyhumansai::{ DeleteMemoryParams, InsertMemoryParams, Priority, QueryMemoryParams, RecallMemoryParams, - SourceType, TinyHumanConfig, TinyHumanMemoryClient, + SourceType, TinyHumanConfig, TinyHumansMemoryClient, }; use uuid::Uuid; @@ -53,7 +53,9 @@ impl MemoryClient { /// Store a skill data-sync result. /// - /// Namespace pattern: `skill:{skill_id}:{integration_id}` + /// Inserts the document then polls `ingestion_job_status` every 30 s until + /// the job reaches `completed` (or `failed`/`error`). Returns only after the + /// ingestion job is confirmed complete. pub async fn store_skill_sync( &self, skill_id: &str, @@ -67,16 +69,23 @@ impl MemoryClient { updated_at: Option, document_id: Option, ) -> Result<(), String> { - let namespace = format!("skill:{skill_id}:{integration_id}"); - log::info!("[memory] store_skill_sync: entry (namespace={namespace}, title={title:?}, content_len={})", content.len()); - log::debug!( - "[memory] store_skill_sync: payload → namespace={namespace} | title={title} | content={}", - content + let namespace = skill_id.to_string(); + log::info!( + "[memory] store_skill_sync: entry (namespace={namespace}, title={title:?}, content_len={})", + content.len() ); - log::info!("[memory] insert_memory: calling SDK (namespace={namespace}, title={title:?}), content_len={}", content.len()); - let result = self.inner + + let document_id_final = document_id.unwrap_or_else(|| Uuid::new_v4().to_string()); + + log::info!( + "[memory] insert_memory: calling SDK (namespace={namespace}, title={title:?}), content_len={}", + content.len() + ); + + let insert_resp = self + .inner .insert_memory(InsertMemoryParams { - document_id: Some(Uuid::new_v4().to_string()), + document_id: Some(document_id_final), title: title.to_string(), content: content.to_string(), namespace: namespace.clone(), @@ -85,22 +94,86 @@ impl MemoryClient { priority, created_at, updated_at, - document_id, ..Default::default() }) .await - .map(|_| { - log::info!("[memory] insert_memory: success (namespace={namespace}, title={title:?})"); - }) .map_err(|e| { - log::warn!("[memory] insert_memory: SDK error — kind={:?} msg={e}", classify_insert_error(&e)); + log::warn!( + "[memory] insert_memory: SDK error — kind={:?} msg={e}", + classify_insert_error(&e) + ); format!("Memory insert failed: {e}") - }); - match &result { - Ok(()) => log::info!("[memory] store_skill_sync: exit — ok (namespace={namespace})"), - Err(e) => log::warn!("[memory] store_skill_sync: exit — error (namespace={namespace}): {e}"), + })?; + + log::info!( + "[memory] insert_memory: accepted (namespace={namespace}, status={:?}, job_id={:?})", + insert_resp.data.status, + insert_resp.data.job_id + ); + + // If the API returned a job_id, poll until the job completes. + if let Some(job_id) = insert_resp.data.job_id { + log::info!("[memory] ingestion job queued (job_id={job_id}), polling every 30s..."); + + loop { + tokio::time::sleep(std::time::Duration::from_secs(30)).await; + + match self.inner.ingestion_job_status(&job_id).await { + Ok(status_resp) => { + let state = status_resp + .data + .state + .as_deref() + .unwrap_or("unknown"); + + log::info!( + "[memory] ingestion job status: job_id={job_id}, state={state}, \ + attempts={:?}, completed_at={:?}", + status_resp.data.attempts, + status_resp.data.completed_at + ); + + match state { + "completed" => { + log::info!( + "[memory] ingestion job completed (job_id={job_id}, namespace={namespace})" + ); + break; + } + "failed" | "error" => { + let err_msg = status_resp + .data + .error + .unwrap_or_else(|| format!("job state={state}")); + log::warn!( + "[memory] ingestion job failed: job_id={job_id}, error={err_msg}" + ); + log::warn!( + "[memory] store_skill_sync: exit — ingestion failed (namespace={namespace})" + ); + return Err(format!("Ingestion job failed: {err_msg}")); + } + _ => { + // pending / processing / queued — keep waiting + log::info!( + "[memory] ingestion job still in progress (state={state}), waiting 30s..." + ); + } + } + } + Err(e) => { + log::warn!( + "[memory] ingestion job status poll error (job_id={job_id}): {e} — retrying in 30s" + ); + } + } + } + } else { + log::info!("[memory] no job_id returned — insert assumed synchronous, proceeding"); } - result + + log::info!("[memory] store_skill_sync: exit — ok (namespace={namespace})"); + Ok(()) } /// Query relevant context for a skill integration (RAG). @@ -139,10 +212,10 @@ impl MemoryClient { pub async fn recall_skill_context( &self, skill_id: &str, - integration_id: &str, + _integration_id: &str, max_chunks: u32, ) -> Result, String> { - let namespace = format!("skill:{skill_id}:{integration_id}"); + let namespace = skill_id.to_string(); log::info!( "[memory] recall_skill_context: entry (namespace={namespace}, max_chunks={max_chunks})" ); @@ -255,6 +328,7 @@ mod tests { None, None, None, + None, ) .await; diff --git a/src-tauri/src/runtime/qjs_skill_instance.rs b/src-tauri/src/runtime/qjs_skill_instance.rs index ae71c264d..257a913fd 100644 --- a/src-tauri/src/runtime/qjs_skill_instance.rs +++ b/src-tauri/src/runtime/qjs_skill_instance.rs @@ -583,50 +583,7 @@ async fn handle_message( }).await; log::info!("[skill:{}] OAuth credential set and persisted to store", skill_id); let params_str = serde_json::to_string(¶ms).unwrap_or_else(|_| "{}".to_string()); - let result = handle_js_call(rt, ctx, "onOAuthComplete", ¶ms_str).await; - - // Fire-and-forget: persist published ops state to TinyHumans memory. - // Skills publish data via state.set()/setPartial() into ops_state.data, - // not as the return value of onOAuthComplete() (which is typically undefined). - let state_snapshot = ops_state.read().data.clone(); - if !state_snapshot.is_empty() { - if let Some(client) = memory_client_opt.clone() { - let skill = skill_id.to_string(); - let integration_id = params - .get("integrationId") - .and_then(|v| v.as_str()) - .unwrap_or("unknown") - .to_string(); - let content = serde_json::to_string_pretty( - &serde_json::Value::Object(state_snapshot), - ) - .unwrap_or_else(|_| "{}".to_string()); - let title = format!("{} OAuth sync — {}", skill, integration_id); - tokio::spawn(async move { - if let Err(e) = client - .store_skill_sync( - &skill, - &integration_id, - &title, - &content, - None, - None, - None, - None, - None, - None, - ) - .await - { - log::warn!("[memory] store_skill_sync failed: {e}"); - } else { - log::info!("[memory] Stored sync for {}:{}", skill, integration_id); - } - }); - } - } - - result + handle_js_call(rt, ctx, "onOAuthComplete", ¶ms_str).await } "skill/ping" => { handle_js_call(rt, ctx, "onPing", "{}").await diff --git a/src/AppRoutes.tsx b/src/AppRoutes.tsx index 6738377ee..fe41326bc 100644 --- a/src/AppRoutes.tsx +++ b/src/AppRoutes.tsx @@ -8,11 +8,11 @@ import Conversations from './pages/Conversations'; import Home from './pages/Home'; import Intelligence from './pages/Intelligence'; import Invites from './pages/Invites'; -import Skills from './pages/Skills'; import Login from './pages/Login'; import Mnemonic from './pages/Mnemonic'; import Onboarding from './pages/onboarding/Onboarding'; import Settings from './pages/Settings'; +import Skills from './pages/Skills'; import Welcome from './pages/Welcome'; import { selectHasEncryptionKey, selectIsOnboarded } from './store/authSelectors'; import { useAppSelector } from './store/hooks'; @@ -69,7 +69,7 @@ const AppRoutes = () => { path="/login" element={ - + } /> diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index e29c76570..566df89a0 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -2,11 +2,11 @@ import DownloadScreen from '../components/DownloadScreen'; import OAuthLoginSection from '../components/oauth/OAuthLoginSection'; import TypewriterGreeting from '../components/TypewriterGreeting'; -interface WelcomeProps { - isWeb: boolean; +interface LoginProps { + isWeb?: boolean; } -const Login = ({ isWeb }: WelcomeProps) => { +const Login = ({ isWeb }: LoginProps) => { const greetings = ['Hello HAL9000! 👋', "Let's cook! 🔥", 'The A-Team is here! 👊']; return ( diff --git a/src/pages/Skills.tsx b/src/pages/Skills.tsx index ae8da3a73..f6f14f9df 100644 --- a/src/pages/Skills.tsx +++ b/src/pages/Skills.tsx @@ -14,9 +14,9 @@ import SkillSetupModal from '../components/skills/SkillSetupModal'; import { deriveConnectionStatus, useSkillConnectionStatus } from '../lib/skills/hooks'; import { skillManager } from '../lib/skills/manager'; import type { SkillConnectionStatus, SkillHostConnectionState } from '../lib/skills/types'; -import { deriveSkillSyncUiState } from './skillsSyncUi'; import { useAppSelector } from '../store/hooks'; import { IS_DEV } from '../utils/config'; +import { deriveSkillSyncUiState } from './skillsSyncUi'; /** Format large numbers: 1200 → "1.2K", 1200000 → "1.2M" */ function formatNumber(n: number): string { @@ -53,7 +53,10 @@ function SkillCard({ skill, onSetup }: SkillCardProps) { | (SkillHostConnectionState & Record) | undefined; const [manualSyncing, setManualSyncing] = useState(false); - const syncUi = useMemo(() => deriveSkillSyncUiState(skill.id, skillState), [skill.id, skillState]); + const syncUi = useMemo( + () => deriveSkillSyncUiState(skill.id, skillState), + [skill.id, skillState] + ); const isSyncing = manualSyncing || syncUi.isSyncing; const handleSync = async (e: React.MouseEvent) => { @@ -212,10 +215,11 @@ export default function Skills() { } const manifests = await invoke>>('runtime_discover_skills'); + const ALLOWED_SKILLS = new Set(['gmail', 'notion']); const validManifests = manifests.filter(m => { const id = m.id as string; if (id.includes('_')) return false; - return true; + return ALLOWED_SKILLS.has(id); }); const processed: SkillListEntry[] = validManifests @@ -300,11 +304,7 @@ export default function Skills() { ) : (
{sortedSkillsList.map(skill => ( - openSkillSetup(skill)} - /> + openSkillSetup(skill)} /> ))}
)} @@ -326,7 +326,6 @@ export default function Skills() { }} /> )} - ); } diff --git a/yarn.lock b/yarn.lock index 3f777e24f..b73180694 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1299,7 +1299,12 @@ dependencies: postcss-selector-parser "6.0.10" -"@tauri-apps/api@2.9.1", "@tauri-apps/api@^2.8.0": +"@tauri-apps/api@^2.10.0": + version "2.10.1" + resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.10.1.tgz#57c1bae6114ec33d977eb2b50dfefc25fa84fc93" + integrity sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw== + +"@tauri-apps/api@^2.8.0": version "2.9.1" resolved "https://registry.npmjs.org/@tauri-apps/api/-/api-2.9.1.tgz" integrity sha512-IGlhP6EivjXHepbBic618GOmiWe4URJiIeZFlB7x3czM0yDHHYviH1Xvoiv4FefdkQtn6v7TuwWCRfOGdnVUGw== @@ -7443,16 +7448,7 @@ strict-event-emitter@^0.5.1: resolved "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz" integrity sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -7551,14 +7547,7 @@ stringify-entities@^4.0.0: character-entities-html4 "^2.0.0" character-entities-legacy "^3.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -8408,7 +8397,7 @@ workerpool@^6.5.1: resolved "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz" integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -8426,15 +8415,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz"