From b1c94b3f028e13b9a0f21799456eca269c10d0d5 Mon Sep 17 00:00:00 2001 From: Hunter B Date: Fri, 12 Jun 2026 02:41:18 -0700 Subject: [PATCH] perf(skills): sync registry entries concurrently Use bounded ordered concurrency for /skills sync so registry downloads no longer serialize one skill at a time while preserving deterministic outcome order. Harvested from PR #3139. Co-authored-by: Hmbown <101357273+Hmbown@users.noreply.github.com> Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- CHANGELOG.md | 3 +++ crates/tui/CHANGELOG.md | 3 +++ crates/tui/src/skills/install.rs | 13 +++++++------ 3 files changed, 13 insertions(+), 6 deletions(-) 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 }) }