diff --git a/CHANGELOG.md b/CHANGELOG.md index 87fff320..04916633 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 loaded only after the workspace is trusted in user-owned config, matching the project-local MCP trust model while preserving the documented shell-command hook contract. +- **Skill registry sync latency (#3139).** `/skills sync` now syncs registry + entries with bounded ordered concurrency, so network latency no longer stacks + one skill at a time while output order stays deterministic. - **SiliconFlow China provider config (#2893/#2895).** `siliconflow-CN` now reads its own `[providers.siliconflow_cn]` / `[providers.siliconflow-CN]` table and falls back to `[providers.siliconflow]` only for unset diff --git a/crates/tui/CHANGELOG.md b/crates/tui/CHANGELOG.md index dd22eac1..ae8e0196 100644 --- a/crates/tui/CHANGELOG.md +++ b/crates/tui/CHANGELOG.md @@ -61,6 +61,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 loaded only after the workspace is trusted in user-owned config, matching the project-local MCP trust model while preserving the documented shell-command hook contract. +- **Skill registry sync latency (#3139).** `/skills sync` now syncs registry + entries with bounded ordered concurrency, so network latency no longer stacks + one skill at a time while output order stays deterministic. - **SiliconFlow China provider config (#2893/#2895).** `siliconflow-CN` now reads its own `[providers.siliconflow_cn]` / `[providers.siliconflow-CN]` table and falls back to `[providers.siliconflow]` only for unset diff --git a/crates/tui/src/skills/install.rs b/crates/tui/src/skills/install.rs index ace55ed4..715074a9 100644 --- a/crates/tui/src/skills/install.rs +++ b/crates/tui/src/skills/install.rs @@ -39,6 +39,7 @@ use std::path::{Component, Path, PathBuf}; use anyhow::{Context, Result, bail}; use flate2::read::GzDecoder; +use futures_util::stream::{self, StreamExt}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use thiserror::Error; @@ -69,6 +70,7 @@ pub const DEFAULT_REGISTRY_URL: &str = /// Default per-skill size cap (5 MiB). Honored at unpack time so a malicious /// gzip bomb can't blow up RAM. pub const DEFAULT_MAX_SIZE_BYTES: u64 = 5 * 1024 * 1024; +const SYNC_REGISTRY_CONCURRENCY: usize = 8; /// File written under each installed skill so [`update`] / [`uninstall`] can /// recover the original [`InstallSource`] without re-parsing user input. @@ -587,12 +589,11 @@ pub async fn sync_registry( } }; - let mut outcomes = Vec::new(); - - for (name, entry) in &doc.skills { - let outcome = sync_one_skill(name, entry, network, cache_dir, max_size).await; - outcomes.push(outcome); - } + let outcomes = stream::iter(doc.skills.iter()) + .map(|(name, entry)| sync_one_skill(name, entry, network, cache_dir, max_size)) + .buffered(SYNC_REGISTRY_CONCURRENCY) + .collect() + .await; Ok(SyncResult::Done { outcomes }) }