ACP debug tools pane (#36768)

Agus Zubiaga created

Adds a new "acp: open debug tools" action that opens a new workspace
item with a log of ACP messages for the active connection.

Release Notes:

- N/A

Change summary

Cargo.lock                         |  27 +
Cargo.toml                         |   4 
crates/acp_tools/Cargo.toml        |  30 +
crates/acp_tools/LICENSE-GPL       |   1 
crates/acp_tools/src/acp_tools.rs  | 494 ++++++++++++++++++++++++++++++++
crates/agent_servers/Cargo.toml    |   1 
crates/agent_servers/src/acp/v1.rs |  11 
crates/zed/Cargo.toml              |   1 
crates/zed/src/main.rs             |   1 
crates/zed/src/zed.rs              |   1 
script/squawk                      |  12 
tooling/workspace-hack/Cargo.toml  |   8 
12 files changed, 574 insertions(+), 17 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -39,6 +39,26 @@ dependencies = [
  "workspace-hack",
 ]
 
+[[package]]
+name = "acp_tools"
+version = "0.1.0"
+dependencies = [
+ "agent-client-protocol",
+ "collections",
+ "gpui",
+ "language",
+ "markdown",
+ "project",
+ "serde",
+ "serde_json",
+ "settings",
+ "theme",
+ "ui",
+ "util",
+ "workspace",
+ "workspace-hack",
+]
+
 [[package]]
 name = "action_log"
 version = "0.1.0"
@@ -171,11 +191,12 @@ dependencies = [
 
 [[package]]
 name = "agent-client-protocol"
-version = "0.0.30"
+version = "0.0.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f792e009ba59b137ee1db560bc37e567887ad4b5af6f32181d381fff690e2d4"
+checksum = "289eb34ee17213dadcca47eedadd386a5e7678094095414e475965d1bcca2860"
 dependencies = [
  "anyhow",
+ "async-broadcast",
  "futures 0.3.31",
  "log",
  "parking_lot",
@@ -264,6 +285,7 @@ name = "agent_servers"
 version = "0.1.0"
 dependencies = [
  "acp_thread",
+ "acp_tools",
  "action_log",
  "agent-client-protocol",
  "agent_settings",
@@ -20417,6 +20439,7 @@ dependencies = [
 name = "zed"
 version = "0.202.0"
 dependencies = [
+ "acp_tools",
  "activity_indicator",
  "agent",
  "agent_servers",

Cargo.toml 🔗

@@ -1,6 +1,7 @@
 [workspace]
 resolver = "2"
 members = [
+    "crates/acp_tools",
     "crates/acp_thread",
     "crates/action_log",
     "crates/activity_indicator",
@@ -227,6 +228,7 @@ edition = "2024"
 # Workspace member crates
 #
 
+acp_tools = { path = "crates/acp_tools" }
 acp_thread = { path = "crates/acp_thread" }
 action_log = { path = "crates/action_log" }
 agent = { path = "crates/agent" }
@@ -425,7 +427,7 @@ zlog_settings = { path = "crates/zlog_settings" }
 #
 
 agentic-coding-protocol = "0.0.10"
-agent-client-protocol = "0.0.30"
+agent-client-protocol = "0.0.31"
 aho-corasick = "1.1"
 alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", branch = "add-hush-login-flag" }
 any_vec = "0.14"

crates/acp_tools/Cargo.toml 🔗

@@ -0,0 +1,30 @@
+[package]
+name = "acp_tools"
+version = "0.1.0"
+edition.workspace = true
+publish.workspace = true
+license = "GPL-3.0-or-later"
+
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/acp_tools.rs"
+doctest = false
+
+[dependencies]
+agent-client-protocol.workspace = true
+collections.workspace = true
+gpui.workspace = true
+language.workspace= true
+markdown.workspace = true
+project.workspace = true
+serde.workspace = true
+serde_json.workspace = true
+settings.workspace = true
+theme.workspace = true
+ui.workspace = true
+util.workspace = true
+workspace-hack.workspace = true
+workspace.workspace = true

crates/acp_tools/src/acp_tools.rs 🔗

@@ -0,0 +1,494 @@
+use std::{
+    cell::RefCell,
+    collections::HashSet,
+    fmt::Display,
+    rc::{Rc, Weak},
+    sync::Arc,
+};
+
+use agent_client_protocol as acp;
+use collections::HashMap;
+use gpui::{
+    App, Empty, Entity, EventEmitter, FocusHandle, Focusable, Global, ListAlignment, ListState,
+    StyleRefinement, Subscription, Task, TextStyleRefinement, Window, actions, list, prelude::*,
+};
+use language::LanguageRegistry;
+use markdown::{CodeBlockRenderer, Markdown, MarkdownElement, MarkdownStyle};
+use project::Project;
+use settings::Settings;
+use theme::ThemeSettings;
+use ui::prelude::*;
+use util::ResultExt as _;
+use workspace::{Item, Workspace};
+
+actions!(acp, [OpenDebugTools]);
+
+pub fn init(cx: &mut App) {
+    cx.observe_new(
+        |workspace: &mut Workspace, _window, _cx: &mut Context<Workspace>| {
+            workspace.register_action(|workspace, _: &OpenDebugTools, window, cx| {
+                let acp_tools =
+                    Box::new(cx.new(|cx| AcpTools::new(workspace.project().clone(), cx)));
+                workspace.add_item_to_active_pane(acp_tools, None, true, window, cx);
+            });
+        },
+    )
+    .detach();
+}
+
+struct GlobalAcpConnectionRegistry(Entity<AcpConnectionRegistry>);
+
+impl Global for GlobalAcpConnectionRegistry {}
+
+#[derive(Default)]
+pub struct AcpConnectionRegistry {
+    active_connection: RefCell<Option<ActiveConnection>>,
+}
+
+struct ActiveConnection {
+    server_name: &'static str,
+    connection: Weak<acp::ClientSideConnection>,
+}
+
+impl AcpConnectionRegistry {
+    pub fn default_global(cx: &mut App) -> Entity<Self> {
+        if cx.has_global::<GlobalAcpConnectionRegistry>() {
+            cx.global::<GlobalAcpConnectionRegistry>().0.clone()
+        } else {
+            let registry = cx.new(|_cx| AcpConnectionRegistry::default());
+            cx.set_global(GlobalAcpConnectionRegistry(registry.clone()));
+            registry
+        }
+    }
+
+    pub fn set_active_connection(
+        &self,
+        server_name: &'static str,
+        connection: &Rc<acp::ClientSideConnection>,
+        cx: &mut Context<Self>,
+    ) {
+        self.active_connection.replace(Some(ActiveConnection {
+            server_name,
+            connection: Rc::downgrade(connection),
+        }));
+        cx.notify();
+    }
+}
+
+struct AcpTools {
+    project: Entity<Project>,
+    focus_handle: FocusHandle,
+    expanded: HashSet<usize>,
+    watched_connection: Option<WatchedConnection>,
+    connection_registry: Entity<AcpConnectionRegistry>,
+    _subscription: Subscription,
+}
+
+struct WatchedConnection {
+    server_name: &'static str,
+    messages: Vec<WatchedConnectionMessage>,
+    list_state: ListState,
+    connection: Weak<acp::ClientSideConnection>,
+    incoming_request_methods: HashMap<i32, Arc<str>>,
+    outgoing_request_methods: HashMap<i32, Arc<str>>,
+    _task: Task<()>,
+}
+
+impl AcpTools {
+    fn new(project: Entity<Project>, cx: &mut Context<Self>) -> Self {
+        let connection_registry = AcpConnectionRegistry::default_global(cx);
+
+        let subscription = cx.observe(&connection_registry, |this, _, cx| {
+            this.update_connection(cx);
+            cx.notify();
+        });
+
+        let mut this = Self {
+            project,
+            focus_handle: cx.focus_handle(),
+            expanded: HashSet::default(),
+            watched_connection: None,
+            connection_registry,
+            _subscription: subscription,
+        };
+        this.update_connection(cx);
+        this
+    }
+
+    fn update_connection(&mut self, cx: &mut Context<Self>) {
+        let active_connection = self.connection_registry.read(cx).active_connection.borrow();
+        let Some(active_connection) = active_connection.as_ref() else {
+            return;
+        };
+
+        if let Some(watched_connection) = self.watched_connection.as_ref() {
+            if Weak::ptr_eq(
+                &watched_connection.connection,
+                &active_connection.connection,
+            ) {
+                return;
+            }
+        }
+
+        if let Some(connection) = active_connection.connection.upgrade() {
+            let mut receiver = connection.subscribe();
+            let task = cx.spawn(async move |this, cx| {
+                while let Ok(message) = receiver.recv().await {
+                    this.update(cx, |this, cx| {
+                        this.push_stream_message(message, cx);
+                    })
+                    .ok();
+                }
+            });
+
+            self.watched_connection = Some(WatchedConnection {
+                server_name: active_connection.server_name,
+                messages: vec![],
+                list_state: ListState::new(0, ListAlignment::Bottom, px(2048.)),
+                connection: active_connection.connection.clone(),
+                incoming_request_methods: HashMap::default(),
+                outgoing_request_methods: HashMap::default(),
+                _task: task,
+            });
+        }
+    }
+
+    fn push_stream_message(&mut self, stream_message: acp::StreamMessage, cx: &mut Context<Self>) {
+        let Some(connection) = self.watched_connection.as_mut() else {
+            return;
+        };
+        let language_registry = self.project.read(cx).languages().clone();
+        let index = connection.messages.len();
+
+        let (request_id, method, message_type, params) = match stream_message.message {
+            acp::StreamMessageContent::Request { id, method, params } => {
+                let method_map = match stream_message.direction {
+                    acp::StreamMessageDirection::Incoming => {
+                        &mut connection.incoming_request_methods
+                    }
+                    acp::StreamMessageDirection::Outgoing => {
+                        &mut connection.outgoing_request_methods
+                    }
+                };
+
+                method_map.insert(id, method.clone());
+                (Some(id), method.into(), MessageType::Request, Ok(params))
+            }
+            acp::StreamMessageContent::Response { id, result } => {
+                let method_map = match stream_message.direction {
+                    acp::StreamMessageDirection::Incoming => {
+                        &mut connection.outgoing_request_methods
+                    }
+                    acp::StreamMessageDirection::Outgoing => {
+                        &mut connection.incoming_request_methods
+                    }
+                };
+
+                if let Some(method) = method_map.remove(&id) {
+                    (Some(id), method.into(), MessageType::Response, result)
+                } else {
+                    (
+                        Some(id),
+                        "[unrecognized response]".into(),
+                        MessageType::Response,
+                        result,
+                    )
+                }
+            }
+            acp::StreamMessageContent::Notification { method, params } => {
+                (None, method.into(), MessageType::Notification, Ok(params))
+            }
+        };
+
+        let message = WatchedConnectionMessage {
+            name: method,
+            message_type,
+            request_id,
+            direction: stream_message.direction,
+            collapsed_params_md: match params.as_ref() {
+                Ok(params) => params
+                    .as_ref()
+                    .map(|params| collapsed_params_md(params, &language_registry, cx)),
+                Err(err) => {
+                    if let Ok(err) = &serde_json::to_value(err) {
+                        Some(collapsed_params_md(&err, &language_registry, cx))
+                    } else {
+                        None
+                    }
+                }
+            },
+
+            expanded_params_md: None,
+            params,
+        };
+
+        connection.messages.push(message);
+        connection.list_state.splice(index..index, 1);
+        cx.notify();
+    }
+
+    fn render_message(
+        &mut self,
+        index: usize,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> AnyElement {
+        let Some(connection) = self.watched_connection.as_ref() else {
+            return Empty.into_any();
+        };
+
+        let Some(message) = connection.messages.get(index) else {
+            return Empty.into_any();
+        };
+
+        let base_size = TextSize::Editor.rems(cx);
+
+        let theme_settings = ThemeSettings::get_global(cx);
+        let text_style = window.text_style();
+
+        let colors = cx.theme().colors();
+        let expanded = self.expanded.contains(&index);
+
+        v_flex()
+            .w_full()
+            .px_4()
+            .py_3()
+            .border_color(colors.border)
+            .border_b_1()
+            .gap_2()
+            .items_start()
+            .font_buffer(cx)
+            .text_size(base_size)
+            .id(index)
+            .group("message")
+            .hover(|this| this.bg(colors.element_background.opacity(0.5)))
+            .on_click(cx.listener(move |this, _, _, cx| {
+                if this.expanded.contains(&index) {
+                    this.expanded.remove(&index);
+                } else {
+                    this.expanded.insert(index);
+                    let Some(connection) = &mut this.watched_connection else {
+                        return;
+                    };
+                    let Some(message) = connection.messages.get_mut(index) else {
+                        return;
+                    };
+                    message.expanded(this.project.read(cx).languages().clone(), cx);
+                    connection.list_state.scroll_to_reveal_item(index);
+                }
+                cx.notify()
+            }))
+            .child(
+                h_flex()
+                    .w_full()
+                    .gap_2()
+                    .items_center()
+                    .flex_shrink_0()
+                    .child(match message.direction {
+                        acp::StreamMessageDirection::Incoming => {
+                            ui::Icon::new(ui::IconName::ArrowDown).color(Color::Error)
+                        }
+                        acp::StreamMessageDirection::Outgoing => {
+                            ui::Icon::new(ui::IconName::ArrowUp).color(Color::Success)
+                        }
+                    })
+                    .child(
+                        Label::new(message.name.clone())
+                            .buffer_font(cx)
+                            .color(Color::Muted),
+                    )
+                    .child(div().flex_1())
+                    .child(
+                        div()
+                            .child(ui::Chip::new(message.message_type.to_string()))
+                            .visible_on_hover("message"),
+                    )
+                    .children(
+                        message
+                            .request_id
+                            .map(|req_id| div().child(ui::Chip::new(req_id.to_string()))),
+                    ),
+            )
+            // I'm aware using markdown is a hack. Trying to get something working for the demo.
+            // Will clean up soon!
+            .when_some(
+                if expanded {
+                    message.expanded_params_md.clone()
+                } else {
+                    message.collapsed_params_md.clone()
+                },
+                |this, params| {
+                    this.child(
+                        div().pl_6().w_full().child(
+                            MarkdownElement::new(
+                                params,
+                                MarkdownStyle {
+                                    base_text_style: text_style,
+                                    selection_background_color: colors.element_selection_background,
+                                    syntax: cx.theme().syntax().clone(),
+                                    code_block_overflow_x_scroll: true,
+                                    code_block: StyleRefinement {
+                                        text: Some(TextStyleRefinement {
+                                            font_family: Some(
+                                                theme_settings.buffer_font.family.clone(),
+                                            ),
+                                            font_size: Some((base_size * 0.8).into()),
+                                            ..Default::default()
+                                        }),
+                                        ..Default::default()
+                                    },
+                                    ..Default::default()
+                                },
+                            )
+                            .code_block_renderer(
+                                CodeBlockRenderer::Default {
+                                    copy_button: false,
+                                    copy_button_on_hover: expanded,
+                                    border: false,
+                                },
+                            ),
+                        ),
+                    )
+                },
+            )
+            .into_any()
+    }
+}
+
+struct WatchedConnectionMessage {
+    name: SharedString,
+    request_id: Option<i32>,
+    direction: acp::StreamMessageDirection,
+    message_type: MessageType,
+    params: Result<Option<serde_json::Value>, acp::Error>,
+    collapsed_params_md: Option<Entity<Markdown>>,
+    expanded_params_md: Option<Entity<Markdown>>,
+}
+
+impl WatchedConnectionMessage {
+    fn expanded(&mut self, language_registry: Arc<LanguageRegistry>, cx: &mut App) {
+        let params_md = match &self.params {
+            Ok(Some(params)) => Some(expanded_params_md(params, &language_registry, cx)),
+            Err(err) => {
+                if let Some(err) = &serde_json::to_value(err).log_err() {
+                    Some(expanded_params_md(&err, &language_registry, cx))
+                } else {
+                    None
+                }
+            }
+            _ => None,
+        };
+        self.expanded_params_md = params_md;
+    }
+}
+
+fn collapsed_params_md(
+    params: &serde_json::Value,
+    language_registry: &Arc<LanguageRegistry>,
+    cx: &mut App,
+) -> Entity<Markdown> {
+    let params_json = serde_json::to_string(params).unwrap_or_default();
+    let mut spaced_out_json = String::with_capacity(params_json.len() + params_json.len() / 4);
+
+    for ch in params_json.chars() {
+        match ch {
+            '{' => spaced_out_json.push_str("{ "),
+            '}' => spaced_out_json.push_str(" }"),
+            ':' => spaced_out_json.push_str(": "),
+            ',' => spaced_out_json.push_str(", "),
+            c => spaced_out_json.push(c),
+        }
+    }
+
+    let params_md = format!("```json\n{}\n```", spaced_out_json);
+    cx.new(|cx| Markdown::new(params_md.into(), Some(language_registry.clone()), None, cx))
+}
+
+fn expanded_params_md(
+    params: &serde_json::Value,
+    language_registry: &Arc<LanguageRegistry>,
+    cx: &mut App,
+) -> Entity<Markdown> {
+    let params_json = serde_json::to_string_pretty(params).unwrap_or_default();
+    let params_md = format!("```json\n{}\n```", params_json);
+    cx.new(|cx| Markdown::new(params_md.into(), Some(language_registry.clone()), None, cx))
+}
+
+enum MessageType {
+    Request,
+    Response,
+    Notification,
+}
+
+impl Display for MessageType {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            MessageType::Request => write!(f, "Request"),
+            MessageType::Response => write!(f, "Response"),
+            MessageType::Notification => write!(f, "Notification"),
+        }
+    }
+}
+
+enum AcpToolsEvent {}
+
+impl EventEmitter<AcpToolsEvent> for AcpTools {}
+
+impl Item for AcpTools {
+    type Event = AcpToolsEvent;
+
+    fn tab_content_text(&self, _detail: usize, _cx: &App) -> ui::SharedString {
+        format!(
+            "ACP: {}",
+            self.watched_connection
+                .as_ref()
+                .map_or("Disconnected", |connection| connection.server_name)
+        )
+        .into()
+    }
+
+    fn tab_icon(&self, _window: &Window, _cx: &App) -> Option<Icon> {
+        Some(ui::Icon::new(IconName::Thread))
+    }
+}
+
+impl Focusable for AcpTools {
+    fn focus_handle(&self, _cx: &App) -> FocusHandle {
+        self.focus_handle.clone()
+    }
+}
+
+impl Render for AcpTools {
+    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        v_flex()
+            .track_focus(&self.focus_handle)
+            .size_full()
+            .bg(cx.theme().colors().editor_background)
+            .child(match self.watched_connection.as_ref() {
+                Some(connection) => {
+                    if connection.messages.is_empty() {
+                        h_flex()
+                            .size_full()
+                            .justify_center()
+                            .items_center()
+                            .child("No messages recorded yet")
+                            .into_any()
+                    } else {
+                        list(
+                            connection.list_state.clone(),
+                            cx.processor(Self::render_message),
+                        )
+                        .with_sizing_behavior(gpui::ListSizingBehavior::Auto)
+                        .flex_grow()
+                        .into_any()
+                    }
+                }
+                None => h_flex()
+                    .size_full()
+                    .justify_center()
+                    .items_center()
+                    .child("No active connection")
+                    .into_any(),
+            })
+    }
+}

crates/agent_servers/Cargo.toml 🔗

@@ -17,6 +17,7 @@ path = "src/agent_servers.rs"
 doctest = false
 
 [dependencies]
+acp_tools.workspace = true
 acp_thread.workspace = true
 action_log.workspace = true
 agent-client-protocol.workspace = true

crates/agent_servers/src/acp/v1.rs 🔗

@@ -1,3 +1,4 @@
+use acp_tools::AcpConnectionRegistry;
 use action_log::ActionLog;
 use agent_client_protocol::{self as acp, Agent as _, ErrorCode};
 use anyhow::anyhow;
@@ -101,6 +102,14 @@ impl AcpConnection {
         })
         .detach();
 
+        let connection = Rc::new(connection);
+
+        cx.update(|cx| {
+            AcpConnectionRegistry::default_global(cx).update(cx, |registry, cx| {
+                registry.set_active_connection(server_name, &connection, cx)
+            });
+        })?;
+
         let response = connection
             .initialize(acp::InitializeRequest {
                 protocol_version: acp::VERSION,
@@ -119,7 +128,7 @@ impl AcpConnection {
 
         Ok(Self {
             auth_methods: response.auth_methods,
-            connection: connection.into(),
+            connection,
             server_name,
             sessions,
             prompt_capabilities: response.agent_capabilities.prompt_capabilities,

crates/zed/Cargo.toml 🔗

@@ -20,6 +20,7 @@ path = "src/main.rs"
 
 [dependencies]
 activity_indicator.workspace = true
+acp_tools.workspace = true
 agent.workspace = true
 agent_ui.workspace = true
 agent_settings.workspace = true

crates/zed/src/main.rs 🔗

@@ -566,6 +566,7 @@ pub fn main() {
         language_models::init(app_state.user_store.clone(), app_state.client.clone(), cx);
         agent_settings::init(cx);
         agent_servers::init(cx);
+        acp_tools::init(cx);
         web_search::init(cx);
         web_search_providers::init(app_state.client.clone(), cx);
         snippet_provider::init(cx);

crates/zed/src/zed.rs 🔗

@@ -4434,6 +4434,7 @@ mod tests {
             assert_eq!(actions_without_namespace, Vec::<&str>::new());
 
             let expected_namespaces = vec![
+                "acp",
                 "activity_indicator",
                 "agent",
                 #[cfg(not(target_os = "macos"))]

script/squawk 🔗

@@ -15,13 +15,11 @@ SQUAWK_VERSION=0.26.0
 SQUAWK_BIN="./target/squawk-$SQUAWK_VERSION"
 SQUAWK_ARGS="--assume-in-transaction --config script/lib/squawk.toml"
 
-if  [ ! -f "$SQUAWK_BIN" ]; then
-  pkgutil --pkg-info com.apple.pkg.RosettaUpdateAuto || /usr/sbin/softwareupdate --install-rosetta --agree-to-license
-  # When bootstrapping a brand new CI machine, the `target` directory may not exist yet.
-  mkdir -p "./target"
-  curl -L -o "$SQUAWK_BIN" "https://github.com/sbdchd/squawk/releases/download/v$SQUAWK_VERSION/squawk-darwin-x86_64"
-  chmod +x "$SQUAWK_BIN"
-fi
+pkgutil --pkg-info com.apple.pkg.RosettaUpdateAuto || /usr/sbin/softwareupdate --install-rosetta --agree-to-license
+# When bootstrapping a brand new CI machine, the `target` directory may not exist yet.
+mkdir -p "./target"
+curl -L -o "$SQUAWK_BIN" "https://github.com/sbdchd/squawk/releases/download/v$SQUAWK_VERSION/squawk-darwin-x86_64"
+chmod +x "$SQUAWK_BIN"
 
 if [ -n "$SQUAWK_GITHUB_TOKEN" ]; then
     export SQUAWK_GITHUB_REPO_OWNER=$(echo $GITHUB_REPOSITORY | awk -F/ '{print $1}')

tooling/workspace-hack/Cargo.toml 🔗

@@ -54,6 +54,7 @@ digest = { version = "0.10", features = ["mac", "oid", "std"] }
 either = { version = "1", features = ["serde", "use_std"] }
 euclid = { version = "0.22" }
 event-listener = { version = "5" }
+event-listener-strategy = { version = "0.5" }
 flate2 = { version = "1", features = ["zlib-rs"] }
 form_urlencoded = { version = "1" }
 futures = { version = "0.3", features = ["io-compat"] }
@@ -183,6 +184,7 @@ digest = { version = "0.10", features = ["mac", "oid", "std"] }
 either = { version = "1", features = ["serde", "use_std"] }
 euclid = { version = "0.22" }
 event-listener = { version = "5" }
+event-listener-strategy = { version = "0.5" }
 flate2 = { version = "1", features = ["zlib-rs"] }
 form_urlencoded = { version = "1" }
 futures = { version = "0.3", features = ["io-compat"] }
@@ -403,7 +405,6 @@ bytemuck = { version = "1", default-features = false, features = ["min_const_gen
 cipher = { version = "0.4", default-features = false, features = ["block-padding", "rand_core", "zeroize"] }
 codespan-reporting = { version = "0.12" }
 crypto-common = { version = "0.1", default-features = false, features = ["rand_core", "std"] }
-event-listener-strategy = { version = "0.5" }
 flume = { version = "0.11" }
 foldhash = { version = "0.1", default-features = false, features = ["std"] }
 getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] }
@@ -444,7 +445,6 @@ bytemuck = { version = "1", default-features = false, features = ["min_const_gen
 cipher = { version = "0.4", default-features = false, features = ["block-padding", "rand_core", "zeroize"] }
 codespan-reporting = { version = "0.12" }
 crypto-common = { version = "0.1", default-features = false, features = ["rand_core", "std"] }
-event-listener-strategy = { version = "0.5" }
 flume = { version = "0.11" }
 foldhash = { version = "0.1", default-features = false, features = ["std"] }
 getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] }
@@ -483,7 +483,6 @@ bytemuck = { version = "1", default-features = false, features = ["min_const_gen
 cipher = { version = "0.4", default-features = false, features = ["block-padding", "rand_core", "zeroize"] }
 codespan-reporting = { version = "0.12" }
 crypto-common = { version = "0.1", default-features = false, features = ["rand_core", "std"] }
-event-listener-strategy = { version = "0.5" }
 flume = { version = "0.11" }
 foldhash = { version = "0.1", default-features = false, features = ["std"] }
 getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] }
@@ -524,7 +523,6 @@ bytemuck = { version = "1", default-features = false, features = ["min_const_gen
 cipher = { version = "0.4", default-features = false, features = ["block-padding", "rand_core", "zeroize"] }
 codespan-reporting = { version = "0.12" }
 crypto-common = { version = "0.1", default-features = false, features = ["rand_core", "std"] }
-event-listener-strategy = { version = "0.5" }
 flume = { version = "0.11" }
 foldhash = { version = "0.1", default-features = false, features = ["std"] }
 getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] }
@@ -610,7 +608,6 @@ bytemuck = { version = "1", default-features = false, features = ["min_const_gen
 cipher = { version = "0.4", default-features = false, features = ["block-padding", "rand_core", "zeroize"] }
 codespan-reporting = { version = "0.12" }
 crypto-common = { version = "0.1", default-features = false, features = ["rand_core", "std"] }
-event-listener-strategy = { version = "0.5" }
 flume = { version = "0.11" }
 foldhash = { version = "0.1", default-features = false, features = ["std"] }
 getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] }
@@ -651,7 +648,6 @@ bytemuck = { version = "1", default-features = false, features = ["min_const_gen
 cipher = { version = "0.4", default-features = false, features = ["block-padding", "rand_core", "zeroize"] }
 codespan-reporting = { version = "0.12" }
 crypto-common = { version = "0.1", default-features = false, features = ["rand_core", "std"] }
-event-listener-strategy = { version = "0.5" }
 flume = { version = "0.11" }
 foldhash = { version = "0.1", default-features = false, features = ["std"] }
 getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] }