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.

This commit is contained in:
M3gA-Mind
2026-03-25 17:19:37 +05:30
parent ab93e68545
commit ec86559382
9 changed files with 456 additions and 112 deletions
@@ -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<MemoryClient>`
in `MemoryState` (a `Mutex<Option<MemoryClientRef>>`).
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<ToolContent>, 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) |
+1 -1
View File
@@ -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",
+61 -2
View File
@@ -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<String> = 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<String> = 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<String> = 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);
}
+96 -22
View File
@@ -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<f64>,
document_id: Option<String>,
) -> 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<Option<String>, 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;
+1 -44
View File
@@ -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(&params).unwrap_or_else(|_| "{}".to_string());
let result = handle_js_call(rt, ctx, "onOAuthComplete", &params_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", &params_str).await
}
"skill/ping" => {
handle_js_call(rt, ctx, "onPing", "{}").await
+2 -2
View File
@@ -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={
<PublicRoute>
<Login isWeb={isWeb} />
<Login />
</PublicRoute>
}
/>
+3 -3
View File
@@ -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 (
+8 -9
View File
@@ -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<string, unknown>)
| 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<Array<Record<string, unknown>>>('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() {
) : (
<div className="space-y-2">
{sortedSkillsList.map(skill => (
<SkillCard
key={skill.id}
skill={skill}
onSetup={() => openSkillSetup(skill)}
/>
<SkillCard key={skill.id} skill={skill} onSetup={() => openSkillSetup(skill)} />
))}
</div>
)}
@@ -326,7 +326,6 @@ export default function Skills() {
}}
/>
)}
</div>
);
}
+9 -29
View File
@@ -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"