diff --git a/CHANGELOG.md b/CHANGELOG.md index 30f42ca..2a64f55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,19 @@ upcoming bare `resume` picker — into a minute-plus silent stall. method: a first prompt buried past the head budget reports as `None`, and `line_count` counts non-empty lines rather than successfully parsed ones. +## The share picker gathers harnesses in parallel — 2026-08-04 + +The unified session picker (`path share`, and bare `path resume` once +it lands) enumerated the seven providers one after another, so the +slowest scan — a big codex or claude history — stacked on top of all +the others before anything appeared. + +- **path-cli** (0.16.2): `gather_artifacts` runs the provider scans in + scoped threads, making pre-picker wall time max-of-providers instead + of sum-of-providers. Claude scans inline on the calling thread (its + chain-index cache is single-threaded); everything else fans out. + Row concatenation keeps the old provider order, so ranking + tie-breaks are unchanged. ## Projected Claude sessions are resumable again — 2026-07-30 diff --git a/Cargo.lock b/Cargo.lock index 4ceeda1..02a1ca0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2441,7 +2441,7 @@ dependencies = [ [[package]] name = "path-cli" -version = "0.16.1" +version = "0.16.2" dependencies = [ "anyhow", "assert_cmd", diff --git a/Cargo.toml b/Cargo.toml index 9a5c886..10ecf9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ toolpath-github = { version = "0.6.0", path = "crates/toolpath-github" } toolpath-dot = { version = "0.5.0", path = "crates/toolpath-dot" } toolpath-md = { version = "0.7.0", path = "crates/toolpath-md" } toolpath-pi = { version = "0.6.1", path = "crates/toolpath-pi" } -path-cli = { version = "0.16.1", path = "crates/path-cli" } +path-cli = { version = "0.16.2", path = "crates/path-cli" } pathbase-client = { version = "0.2.0", path = "crates/pathbase-client" } reqwest = { version = "0.13", default-features = false, features = ["blocking", "json", "rustls"] } diff --git a/crates/path-cli/Cargo.toml b/crates/path-cli/Cargo.toml index 1ce394b..137839d 100644 --- a/crates/path-cli/Cargo.toml +++ b/crates/path-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "path-cli" -version = "0.16.1" +version = "0.16.2" edition.workspace = true license.workspace = true repository = "https://github.com/empathic/toolpath" diff --git a/crates/path-cli/src/cmd_share.rs b/crates/path-cli/src/cmd_share.rs index d85b780..aecf1a0 100644 --- a/crates/path-cli/src/cmd_share.rs +++ b/crates/path-cli/src/cmd_share.rs @@ -90,47 +90,63 @@ pub(crate) fn gather_artifacts( harness_filter: Option, project_filter: Option<&std::path::Path>, ) -> Vec { - let mut rows = Vec::new(); let canonical_cwd = canonicalize_or_self(cwd); let canonical_project = project_filter.map(canonicalize_or_self); let want = |h: ArtifactType| harness_filter.is_none_or(|f| f == h); - if want(ArtifactType::Claude) - && let Some(mgr) = &bundle.claude - { - collect_claude(mgr, &canonical_cwd, canonical_project.as_deref(), &mut rows); - } - if want(ArtifactType::Gemini) - && let Some(mgr) = &bundle.gemini - { - collect_gemini(mgr, &canonical_cwd, canonical_project.as_deref(), &mut rows); - } - if want(ArtifactType::Pi) - && let Some(mgr) = &bundle.pi - { - collect_pi(mgr, &canonical_cwd, canonical_project.as_deref(), &mut rows); - } - if want(ArtifactType::Codex) - && let Some(mgr) = &bundle.codex - { - collect_codex(mgr, &canonical_cwd, canonical_project.as_deref(), &mut rows); - } - if want(ArtifactType::Copilot) - && let Some(mgr) = &bundle.copilot - { - collect_copilot(mgr, &canonical_cwd, canonical_project.as_deref(), &mut rows); - } - if want(ArtifactType::Opencode) - && let Some(mgr) = &bundle.opencode - { - collect_opencode(mgr, &canonical_cwd, canonical_project.as_deref(), &mut rows); - } - if want(ArtifactType::Cursor) - && let Some(mgr) = &bundle.cursor - { - collect_cursor(mgr, &canonical_cwd, canonical_project.as_deref(), &mut rows); - } + // Enumerate providers concurrently: each is an independent + // read-only scan of its own on-disk tree, and the slowest (a big + // codex or claude history) otherwise serializes behind the rest. + // Wall time becomes max-of-providers instead of sum. Claude is the + // one provider that can't cross threads (`ClaudeConvo` caches its + // chain index in a `RefCell`), so it scans inline on this thread + // while the rest run in scoped threads. Concatenation happens in + // the old sequential provider order, so the stable sort's + // tie-breaking matches the previous behavior exactly. + let mut rows = Vec::new(); + let cwd_ref = &canonical_cwd; + let project_ref = canonical_project.as_deref(); + std::thread::scope(|s| { + let mut handles = Vec::new(); + + macro_rules! spawn_collect { + ($ty:expr, $mgr:expr, $collect:ident) => { + if want($ty) + && let Some(mgr) = $mgr + { + handles.push(s.spawn(move || { + let mut out = Vec::new(); + $collect(mgr, cwd_ref, project_ref, &mut out); + out + })); + } + }; + } + + spawn_collect!(ArtifactType::Gemini, &bundle.gemini, collect_gemini); + spawn_collect!(ArtifactType::Pi, &bundle.pi, collect_pi); + spawn_collect!(ArtifactType::Codex, &bundle.codex, collect_codex); + spawn_collect!(ArtifactType::Copilot, &bundle.copilot, collect_copilot); + spawn_collect!(ArtifactType::Opencode, &bundle.opencode, collect_opencode); + spawn_collect!(ArtifactType::Cursor, &bundle.cursor, collect_cursor); + + if want(ArtifactType::Claude) + && let Some(mgr) = &bundle.claude + { + collect_claude(mgr, cwd_ref, project_ref, &mut rows); + } + + for handle in handles { + match handle.join() { + Ok(out) => rows.extend(out), + // A panicking collector degrades to "that provider is + // missing from the picker", matching how collector-level + // errors already warn-and-continue. + Err(_) => eprintln!("warning: a session collector panicked; its rows are skipped"), + } + } + }); rows.sort_by(|a, b| { b.matches_cwd diff --git a/site/_data/crates.json b/site/_data/crates.json index 7c364f2..5852d65 100644 --- a/site/_data/crates.json +++ b/site/_data/crates.json @@ -113,7 +113,7 @@ }, { "name": "path-cli", - "version": "0.16.1", + "version": "0.16.2", "description": "Unified CLI (binary: path)", "docs": "https://docs.rs/path-cli", "crate": "https://crates.io/crates/path-cli",