From a1bed81a9c73e89a0a6010e930864c307cbc7215 Mon Sep 17 00:00:00 2001 From: Thomas Petersen Date: Tue, 22 Sep 2026 14:27:25 -0400 Subject: [PATCH 1/5] Add Compute sharing plugin and local agent runner Signed-off-by: Thomas Petersen --- Cargo.lock | 9195 ++++++++++++++--- Cargo.toml | 2 +- crates/community-compute/Cargo.toml | 26 + crates/community-compute/src/broker.rs | 379 + crates/community-compute/src/catalog.rs | 347 + crates/community-compute/src/consumer.rs | 122 + crates/community-compute/src/discovery.rs | 143 + crates/community-compute/src/identity.rs | 152 + crates/community-compute/src/lib.rs | 72 + crates/community-compute/src/snapshot.rs | 359 + crates/community-compute/src/tests.rs | 181 + .../community-compute/src/transport_policy.rs | 490 + crates/community-compute/src/usage.rs | 49 + crates/community-compute/src/worker.rs | 548 + crates/community-compute/src/worker/models.rs | 117 + crates/plugin-manager/src/lib.rs | 4 + crates/plugin-manager/tests/management.rs | 1 + dev/compute-status.mjs | 161 + dev/compute-status.test.mjs | 164 + dev/compute-widget.test.mjs | 128 + dev/relay-broker.d.mts | 2 + dev/relay-broker.mjs | 64 +- dev/relay-identity.test.mjs | 53 + docs/agents.md | 47 +- docs/community-compute.md | 198 + docs/compute-testing.md | 167 + public/compute-widget.html | 3 + public/compute-widget.js | 1502 +++ scripts/build-compute-pair.mjs | 115 + src-tauri/Cargo.toml | 5 +- src-tauri/capabilities/compute-widget.json | 7 + src-tauri/src/agent_runner/mod.rs | 352 + src-tauri/src/agent_runner/profile.rs | 197 + src-tauri/src/agent_runner/supervisor.rs | 75 + src-tauri/src/community_compute.rs | 71 + src-tauri/src/compute_host.rs | 617 ++ src-tauri/src/compute_widget.rs | 30 + src-tauri/src/lib.rs | 63 +- src-tauri/src/main.rs | 8 + src-tauri/tauri.conf.json | 1 + src/app/Settings.tsx | 24 +- src/app/navigation.ts | 2 +- src/app/pages.integration.test.mjs | 31 + src/app/shell/PanelLaunchers.test.tsx | 143 + src/app/shell/PanelLaunchers.tsx | 37 +- src/bundled/agents/AgentRunner.test.tsx | 97 + src/bundled/agents/AgentRunner.tsx | 99 + src/bundled/agents/AgentsPage.tsx | 28 +- src/bundled/channels/ChannelsPage.tsx | 24 +- src/bundled/channels/useSidebarView.ts | 24 +- .../CommunityComputePage.test.tsx | 180 + .../CommunityComputePage.tsx | 96 + .../CommunityComputeTerritoryMap.tsx | 131 + .../CommunityComputeView.test.tsx | 264 + .../CommunityComputeView.tsx | 427 + .../community-compute/Compute.module.css | 80 + .../ComputeWidgetLauncher.tsx | 59 + .../community-compute/ConsumerComputeView.tsx | 134 + .../communityComputeFixtures.ts | 239 + .../communityComputeHexLayout.test.mjs | 358 + .../communityComputeHexLayout.ts | 453 + .../communityComputeMapModel.test.mjs | 206 + .../communityComputeMapModel.ts | 365 + src/bundled/community-compute/index.tsx | 15 + src/bundled/community-compute/manifest.json | 1 + src/bundled/community-compute/types.ts | 28 + src/bundled/index.ts | 3 + src/features/community-compute/native.ts | 25 + .../community-compute/sharing.test.ts | 98 + src/features/community-compute/sharing.ts | 153 + src/features/community-compute/status.test.ts | 241 + src/features/community-compute/status.ts | 227 + .../messages/MessageComposer.test.tsx | 47 +- src/features/messages/MessageComposer.tsx | 27 +- src/features/relay/service.ts | 11 + src/shared/design-system/icons/index.ts | 3 + src/shared/design-system/icons/svg.ts | 4 + tests/fixtures/community-compute.html | 5 + tests/fixtures/community-compute.tsx | 120 + vite.config.ts | 6 + 80 files changed, 19169 insertions(+), 1563 deletions(-) create mode 100644 crates/community-compute/Cargo.toml create mode 100644 crates/community-compute/src/broker.rs create mode 100644 crates/community-compute/src/catalog.rs create mode 100644 crates/community-compute/src/consumer.rs create mode 100644 crates/community-compute/src/discovery.rs create mode 100644 crates/community-compute/src/identity.rs create mode 100644 crates/community-compute/src/lib.rs create mode 100644 crates/community-compute/src/snapshot.rs create mode 100644 crates/community-compute/src/tests.rs create mode 100644 crates/community-compute/src/transport_policy.rs create mode 100644 crates/community-compute/src/usage.rs create mode 100644 crates/community-compute/src/worker.rs create mode 100644 crates/community-compute/src/worker/models.rs create mode 100644 dev/compute-status.mjs create mode 100644 dev/compute-status.test.mjs create mode 100644 dev/compute-widget.test.mjs create mode 100644 dev/relay-identity.test.mjs create mode 100644 docs/community-compute.md create mode 100644 docs/compute-testing.md create mode 100644 public/compute-widget.html create mode 100644 public/compute-widget.js create mode 100644 scripts/build-compute-pair.mjs create mode 100644 src-tauri/capabilities/compute-widget.json create mode 100644 src-tauri/src/agent_runner/mod.rs create mode 100644 src-tauri/src/agent_runner/profile.rs create mode 100644 src-tauri/src/agent_runner/supervisor.rs create mode 100644 src-tauri/src/community_compute.rs create mode 100644 src-tauri/src/compute_host.rs create mode 100644 src-tauri/src/compute_widget.rs create mode 100644 src/app/shell/PanelLaunchers.test.tsx create mode 100644 src/bundled/agents/AgentRunner.test.tsx create mode 100644 src/bundled/agents/AgentRunner.tsx create mode 100644 src/bundled/community-compute/CommunityComputePage.test.tsx create mode 100644 src/bundled/community-compute/CommunityComputePage.tsx create mode 100644 src/bundled/community-compute/CommunityComputeTerritoryMap.tsx create mode 100644 src/bundled/community-compute/CommunityComputeView.test.tsx create mode 100644 src/bundled/community-compute/CommunityComputeView.tsx create mode 100644 src/bundled/community-compute/Compute.module.css create mode 100644 src/bundled/community-compute/ComputeWidgetLauncher.tsx create mode 100644 src/bundled/community-compute/ConsumerComputeView.tsx create mode 100644 src/bundled/community-compute/communityComputeFixtures.ts create mode 100644 src/bundled/community-compute/communityComputeHexLayout.test.mjs create mode 100644 src/bundled/community-compute/communityComputeHexLayout.ts create mode 100644 src/bundled/community-compute/communityComputeMapModel.test.mjs create mode 100644 src/bundled/community-compute/communityComputeMapModel.ts create mode 100644 src/bundled/community-compute/index.tsx create mode 100644 src/bundled/community-compute/manifest.json create mode 100644 src/bundled/community-compute/types.ts create mode 100644 src/features/community-compute/native.ts create mode 100644 src/features/community-compute/sharing.test.ts create mode 100644 src/features/community-compute/sharing.ts create mode 100644 src/features/community-compute/status.test.ts create mode 100644 src/features/community-compute/status.ts create mode 100644 tests/fixtures/community-compute.html create mode 100644 tests/fixtures/community-compute.tsx diff --git a/Cargo.lock b/Cargo.lock index 1317d17c1..a0446edaf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,54 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common 0.1.7", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.5" @@ -32,6 +80,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "ambient-authority" version = "0.0.2" @@ -47,12 +101,107 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "anyhow" version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "arc-swap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" +dependencies = [ + "rustversion", +] + +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures 0.2.17", + "password-hash", +] + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + [[package]] name = "async-broadcast" version = "0.7.2" @@ -104,7 +253,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix", + "rustix 1.1.4", "slab", "windows-sys 0.61.2", ] @@ -135,7 +284,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix", + "rustix 1.1.4", ] [[package]] @@ -161,7 +310,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix", + "rustix 1.1.4", "signal-hook-registry", "slab", "windows-sys 0.61.2", @@ -184,6 +333,49 @@ dependencies = [ "syn 3.0.5", ] +[[package]] +name = "async-utility" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "188f83b9a198af8c336e505611edb00d6d2ac5c694241c5a4f9a12316938cfe9" +dependencies = [ + "futures-util", + "gloo-timers", + "tokio", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-wsocket" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c713e1f14c7b82e32ea159af1c6e2f070cfadbdf23fb2512acce9af0a26f1a2" +dependencies = [ + "futures", + "futures-util", + "js-sys", + "tokio", + "tokio-happy-eyeballs", + "tokio-rustls", + "tokio-socks", + "tokio-tungstenite", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "async_io_stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +dependencies = [ + "futures", + "pharos", + "rustc_version", +] + [[package]] name = "atk" version = "0.18.2" @@ -207,18 +399,131 @@ dependencies = [ "system-deps", ] +[[package]] +name = "atomic" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" +dependencies = [ + "bytemuck", +] + [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "attohttpc" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9" +dependencies = [ + "base64 0.22.1", + "http", + "log", + "url", +] + [[package]] name = "autocfg" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "aws-lc-rs" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b281d307588d634de920874890732659e2e7672f72b5e10e81badc1a8a83621e" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bff6c3b54fad79a2e60b8102caf565819711497c1f5f092f49508e2f5c31b27" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + +[[package]] +name = "axum" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" +dependencies = [ + "axum-core", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "backon" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" +dependencies = [ + "fastrand", + "gloo-timers", + "tokio", +] + +[[package]] +name = "base16ct" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" + [[package]] name = "base64" version = "0.21.7" @@ -238,82 +543,238 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" [[package]] -name = "bit-set" -version = "0.8.0" +name = "base64ct" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" -dependencies = [ - "bit-vec", -] +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] -name = "bit-vec" -version = "0.8.0" +name = "bech32" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" [[package]] -name = "bitflags" -version = "1.3.2" +name = "bech32" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "efbd3e1070bbdf4cd88a75264e18e8a26f7cb5c6949eadf0ceb85fb159cf08f8" [[package]] -name = "bitflags" -version = "2.13.1" +name = "bip39" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc" dependencies = [ - "serde_core", + "bitcoin_hashes 0.14.101", + "serde", + "unicode-normalization", ] [[package]] -name = "block-buffer" -version = "0.10.4" +name = "bit-set" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ - "generic-array", + "bit-vec 0.6.3", ] [[package]] -name = "block2" -version = "0.6.2" +name = "bit-set" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ - "objc2", + "bit-vec 0.8.0", ] [[package]] -name = "blocking" -version = "1.7.0" +name = "bit-vec" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa" -dependencies = [ - "async-channel", - "async-task", - "futures-io", - "futures-lite", - "piper", -] +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] -name = "brotli" -version = "8.0.4" +name = "bit-vec" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] -name = "brotli-decompressor" -version = "5.0.3" +name = "bitcoin-consensus-encoding" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" +checksum = "6712f9c6fd6785b3b270884e57c441c403dc5d7e19ca45368c97c7a1de3000ec" +dependencies = [ + "bitcoin-internals", + "hex-conservative 1.3.0", + "serde", +] + +[[package]] +name = "bitcoin-internals" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d573f4cf32996a8dce612e4348cece65a241f1882ed594047c9ba348e8869fa5" + +[[package]] +name = "bitcoin-io" +version = "0.1.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb5de036369d1ac59d3c1819ebc4d850f89466f5401c571a285b6ed564a4cb78" +dependencies = [ + "bitcoin-consensus-encoding", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bca4c7abb40c8817d77403c880988cfd484f23ab2365726afb2f798363e2c4a2" +dependencies = [ + "bitcoin-io", + "hex-conservative 0.2.3", + "serde", +] + +[[package]] +name = "bitcoin_hashes" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5304e53726dbe5f93141535e102ed97b5bf4714fbecefdda8f9fb98d7fdaff0e" +dependencies = [ + "bitcoin-consensus-encoding", + "bitcoin-internals", + "hex-conservative 1.3.0", + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +dependencies = [ + "serde_core", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "blake3" +version = "1.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9e454fc11f76977dc803893aff6304ed33d6a26efae8696573bea74baa27ae" +dependencies = [ + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.3.1", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "blocking" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bon" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60eafe0d77c3a2fc292c1d1346c3041b33c0a108085a2afabf672b70f69dbbc9" +dependencies = [ + "bon-macros", +] + +[[package]] +name = "bon-macros" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0f9631d8aaaee112c41985d675ef269e02acbd4f33122836af4f0c5f699ff6" +dependencies = [ + "darling 0.24.1", + "ident_case", + "prettyplease 0.3.0", + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "brotli" +version = "8.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -328,23 +789,61 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "bstr" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" +dependencies = [ + "memchr", + "regex-automata", + "serde_core", +] + [[package]] name = "bumpalo" version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +[[package]] +name = "buzz-community-compute" +version = "0.0.0" +dependencies = [ + "anyhow", + "base64 0.22.1", + "ed25519-dalek", + "hex", + "iroh", + "mesh-llm-client", + "mesh-llm-events", + "mesh-llm-host-runtime", + "mesh-llm-node", + "mesh-llm-sdk", + "mesh-llm-system", + "nostr 0.44.8", + "reqwest 0.13.4", + "serde", + "serde_json", + "sha2 0.11.0", + "tokio", + "url", +] + [[package]] name = "buzz-foundation" version = "0.0.0" dependencies = [ + "buzz-community-compute", "buzzodz-plugins", "futures-lite", "gtk", "libc", "mac-notification-sys", + "nostr 0.44.8", "portable-pty", "serde", + "serde_json", "tauri", "tauri-build", "tauri-plugin-dialog", @@ -352,7 +851,7 @@ dependencies = [ "tauri-winrt-notification", "url", "uuid", - "zbus", + "zbus 5.19.0", ] [[package]] @@ -364,11 +863,17 @@ dependencies = [ "libc", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "tempfile", "url", ] +[[package]] +name = "by_address" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" + [[package]] name = "bytemuck" version = "1.25.2" @@ -436,7 +941,7 @@ dependencies = [ "io-lifetimes 3.0.1", "ipnet", "maybe-owned", - "rustix", + "rustix 1.1.4", "rustix-linux-procfs", "windows-sys 0.61.2", "winx", @@ -451,7 +956,7 @@ dependencies = [ "cap-primitives", "io-extras", "io-lifetimes 3.0.1", - "rustix", + "rustix 1.1.4", ] [[package]] @@ -487,6 +992,24 @@ dependencies = [ "toml 0.9.12+spec-1.1.0", ] +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.4.5" @@ -494,6 +1017,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -536,6 +1061,47 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "rand_core 0.10.1", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20 0.9.1", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.45" @@ -543,1371 +1109,5047 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-link 0.2.1", ] [[package]] -name = "combine" -version = "4.6.8" +name = "cipher" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "bytes", - "memchr", + "crypto-common 0.1.7", + "inout", + "zeroize", ] [[package]] -name = "concurrent-queue" -version = "2.5.0" +name = "clap" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" dependencies = [ - "crossbeam-utils", + "clap_builder", + "clap_derive", ] [[package]] -name = "cookie" -version = "0.18.2" +name = "clap_builder" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87" +checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" dependencies = [ - "time", - "version_check", + "anstream", + "anstyle", + "clap_lex", + "strsim", ] [[package]] -name = "core-foundation" -version = "0.10.1" +name = "clap_derive" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" dependencies = [ - "core-foundation-sys", - "libc", + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 3.0.5", ] [[package]] -name = "core-foundation-sys" -version = "0.8.7" +name = "clap_lex" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" [[package]] -name = "core-graphics" -version = "0.25.0" +name = "cmake" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ - "bitflags 2.13.1", - "core-foundation", - "core-graphics-types", - "foreign-types", - "libc", + "cc", ] [[package]] -name = "core-graphics-types" -version = "0.2.0" +name = "cmov" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" -dependencies = [ - "bitflags 2.13.1", - "core-foundation", - "libc", -] +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" [[package]] -name = "cpufeatures" -version = "0.2.17" +name = "cobs" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" dependencies = [ - "libc", + "thiserror 2.0.20", ] [[package]] -name = "crc32fast" -version = "1.5.1" +name = "colorchoice" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550" -dependencies = [ - "cfg-if", -] +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] -name = "crossbeam-channel" -version = "0.5.17" +name = "colored" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "crossbeam-utils", + "windows-sys 0.61.2", ] [[package]] -name = "crossbeam-utils" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" - -[[package]] -name = "crypto-common" -version = "0.1.7" +name = "combine" +version = "4.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" dependencies = [ - "generic-array", - "typenum", + "bytes", + "memchr", ] [[package]] -name = "cssparser" -version = "0.36.0" +name = "compact_str" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" +checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab" dependencies = [ - "cssparser-macros", - "dtoa-short", + "castaway", + "cfg-if", "itoa", - "phf", - "smallvec", + "rustversion", + "ryu", + "static_assertions", ] [[package]] -name = "cssparser-macros" -version = "0.6.1" +name = "concurrent-queue" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ - "quote", - "syn 2.0.119", + "crossbeam-utils", ] [[package]] -name = "ctor" -version = "0.8.0" +name = "const-hex" +version = "1.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" +checksum = "0e59eef12462b0f9b0a3620219be5d639afd79fe39dff0a42c3997061f9298b4" dependencies = [ - "ctor-proc-macro", - "dtor", + "cfg-if", + "cpufeatures 0.2.17", + "proptest", + "serde_core", ] [[package]] -name = "ctor-proc-macro" -version = "0.0.7" +name = "const-oid" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" [[package]] -name = "darling" -version = "0.23.0" +name = "const-str" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" -dependencies = [ - "darling_core", - "darling_macro", -] +checksum = "18f12cc9948ed9604230cdddc7c86e270f9401ccbe3c2e98a4378c5e7632212f" [[package]] -name = "darling_core" -version = "0.23.0" +name = "const_panic" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +checksum = "9603f79528ece8163c496f8932121cb36cfe46259e9c907bb3d8205139d7caa3" dependencies = [ - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.119", + "typewit", ] [[package]] -name = "darling_macro" -version = "0.23.0" +name = "constant_time_eq" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.119", -] +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" [[package]] -name = "dbus" -version = "0.9.12" +name = "convert_case" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" dependencies = [ - "libc", - "libdbus-sys", - "windows-sys 0.61.2", + "unicode-segmentation", ] [[package]] -name = "defmt" -version = "1.1.1" +name = "cookie" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87" dependencies = [ - "bitflags 1.3.2", - "defmt-macros", + "time", + "version_check", ] [[package]] -name = "defmt-macros" -version = "1.1.1" +name = "cordyceps" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +checksum = "5b9ab7e0ca1d179628fa0172b2b97203c7fa0cd81be2448bd446fb9559ca9261" dependencies = [ - "defmt-parser", - "proc-macro2", - "quote", - "syn 2.0.119", + "loom", + "tracing", ] [[package]] -name = "defmt-parser" -version = "1.0.0" +name = "core-foundation" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ - "thiserror 2.0.20", + "core-foundation-sys", + "libc", ] [[package]] -name = "deranged" -version = "0.5.8" +name = "core-foundation" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ - "serde_core", + "core-foundation-sys", + "libc", ] [[package]] -name = "derive_more" -version = "2.1.1" +name = "core-foundation-sys" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" -dependencies = [ - "derive_more-impl", -] +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] -name = "derive_more-impl" -version = "2.1.1" +name = "core-graphics" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" dependencies = [ - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.119", + "bitflags 2.13.1", + "core-foundation 0.10.1", + "core-graphics-types", + "foreign-types 0.5.0", + "libc", ] [[package]] -name = "digest" -version = "0.10.7" +name = "core-graphics-types" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "block-buffer", - "crypto-common", + "bitflags 2.13.1", + "core-foundation 0.10.1", + "libc", ] [[package]] -name = "dirs" -version = "6.0.0" +name = "core_detect" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + +[[package]] +name = "countio" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9702aee5d1d744c01d82f6915644f950f898e014903385464c773b96fefdecb" dependencies = [ - "dirs-sys", + "futures-io", ] [[package]] -name = "dirs-sys" -version = "0.5.0" +name = "cpufeatures" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", - "option-ext", - "redox_users", - "windows-sys 0.61.2", ] [[package]] -name = "dispatch2" +name = "cpufeatures" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" dependencies = [ - "bitflags 2.13.1", - "block2", "libc", - "objc2", ] [[package]] -name = "displaydoc" -version = "0.2.7" +name = "crc32fast" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550" dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", + "cfg-if", ] [[package]] -name = "dlopen2" -version = "0.8.2" +name = "critical-section" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" -dependencies = [ - "dlopen2_derive", - "libc", - "once_cell", - "winapi", -] +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" [[package]] -name = "dlopen2_derive" -version = "0.4.3" +name = "crossbeam-channel" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" +checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", + "crossbeam-utils", ] [[package]] -name = "dom_query" -version = "0.27.0" +name = "crossbeam-utils" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" + +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bit-set", - "cssparser", - "foldhash", - "html5ever", - "precomputed-hash", - "selectors", - "tendril", + "bitflags 2.13.1", + "crossterm_winapi", + "mio", + "parking_lot", + "rustix 0.38.44", + "signal-hook", + "signal-hook-mio", + "winapi", ] [[package]] -name = "downcast-rs" -version = "1.2.1" +name = "crossterm" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags 2.13.1", + "crossterm_winapi", + "derive_more", + "document-features", + "mio", + "parking_lot", + "rustix 1.1.4", + "signal-hook", + "signal-hook-mio", + "winapi", +] [[package]] -name = "dpi" -version = "0.1.2" +name = "crossterm_winapi" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" dependencies = [ - "serde", + "winapi", ] [[package]] -name = "dtoa" -version = "1.0.11" +name = "crypto-common" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] [[package]] -name = "dtoa-short" -version = "0.3.5" +name = "crypto-common" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ - "dtoa", + "hybrid-array", + "rand_core 0.10.1", ] [[package]] -name = "dtor" -version = "0.3.0" +name = "crypto_box" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" +checksum = "16182b4f39a82ec8a6851155cc4c0cda3065bb1db33651726a29e1951de0f009" dependencies = [ - "dtor-proc-macro", + "aead", + "crypto_secretbox", + "curve25519-dalek 4.1.3", + "salsa20", + "subtle", + "zeroize", ] [[package]] -name = "dtor-proc-macro" -version = "0.0.6" +name = "crypto_secretbox" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" +checksum = "b9d6cf87adf719ddf43a805e92c6870a531aedda35ff640442cbaf8674e141e1" +dependencies = [ + "aead", + "cipher", + "generic-array", + "poly1305", + "salsa20", + "subtle", + "zeroize", +] [[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - -[[package]] -name = "dyn-clone" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" - -[[package]] -name = "embed-resource" -version = "3.0.11" +name = "csscolorparser" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd" +checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" dependencies = [ - "cc", - "memchr", - "rustc_version", - "toml 1.1.5+spec-1.1.0", - "vswhom", - "winreg 0.55.0", + "lab", + "phf 0.11.3", ] [[package]] -name = "embed_plist" -version = "1.2.2" +name = "cssparser" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" +checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf 0.13.1", + "smallvec", +] [[package]] -name = "endi" -version = "1.1.1" +name = "cssparser-macros" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.119", +] [[package]] -name = "enumflags2" -version = "0.7.12" +name = "ctor" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" dependencies = [ - "enumflags2_derive", - "serde", + "ctor-proc-macro", + "dtor", ] [[package]] -name = "enumflags2_derive" -version = "0.7.12" +name = "ctor" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", + "link-section", + "linktime-proc-macro", ] [[package]] -name = "equivalent" -version = "1.0.2" +name = "ctor-proc-macro" +version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" [[package]] -name = "erased-serde" -version = "0.4.10" +name = "ctr" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "serde", - "serde_core", - "typeid", + "cipher", ] [[package]] -name = "errno" -version = "0.3.14" +name = "ctutils" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" dependencies = [ - "libc", - "windows-sys 0.61.2", + "cmov", ] [[package]] -name = "event-listener" -version = "5.4.2" +name = "curve25519-dalek" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ - "parking", - "pin-project-lite", + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "fiat-crypto 0.2.9", + "rustc_version", + "subtle", + "zeroize", ] [[package]] -name = "event-listener-strategy" -version = "0.5.4" +name = "curve25519-dalek" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" dependencies = [ - "event-listener", - "pin-project-lite", + "cfg-if", + "cpufeatures 0.3.1", + "curve25519-dalek-derive", + "digest 0.11.3", + "fiat-crypto 0.3.0", + "rand_core 0.10.1", + "rustc_version", + "serde", + "subtle", + "zeroize", ] [[package]] -name = "fastrand" -version = "2.5.0" +name = "curve25519-dalek-derive" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] [[package]] -name = "fdeflate" -version = "0.3.7" +name = "darling" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ - "simd-adler32", + "darling_core 0.23.0", + "darling_macro 0.23.0", ] [[package]] -name = "field-offset" -version = "0.3.6" +name = "darling" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" dependencies = [ - "memoffset", - "rustc_version", + "darling_core 0.24.1", + "darling_macro 0.24.1", ] [[package]] -name = "filedescriptor" -version = "0.8.3" +name = "darling_core" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" dependencies = [ - "libc", - "thiserror 1.0.69", - "winapi", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", ] [[package]] -name = "find-msvc-tools" -version = "0.1.12" +name = "darling_core" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" +checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 3.0.5", +] [[package]] -name = "flate2" -version = "1.1.10" +name = "darling_macro" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ - "crc32fast", - "miniz_oxide 0.9.1", - "zlib-rs", + "darling_core 0.23.0", + "quote", + "syn 2.0.119", ] [[package]] -name = "fnv" -version = "1.0.7" +name = "darling_macro" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" +dependencies = [ + "darling_core 0.24.1", + "quote", + "syn 3.0.5", +] [[package]] -name = "foldhash" -version = "0.2.0" +name = "data-encoding" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" [[package]] -name = "foreign-types" -version = "0.5.0" +name = "data-encoding-macro" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +checksum = "c6a127ecbb3c4632e1525380e04c0c3fcf8dcb44d32a79ea290d8a36906edcd8" dependencies = [ - "foreign-types-macros", - "foreign-types-shared", + "data-encoding", + "data-encoding-macro-internal", ] [[package]] -name = "foreign-types-macros" -version = "0.2.4" +name = "data-encoding-macro-internal" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" +checksum = "c54e03a951783e8b327515db3f2a2fd0e3bed362a96b066f341ce66ed49b4ead" dependencies = [ - "proc-macro2", - "quote", + "data-encoding", "syn 3.0.5", ] [[package]] -name = "foreign-types-shared" -version = "0.3.1" +name = "dbus" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e" +dependencies = [ + "libc", + "libdbus-sys", + "windows-sys 0.61.2", +] [[package]] -name = "form_urlencoded" -version = "1.2.2" +name = "dbus-secret-service" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +checksum = "708b509edf7889e53d7efb0ffadd994cc6c2345ccb62f55cfd6b0682165e4fa6" dependencies = [ - "percent-encoding", + "aes", + "block-padding", + "cbc", + "dbus", + "fastrand", + "hkdf", + "num", + "once_cell", + "openssl", + "sha2 0.10.9", + "zeroize", ] [[package]] -name = "fs-set-times" -version = "0.20.3" +name = "defmt" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94e7099f6313ecacbe1256e8ff9d617b75d1bcb16a6fddef94866d225a01a14a" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" dependencies = [ - "io-lifetimes 2.0.4", - "rustix", - "windows-sys 0.59.0", + "bitflags 1.3.2", + "defmt-macros", ] [[package]] -name = "futures-channel" -version = "0.3.34" +name = "defmt-macros" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" dependencies = [ - "futures-core", + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] -name = "futures-core" -version = "0.3.34" +name = "defmt-parser" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.20", +] [[package]] -name = "futures-executor" -version = "0.3.34" +name = "deltae" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] +checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" [[package]] -name = "futures-io" -version = "0.3.34" +name = "der" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" +checksum = "a878c850e9e421b20262e9b41f9c860e4785fa07541c266b62ff9d1ef998a80a" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] [[package]] -name = "futures-lite" -version = "2.6.1" +name = "deranged" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", + "serde_core", ] [[package]] -name = "futures-macro" -version = "0.3.34" +name = "derive_arbitrary" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 2.0.119", ] [[package]] -name = "futures-sink" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" - -[[package]] -name = "futures-task" -version = "0.3.34" +name = "derive_more" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] [[package]] -name = "futures-util" -version = "0.3.34" +name = "derive_more-impl" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", + "unicode-xid", ] [[package]] -name = "gdk" -version = "0.18.2" +name = "diatomic-waker" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "cairo-rs", - "gdk-pixbuf", - "gdk-sys", - "gio", - "glib", - "libc", - "pango", + "block-buffer 0.10.4", + "crypto-common 0.1.7", + "subtle", ] [[package]] -name = "gdk-pixbuf" -version = "0.18.5" +name = "digest" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ - "gdk-pixbuf-sys", - "gio", - "glib", - "libc", - "once_cell", + "block-buffer 0.12.1", + "const-oid", + "crypto-common 0.2.2", ] [[package]] -name = "gdk-pixbuf-sys" -version = "0.18.0" +name = "dirs" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", + "dirs-sys", ] [[package]] -name = "gdk-sys" -version = "0.18.2" +name = "dirs-sys" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", "libc", - "pango-sys", - "pkg-config", - "system-deps", + "option-ext", + "redox_users", + "windows-sys 0.61.2", ] [[package]] -name = "gdkwayland-sys" -version = "0.18.2" +name = "dispatch2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "gdk-sys", - "glib-sys", - "gobject-sys", + "bitflags 2.13.1", + "block2", "libc", - "pkg-config", - "system-deps", + "objc2", ] [[package]] -name = "gdkx11" -version = "0.18.2" +name = "displaydoc" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ - "gdk", - "gdkx11-sys", - "gio", - "glib", - "libc", - "x11", + "proc-macro2", + "quote", + "syn 3.0.5", ] [[package]] -name = "gdkx11-sys" -version = "0.18.2" +name = "dlopen2" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" dependencies = [ - "gdk-sys", - "glib-sys", + "dlopen2_derive", "libc", - "system-deps", - "x11", + "once_cell", + "winapi", ] [[package]] -name = "generic-array" -version = "0.14.7" +name = "dlopen2_derive" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" dependencies = [ - "typenum", - "version_check", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] -name = "getrandom" -version = "0.2.17" +name = "document-features" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" dependencies = [ - "cfg-if", - "libc", - "wasi", + "litrs", ] [[package]] -name = "getrandom" -version = "0.3.4" +name = "dom_query" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" dependencies = [ - "cfg-if", - "libc", - "r-efi 5.3.0", - "wasip2", + "bit-set 0.8.0", + "cssparser", + "foldhash 0.2.0", + "html5ever", + "precomputed-hash", + "selectors", + "tendril", ] [[package]] -name = "getrandom" -version = "0.4.3" +name = "downcast-rs" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" -dependencies = [ - "cfg-if", - "libc", - "r-efi 6.0.0", -] +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] -name = "gio" -version = "0.18.4" +name = "dpi" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "once_cell", - "pin-project-lite", - "smallvec", - "thiserror 1.0.69", + "serde", ] [[package]] -name = "gio-sys" -version = "0.18.1" +name = "dtoa" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "winapi", -] +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" [[package]] -name = "glib" -version = "0.18.5" +name = "dtoa-short" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" dependencies = [ - "bitflags 2.13.1", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "once_cell", - "smallvec", - "thiserror 1.0.69", + "dtoa", ] [[package]] -name = "glib-macros" -version = "0.18.5" +name = "dtor" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" dependencies = [ - "heck 0.4.1", - "proc-macro-crate 2.0.2", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.119", + "dtor-proc-macro", ] [[package]] -name = "glib-sys" -version = "0.18.1" +name = "dtor-proc-macro" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" -dependencies = [ - "libc", - "system-deps", -] +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" [[package]] -name = "glob" -version = "0.3.4" +name = "dunce" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] -name = "gobject-sys" -version = "0.18.0" +name = "dyn-clone" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] -name = "gtk" -version = "0.18.2" +name = "ed25519" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" dependencies = [ - "atk", - "cairo-rs", - "field-offset", - "futures-channel", - "gdk", - "gdk-pixbuf", - "gio", - "glib", - "gtk-sys", - "gtk3-macros", - "libc", - "pango", - "pkg-config", + "pkcs8", + "serdect", + "signature", ] [[package]] -name = "gtk-sys" -version = "0.18.2" +name = "ed25519-dalek" +version = "3.0.0-rc.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +checksum = "b011170fe4f04665565b4110afef66774fe9ffff278f3eb5b81cc73d26e27d60" dependencies = [ - "atk-sys", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "system-deps", + "curve25519-dalek 5.0.0", + "ed25519", + "rand_core 0.10.1", + "serde", + "sha2 0.11.0", + "signature", + "subtle", + "zeroize", ] [[package]] -name = "gtk3-macros" -version = "0.18.2" +name = "either" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" + +[[package]] +name = "embed-resource" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd" dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.119", + "cc", + "memchr", + "rustc_version", + "toml 1.1.5+spec-1.1.0", + "vswhom", + "winreg 0.55.0", ] [[package]] -name = "hashbrown" -version = "0.12.3" +name = "embed_plist" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" [[package]] -name = "hashbrown" -version = "0.17.1" +name = "embedded-io" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" [[package]] -name = "heck" -version = "0.4.1" +name = "embedded-io" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" [[package]] -name = "heck" -version = "0.5.0" +name = "encoding_rs" +version = "0.8.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +checksum = "7b5ef0006ac9ab233c38522f5ae99cae3625151de8f706cacee1cba4b8e2832a" +dependencies = [ + "cfg-if", + "core_detect", + "multiversion", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", +] [[package]] -name = "hermit-abi" -version = "0.5.3" +name = "endi" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" [[package]] -name = "hex" -version = "0.4.3" +name = "enum-assoc" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +checksum = "0590c4a94da3372e83493b956755a6e2266830b6e4e3b101afe66e3f39477b91" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] [[package]] -name = "html5ever" -version = "0.38.0" +name = "enumflags2" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" dependencies = [ - "log", - "markup5ever", + "enumflags2_derive", + "serde", ] [[package]] -name = "http" -version = "1.5.0" +name = "enumflags2_derive" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ - "bytes", - "itoa", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] -name = "http-body" -version = "1.1.0" +name = "equivalent" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" dependencies = [ - "bytes", - "http", + "serde", + "serde_core", + "typeid", ] [[package]] -name = "http-body-util" -version = "0.1.5" +name = "errno" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", + "libc", + "windows-sys 0.61.2", ] [[package]] -name = "httparse" -version = "1.10.1" +name = "euclid" +version = "0.22.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" +dependencies = [ + "num-traits", +] [[package]] -name = "hyper" -version = "1.11.1" +name = "event-listener" +version = "5.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "http", - "http-body", - "httparse", - "itoa", + "parking", "pin-project-lite", - "smallvec", - "tokio", - "want", ] [[package]] -name = "hyper-util" -version = "0.1.20" +name = "event-listener-strategy" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", + "event-listener", "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", ] [[package]] -name = "iana-time-zone" -version = "0.1.65" +name = "fallible-iterator" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core 0.62.2", -] +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" +name = "fallible-streaming-iterator" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] -name = "ico" -version = "0.5.0" +name = "fancy-regex" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" +checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" dependencies = [ - "byteorder", - "png 0.17.16", + "bit-set 0.5.3", + "regex", ] [[package]] -name = "icu_collections" -version = "2.3.0" +name = "faster-hex" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73" dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", + "heapless", + "serde", ] [[package]] -name = "icu_locale_core" -version = "2.3.0" +name = "fastrand" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] -name = "icu_normalizer" -version = "2.3.0" +name = "fdeflate" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", + "simd-adler32", ] [[package]] -name = "icu_normalizer_data" -version = "2.3.0" +name = "fiat-crypto" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] -name = "icu_properties" -version = "2.3.0" +name = "fiat-crypto" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "filedescriptor" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" +dependencies = [ + "libc", + "thiserror 1.0.69", + "winapi", +] + +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" + +[[package]] +name = "finl_unicode" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80bb028c8b4148c9ee0cca68fcd9add6044e81d3619f48577ddf13a263d047a2" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flate2" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" +dependencies = [ + "crc32fast", + "miniz_oxide 0.9.1", + "zlib-rs", +] + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin 0.9.9", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs-set-times" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e7099f6313ecacbe1256e8ff9d617b75d1bcb16a6fddef94866d225a01a14a" +dependencies = [ + "io-lifetimes 2.0.4", + "rustix 1.1.4", + "windows-sys 0.59.0", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-buffered" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4421cb78ee172b6b06080093479d3c50f058e7c81b7d577bbb8d118d551d4cd5" +dependencies = [ + "cordyceps", + "diatomic-waker", + "futures-core", + "pin-project-lite", + "spin 0.10.1", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "gearhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616e8f476a586f1b078d9eece21f7a071f27997e709e2b471f5697deea53bda9" + +[[package]] +name = "generator" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54ade96dc9003043bce7c035c85a9df5a858bfb2039c5a2e6fdf00f324f6c551" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link 0.1.3", + "windows-result 0.3.4", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasm-bindgen", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "git-version" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad568aa3db0fcbc81f2f116137f263d7304f512a1209b35b85150d3ef88ad19" +dependencies = [ + "git-version-macro", +] + +[[package]] +name = "git-version-macro" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53010ccb100b96a67bc32c0175f0ed1426b31b655d562898e57325f81c023ac0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.13.1", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.2", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "globset" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c34a9410465b45bd9787443bc7370f37735bad04b0f0cd57ff1a3186c98988" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "h2" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.14.2", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "heapify" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0049b265b7f201ca9ab25475b22b47fe444060126a51abe00f77d986fc5cc52e" + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-conservative" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3fef046dca3ca91ee1408a8c1b80ab777e80a4d308d1bf4e7adb3fcb047e08" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex-conservative" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271e0d19bcb473b6675739a2b536076b24a082316cb5199ad918edce10c599e8" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hf-xet" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c237ef4fb0ce1962a5117f8bd8c74454b41629826a9df17d14a1840ca18f0754" +dependencies = [ + "anyhow", + "async-trait", + "bytes", + "http", + "more-asserts", + "serde", + "serde_json", + "thiserror 2.0.20", + "tokio", + "tokio-util", + "tokio_with_wasm", + "tracing", + "uuid", + "xet-client", + "xet-core-structures", + "xet-data", + "xet-runtime", +] + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "html5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" +dependencies = [ + "log", + "markup5ever", +] + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15" + +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration 0.7.0", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.61.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ico" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" +dependencies = [ + "byteorder", + "png 0.17.16", +] + +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "identity-hash" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfdd7caa900436d8f13b2346fe10257e0c05c1f1f9e351f4f5d57c03bd5f45da" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "if-addrs" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0a05c691e1fae256cf7013d99dad472dc52d5543322761f83ec8d47eab40d2b" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "igd-next" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de7238d487a9aff61f81b5ab41c0a841532a115a398b5fa92a2fadd0885e2581" +dependencies = [ + "attohttpc", + "bytes", + "futures", + "http", + "http-body-util", + "hyper", + "hyper-util", + "log", + "rand 0.10.3", + "tokio", + "url", + "xmltree", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "instability" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf84e73fa6f27f299dec58e13223cf70db80da872eb921d4f6138342a0eabc8" +dependencies = [ + "darling 0.24.1", + "indoc", + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "io-extras" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20fd6de4ccfcc187e38bc21cfa543cb5a302cb86a8b114eb7f0bf0dc9f8ac00f" +dependencies = [ + "io-lifetimes 3.0.1", + "windows-sys 0.60.2", +] + +[[package]] +name = "io-lifetimes" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983" + +[[package]] +name = "io-lifetimes" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0fb0570afe1fed943c5c3d4102d5358592d8625fda6a0007fdbe65a92fba96" + +[[package]] +name = "ipconfig" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" +dependencies = [ + "socket2", + "widestring", + "windows-registry", + "windows-result 0.4.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "ipnet" +version = "2.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" + +[[package]] +name = "iroh" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2f8d1cfffc83efe39a1031aab423ce09cb8048071baba550508931e9a81ce46" +dependencies = [ + "backon", + "blake3", + "bytes", + "cfg_aliases 0.2.2", + "ctutils", + "data-encoding", + "derive_more", + "ed25519-dalek", + "futures-util", + "getrandom 0.4.3", + "http", + "ipnet", + "iroh-base", + "iroh-dns", + "iroh-metrics", + "iroh-relay", + "n0-error", + "n0-future", + "n0-watcher", + "netwatch", + "noq", + "noq-proto", + "noq-udp", + "papaya", + "pin-project", + "portable-atomic", + "portmapper", + "rand 0.10.3", + "reqwest 0.13.4", + "rustc-hash", + "rustls", + "rustls-pki-types", + "serde", + "smallvec", + "strum", + "time", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", + "url", + "wasm-bindgen-futures", +] + +[[package]] +name = "iroh-base" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffd1efd1aaecd68d4d1b0727a30c5811f43fb822843e48f65f6e5db3b7256cc9" +dependencies = [ + "curve25519-dalek 5.0.0", + "data-encoding", + "data-encoding-macro", + "derive_more", + "ed25519-dalek", + "getrandom 0.4.3", + "n0-error", + "rand 0.10.3", + "serde", + "url", + "zeroize", +] + +[[package]] +name = "iroh-dns" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80276761b56e904e26ea71d0863beef0bb8d5ab6b639cbd1338e4b615209e3a1" +dependencies = [ + "arc-swap", + "cfg_aliases 0.2.2", + "derive_more", + "iroh-base", + "n0-dns-resolver", + "n0-error", + "n0-future", + "portable-atomic", + "rand 0.10.3", + "rustls", + "simple-dns", + "strum", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "iroh-metrics" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291065721ad7c477b972e581bbc528df031dc8eb5e39fe1ff3300ae5dfb157ef" +dependencies = [ + "iroh-metrics-derive", + "itoa", + "n0-error", + "portable-atomic", + "ryu", + "serde", + "tracing", +] + +[[package]] +name = "iroh-metrics-derive" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae5f0c4405d1fbc9fb16ff422ca40620e93dc36c30ecaba0c2aee3992b7bd48" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "iroh-relay" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beb2294a9749d6a25fd7cd8bcf0fccd932f20716d4f967d85d3f23c7135ae6a2" +dependencies = [ + "blake3", + "bytes", + "cfg_aliases 0.2.2", + "data-encoding", + "derive_more", + "getrandom 0.4.3", + "http", + "http-body-util", + "hyper", + "hyper-util", + "iroh-base", + "iroh-dns", + "iroh-metrics", + "lru", + "n0-error", + "n0-future", + "noq", + "noq-proto", + "num_enum", + "pin-project", + "postcard", + "rand 0.10.3", + "reqwest 0.13.4", + "rustls", + "rustls-pki-types", + "serde", + "serde_bytes", + "strum", + "tokio", + "tokio-rustls", + "tokio-util", + "tokio-websockets", + "tracing", + "url", + "webpki-roots 1.0.9", + "ws_stream_wasm", +] + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jiff" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" +dependencies = [ + "defmt", + "jiff-core", + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-link 0.2.1", +] + +[[package]] +name = "jiff-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" +dependencies = [ + "defmt", +] + +[[package]] +name = "jiff-static" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" +dependencies = [ + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.20", + "walkdir", + "windows-link 0.2.1", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jsonptr" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "kasuari" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899" +dependencies = [ + "hashbrown 0.16.1", + "portable-atomic", + "thiserror 2.0.20", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.13.1", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "keyring" +version = "3.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" +dependencies = [ + "byteorder", + "dbus-secret-service", + "log", + "openssl", + "secret-service", + "security-framework 2.11.1", + "security-framework 3.7.0", + "windows-sys 0.60.2", + "zeroize", +] + +[[package]] +name = "konst" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f660d5f887e3562f9ab6f4a14988795b694099d66b4f5dedc02d197ba9becb1d" +dependencies = [ + "const_panic", + "konst_proc_macros", + "typewit", +] + +[[package]] +name = "konst_proc_macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e037a2e1d8d5fdbd49b16a4ea09d5d6401c1f29eca5ff29d03d3824dba16256a" + +[[package]] +name = "lab" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading 0.7.4", + "once_cell", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libdbus-sys" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d8f1ea3f21fd3405dcaf6c9b5c1630af9afc422d9073ea39c5f6d6c772e08ed" +dependencies = [ + "libc", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "line-clipping" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e752191d037c44ad111a8caa762921926658402f01cc1253f7bef2020ece4f5e" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "link-section" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39c29a617ce3df32c08497bdc1ab6e2376e0b17948ac166a2fbe5977c5954cd9" + +[[package]] +name = "linktime-proc-macro" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e57c38c1e860fd37c604281cdfb1dd2216977fd76a50f85ba2f388ef3219616" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "lru" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff9840bcc50b71349309900da0ce7279aa336ae71d73250b07998932c7d97c25" +dependencies = [ + "hashbrown 0.17.1", +] + +[[package]] +name = "lru-slab" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" + +[[package]] +name = "lz4_flex" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef0d4ed8669f8f8826eb00dc878084aa8f253506c4fd5e8f58f5bce72ddb97e" +dependencies = [ + "twox-hash", +] + +[[package]] +name = "mac-addr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d25b0e0b648a86960ac23b7ad4abb9717601dec6f66c165f5b037f3f03065f" + +[[package]] +name = "mac-notification-sys" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd604973958ddcc11b561193c0fb96ba146506ef2f231ef2e7c35fd2cbc9beca" +dependencies = [ + "cc", + "log", + "objc2", + "objc2-foundation", + "time", + "uuid", +] + +[[package]] +name = "mac_address" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" +dependencies = [ + "nix 0.29.0", + "winapi", +] + +[[package]] +name = "markup5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" +dependencies = [ + "log", + "tendril", + "web_atoms", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "maybe-owned" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" + +[[package]] +name = "mdns-sd" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18148fee27e99e76dbf6e137f27727113d31f766e578d1b93a93c3615fca7081" +dependencies = [ + "fastrand", + "flume", + "if-addrs", + "log", + "mio", + "socket-pktinfo", + "socket2", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memmem" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mesh-llm-api-client" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "hex", + "mesh-llm-client", + "thiserror 2.0.20", +] + +[[package]] +name = "mesh-llm-api-server" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "anyhow", + "mesh-llm-api-client", + "mesh-llm-node", + "tokio", +] + +[[package]] +name = "mesh-llm-build-info" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" + +[[package]] +name = "mesh-llm-client" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "anyhow", + "async-trait", + "base64 0.22.1", + "bytes", + "httparse", + "iroh", + "mesh-llm-identity", + "mesh-llm-protocol", + "mesh-llm-routing", + "mesh-llm-types", + "model-artifact", + "nostr-sdk", + "prost", + "rustls", + "serde", + "serde_json", + "thiserror 2.0.20", + "tokio", + "tracing", + "uuid", +] + +[[package]] +name = "mesh-llm-config" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "anyhow", + "dirs", + "mesh-llm-types", + "semver", + "serde", + "skippy-protocol", + "toml 0.9.12+spec-1.1.0", + "toml_edit 0.25.13+spec-1.1.0", + "url", +] + +[[package]] +name = "mesh-llm-embedded-runtime" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "anyhow", + "mesh-llm-host-runtime", + "serde_json", +] + +[[package]] +name = "mesh-llm-events" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "anyhow", + "chrono", + "clap", + "crossterm 0.28.1", + "libc", + "ratatui", + "serde", + "serde_json", + "tracing", + "uuid", +] + +[[package]] +name = "mesh-llm-gpu-bench" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "serde", + "serde_json", + "tracing", +] + +[[package]] +name = "mesh-llm-guardrails" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "mesh-llm-hardware-profile" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "mesh-llm-native-runtime", +] + +[[package]] +name = "mesh-llm-hf-hub" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43088a838cf0c6715c65f65a5ac99045fd6d6e90949a8a4183b8104ab791e96b" +dependencies = [ + "base64 0.22.1", + "bon", + "bytes", + "futures", + "getrandom 0.2.17", + "globset", + "hf-xet", + "hyper", + "pathdiff", + "percent-encoding", + "reqwest 0.13.4", + "serde", + "serde_json", + "sha2 0.11.0", + "thiserror 2.0.20", + "tokio", + "tokio-retry", + "tokio-util", + "tracing", + "url", + "wasm-bindgen-futures", +] + +[[package]] +name = "mesh-llm-host-runtime" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "anyhow", + "argon2", + "async-trait", + "axum", + "base64 0.22.1", + "bytes", + "chacha20poly1305", + "chrono", + "clap", + "crossterm 0.28.1", + "crypto_box", + "dirs", + "ed25519-dalek", + "flate2", + "futures-util", + "hex", + "http", + "http-body-util", + "httparse", + "iroh", + "keyring", + "libc", + "mdns-sd", + "mesh-llm-api-server", + "mesh-llm-build-info", + "mesh-llm-client", + "mesh-llm-config", + "mesh-llm-events", + "mesh-llm-guardrails", + "mesh-llm-hf-hub", + "mesh-llm-identity", + "mesh-llm-log-store", + "mesh-llm-native-runtime", + "mesh-llm-node", + "mesh-llm-plugin", + "mesh-llm-plugin-manager", + "mesh-llm-protocol", + "mesh-llm-routing", + "mesh-llm-runtime-install", + "mesh-llm-system", + "mesh-llm-types", + "mesh-llm-ui", + "mesh-mixture-of-agents", + "mesh-native-serving-plugin-host", + "model-artifact", + "model-hf", + "model-package", + "model-ref", + "model-resolver", + "nostr-sdk", + "openai-frontend", + "opentelemetry", + "opentelemetry-otlp", + "opentelemetry_sdk", + "prost", + "rand 0.10.3", + "regex-lite", + "reqwest 0.12.28", + "rmcp", + "rpassword", + "rustls", + "schemars 1.2.2", + "semver", + "serde", + "serde_json", + "sha2 0.10.9", + "skippy-coordinator", + "skippy-ffi", + "skippy-protocol", + "skippy-runtime", + "skippy-server", + "skippy-topology", + "socket2", + "tabwriter", + "tar", + "tempfile", + "thiserror 2.0.20", + "tokio", + "tokio-stream", + "toml 0.9.12+spec-1.1.0", + "tracing", + "tracing-subscriber", + "url", + "urlencoding", + "uuid", + "windows-sys 0.61.2", + "zeroize", + "zip", +] + +[[package]] +name = "mesh-llm-identity" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "argon2", + "base64 0.22.1", + "chacha20poly1305", + "chrono", + "crypto_box", + "dirs", + "ed25519-dalek", + "hex", + "keyring", + "rand 0.10.3", + "serde", + "serde_json", + "sha2 0.10.9", + "thiserror 2.0.20", + "zeroize", +] + +[[package]] +name = "mesh-llm-log-store" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "chrono", + "data-encoding", + "hex", + "mesh-llm-events", + "rusqlite", + "serde", + "serde_json", + "sha2 0.10.9", + "thiserror 2.0.20", + "uuid", + "windows-sys 0.61.2", +] + +[[package]] +name = "mesh-llm-native-runtime" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "anyhow", + "serde", + "serde_json", + "sha2 0.10.9", +] + +[[package]] +name = "mesh-llm-node" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "anyhow", + "mesh-llm-types", + "model-artifact", + "model-hf", + "model-ref", + "serde", + "serde_json", +] + +[[package]] +name = "mesh-llm-plugin" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "anyhow", + "async-trait", + "prost", + "prost-build", + "protoc-bin-vendored", + "rmcp", + "schemars 1.2.2", + "serde", + "serde_json", + "tokio", +] + +[[package]] +name = "mesh-llm-plugin-manager" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "anyhow", + "dirs", + "flate2", + "futures-util", + "mesh-llm-skills", + "reqwest 0.12.28", + "serde", + "serde_json", + "sha2 0.10.9", + "tar", + "tempfile", + "zip", +] + +[[package]] +name = "mesh-llm-protocol" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "anyhow", + "hex", + "iroh", + "prost", + "serde_json", + "sha2 0.10.9", +] + +[[package]] +name = "mesh-llm-release-footer" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "hex", + "sha2 0.10.9", +] + +[[package]] +name = "mesh-llm-routing" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "blake3", + "iroh", + "serde", + "serde_json", +] + +[[package]] +name = "mesh-llm-runtime-install" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "anyhow", + "dirs", + "flate2", + "futures-util", + "hex", + "mesh-llm-build-info", + "mesh-llm-hardware-profile", + "mesh-llm-native-runtime", + "reqwest 0.12.28", + "serde", + "serde_json", + "sha2 0.10.9", + "skippy-ffi", + "tar", + "tempfile", + "tokio", +] + +[[package]] +name = "mesh-llm-sdk" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "anyhow", + "mesh-llm-api-client", + "mesh-llm-api-server", + "mesh-llm-embedded-runtime", + "mesh-llm-runtime-install", + "reqwest 0.12.28", + "serde", + "serde_json", +] + +[[package]] +name = "mesh-llm-skills" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "anyhow", + "dirs", + "serde", + "serde_json", +] + +[[package]] +name = "mesh-llm-system" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "anyhow", + "chrono", + "clap", + "dirs", + "hex", + "libc", + "libloading 0.8.9", + "mesh-llm-build-info", + "mesh-llm-gpu-bench", + "mesh-llm-native-runtime", + "mesh-llm-release-footer", + "mesh-llm-runtime-install", + "reqwest 0.12.28", + "semver", + "serde", + "serde_json", + "sha2 0.10.9", + "skippy-runtime", + "tracing", + "zip", +] + +[[package]] +name = "mesh-llm-types" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "hex", + "serde", + "serde_json", + "sha2 0.10.9", +] + +[[package]] +name = "mesh-llm-ui" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" + +[[package]] +name = "mesh-mixture-of-agents" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "async-trait", + "mesh-llm-guardrails", + "reqwest 0.12.28", + "serde", + "serde_json", + "tokio", + "tracing", +] + +[[package]] +name = "mesh-native-serving-plugin-api" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" + +[[package]] +name = "mesh-native-serving-plugin-host" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "anyhow", + "libloading 0.8.9", + "mesh-native-serving-plugin-api", + "skippy-server", + "skippy-tokenizer", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" +dependencies = [ + "libc", + "log", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.61.2", +] + +[[package]] +name = "model-artifact" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "anyhow", + "async-trait", + "model-ref", + "serde", +] + +[[package]] +name = "model-hf" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "anyhow", + "async-trait", + "chrono", + "dirs", + "libc", + "mesh-llm-hf-hub", + "model-artifact", + "model-ref", + "rustls", + "serde", + "serde_json", + "sha2 0.10.9", + "tokio", + "tracing", +] + +[[package]] +name = "model-package" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "anyhow", + "bytes", + "chrono", + "futures", + "mesh-llm-hf-hub", + "model-hf", + "model-ref", + "reqwest 0.12.28", + "serde", + "serde_json", + "sha2 0.10.9", + "tempfile", + "tokio", +] + +[[package]] +name = "model-ref" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "serde", +] + +[[package]] +name = "model-resolver" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "anyhow", + "model-artifact", + "model-ref", + "serde", + "serde_json", +] + +[[package]] +name = "more-asserts" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fafa6961cabd9c63bcd77a45d7e3b7f3b552b70417831fb0f56db717e72407e" + +[[package]] +name = "muda" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "once_cell", + "png 0.18.1", + "serde", + "thiserror 2.0.20", + "windows-sys 0.61.2", +] + +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "multiversion" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ca4bea16ffc3f443cf7d866912118196bfef4c6a1556ca00f9f9b00bb43f7c" +dependencies = [ + "multiversion-macros", +] + +[[package]] +name = "multiversion-macros" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d416831a7317ef4b08bee00b69cbbb9c8763da7959a7026244d6266869f9c83" +dependencies = [ + "proc-macro2", + "quote", + "rustversion", + "syn 3.0.5", +] + +[[package]] +name = "multiversion_no_op" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" + +[[package]] +name = "n0-dns-resolver" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "535c99c9a786bee714155d2ee4fc2477405c2c43536e1bc6a51de09a0d1448f7" +dependencies = [ + "derive_more", + "ipconfig", + "jni 0.22.4", + "lru", + "n0-error", + "n0-future", + "ndk-context", + "rand 0.10.3", + "reqwest 0.13.4", + "rustc-hash", + "rustls", + "simple-dns", + "system-configuration 0.8.0", + "tokio", + "tokio-rustls", + "tracing", + "webpki-roots 1.0.9", +] + +[[package]] +name = "n0-error" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c59ea174675ce6bc196878851e5049e11b8b1f9af3ba87e4d6df8d828bb001" +dependencies = [ + "n0-error-macros", + "spez", +] + +[[package]] +name = "n0-error-macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4412346410d6616f3668c40e53c298e5320c7b856f7a960e5914e442601be79f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "n0-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2ab99dfb861450e68853d34ae665243a88b8c493d01ba957321a1e9b2312bbe" +dependencies = [ + "cfg_aliases 0.2.2", + "derive_more", + "futures-buffered", + "futures-lite", + "futures-util", + "js-sys", + "pin-project", + "send_wrapper", + "tokio", + "tokio-util", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-time", +] + +[[package]] +name = "n0-watcher" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc618745ad0b7414b149d0517ad8b5573b2fb4d4e2717add3d2446ce1fdd826" +dependencies = [ + "derive_more", + "n0-error", + "n0-future", +] + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework 3.7.0", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.13.1", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "negentropy" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81c353b400a5503efdcf398f11a83fb7aa84f59f5d76fc4bf5bbc1e4f5366caa" + +[[package]] +name = "netdev" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "096c44b66a9b09b99b4dd36b1c295ff3a492039ccecaf55466bd6ddcdba59968" +dependencies = [ + "block2", + "dispatch2", + "dlopen2", + "ipnet", + "jni 0.21.1", + "libc", + "mac-addr", + "ndk-context", + "netlink-packet-core 0.8.2", + "netlink-packet-route 0.31.0", + "netlink-sys 0.8.8", + "objc2", + "objc2-core-foundation", + "objc2-core-wlan", + "objc2-foundation", + "objc2-system-configuration", + "once_cell", + "plist", + "windows-sys 0.61.2", +] + +[[package]] +name = "netdev" +version = "0.46.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c52c2584961c68f5a6e3356797344061f818c93b8acc9cd6d7e284795e563e" +dependencies = [ + "block2", + "dispatch2", + "dlopen2", + "ipnet", + "jni 0.21.1", + "libc", + "mac-addr", + "ndk-context", + "netlink-packet-core 0.9.0", + "netlink-packet-route 0.33.0", + "netlink-sys 0.9.0", + "objc2-core-foundation", + "objc2-system-configuration", + "once_cell", + "plist", + "windows-sys 0.61.2", +] + +[[package]] +name = "netlink-packet-core" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b897d7bd4f0af82e68d40d0344cf37e97f9c97ddf74a098de3e4da05e96ca395" +dependencies = [ + "paste", +] + +[[package]] +name = "netlink-packet-core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6e2daf9a8c2e21302714706860a0e6be02e03d17294ecc66b354ea7d4059dd" + +[[package]] +name = "netlink-packet-route" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2288fcb784eb3defd5fb16f4c4160d5f477de192eac730f43e1d11c24d9a007" +dependencies = [ + "bitflags 2.13.1", + "libc", + "log", + "netlink-packet-core 0.8.2", +] + +[[package]] +name = "netlink-packet-route" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59d48ffc8b73a506e1ff0878528c72668f2bfbc3d875b6be890a96be5d0d5576" +dependencies = [ + "bitflags 2.13.1", + "libc", + "log", + "netlink-packet-core 0.9.0", + "zerocopy", +] + +[[package]] +name = "netlink-proto" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93af8261786086024cd5e96e0a991dd65ced07bbf7c233a487bbc96b971d5539" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "log", + "netlink-packet-core 0.8.2", + "netlink-sys 0.8.8", + "thiserror 2.0.20", +] + +[[package]] +name = "netlink-sys" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6c30ed10fa69cc491d491b85cc971f6bdeb8e7367b7cde2ee6cc878d583fae" +dependencies = [ + "bytes", + "futures-util", + "libc", + "log", + "tokio", +] + +[[package]] +name = "netlink-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c99d38e00b420df49e940fe0826e667c3dad82ac68eb4c2bbf754c1c14a83a10" +dependencies = [ + "bytes", + "libc", + "log", +] + +[[package]] +name = "netwatch" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39da9cad5f23aa43401f09497d3b280e51b5feba115d9ffbf38ca35d6ff95e96" +dependencies = [ + "atomic-waker", + "bytes", + "cfg_aliases 0.2.2", + "derive_more", + "ipnet", + "js-sys", + "libc", + "n0-error", + "n0-future", + "n0-watcher", + "netdev 0.45.1", + "netdev 0.46.3", + "netlink-packet-core 0.8.2", + "netlink-packet-route 0.31.0", + "netlink-proto", + "netlink-sys 0.8.8", + "noq-udp", + "objc2-core-foundation", + "objc2-system-configuration", + "pin-project-lite", + "serde", + "socket2", + "time", + "tokio", + "tokio-util", + "tracing", + "web-sys", + "windows 0.62.2", + "windows-result 0.4.1", + "wmi", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nix" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +dependencies = [ + "bitflags 2.13.1", + "cfg-if", + "cfg_aliases 0.1.1", + "libc", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.13.1", + "cfg-if", + "cfg_aliases 0.2.2", + "libc", + "memoffset", +] + +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.13.1", + "cfg-if", + "cfg_aliases 0.2.2", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "noq" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b78be567e796cfa74bb9bdc4117790af2d505eb887019cf8803244353eb09d89" +dependencies = [ + "bytes", + "cfg_aliases 0.2.2", + "derive_more", + "noq-proto", + "noq-udp", + "pin-project-lite", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.20", + "tokio", + "tokio-stream", + "tracing", + "web-time", +] + +[[package]] +name = "noq-proto" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c1e5b6fe668491eca022f745a0a9402585626c73a7b839b3424ace15d6a9c8f" +dependencies = [ + "aes-gcm", + "aws-lc-rs", + "bytes", + "derive_more", + "enum-assoc", + "getrandom 0.4.3", + "identity-hash", + "lru-slab", + "rand 0.10.3", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "sorted-index-buffer", + "thiserror 2.0.20", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "noq-udp" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dc50afea61aff926e150a36c31f06094608848e114a3fe667d76ec233163b1c" +dependencies = [ + "cfg_aliases 0.2.2", + "libc", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "nostr" +version = "0.44.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ff7b77ef428b40aa2834a6acbae38a0e104c98b306208ca4b87a420d579a4b" +dependencies = [ + "base64 0.22.1", + "bech32 0.11.1", + "bip39", + "bitcoin_hashes 0.14.101", + "cbc", + "chacha20 0.9.1", + "chacha20poly1305", + "getrandom 0.2.17", + "hex", + "instant", + "scrypt", + "secp256k1 0.29.1", + "serde", + "serde_json", + "unicode-normalization", + "url", +] + +[[package]] +name = "nostr" +version = "0.45.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38ca5c25a6df8d4d78fdf39b42792438f6ce22b4809ccbfdb27582ed9ca45407" +dependencies = [ + "base64 0.22.1", + "bech32 0.12.0", + "bip39", + "bitcoin_hashes 1.2.0", + "cbc", + "chacha20 0.9.1", + "chacha20poly1305", + "faster-hex", + "opaquerr", + "rand 0.10.3", + "secp256k1 0.30.0", + "serde", + "serde_json", + "unicode-normalization", + "universal-time", + "url", + "zeroize", +] + +[[package]] +name = "nostr-database" +version = "0.45.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "309950383c9854ca413d195705400e78b1376aedc48f3256754a86c609c047e8" +dependencies = [ + "nostr 0.45.5", + "opaquerr", +] + +[[package]] +name = "nostr-gossip" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea822fd48ff6b84b81ddf84169e6edf1dc0bc9b312c8e41685b2278debaac3d" +dependencies = [ + "nostr 0.45.5", + "opaquerr", +] + +[[package]] +name = "nostr-sdk" +version = "0.45.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db717044f755b5ed9be53cacb59660c36efbe578bde870151a24d6bead3cb218" +dependencies = [ + "async-utility", + "async-wsocket", + "faster-hex", + "futures", + "lru", + "negentropy", + "nostr 0.45.5", + "nostr-database", + "nostr-gossip", + "opaquerr", + "rand 0.10.3", + "tokio", + "tokio-stream", + "tracing", + "universal-time", +] + +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", + "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.13.1", + "block2", + "dispatch2", + "libc", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.13.1", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" dependencies = [ - "displaydoc", - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", + "objc2", + "objc2-foundation", ] [[package]] -name = "icu_properties_data" -version = "2.3.0" +name = "objc2-core-location" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2", + "objc2-foundation", +] [[package]] -name = "icu_provider" -version = "2.3.1" +name = "objc2-core-text" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-core-wlan" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e34919aba0d701380d911702455038a8a3587467fe0141d6a71501e7ffe48" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", + "objc2-foundation", + "objc2-security", + "objc2-security-foundation", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.13.1", + "block2", + "libc", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-security" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-security-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef76382e9cedd18123099f17638715cc3d81dba3637d4c0d39ab69df2ef345a5" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-system-configuration" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396" +dependencies = [ + "bitflags 2.13.1", + "dispatch2", + "libc", + "objc2", + "objc2-core-foundation", + "objc2-security", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation", + "objc2-quartz-core", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-web-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" +dependencies = [ + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "oneshot" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "269bca4c2591a28585d6bf10d9ed0332b7d76900a1b02bec41bdc3a2cdcda107" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "opaquerr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f933a4265d5cdad61d19bbdfc972ea5726d56cd8d3d57b8f2d3c365dd42bee9" + +[[package]] +name = "open" +version = "5.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa576c76302b7b808eecc68061e67336c47833ef9d22caa74dda10fa9675eebc" +dependencies = [ + "dunce", + "is-wsl", + "libc", +] + +[[package]] +name = "openai-frontend" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "async-trait", + "axum", + "futures-core", + "futures-util", + "mesh-llm-events", + "mesh-llm-guardrails", + "serde", + "serde_json", + "tokio", + "tracing", + "uuid", +] + +[[package]] +name = "openssl" +version = "0.10.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" +dependencies = [ + "bitflags 2.13.1", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-src" +version = "300.6.1+3.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46eb8fb9fb3b61ce1c0f8a026c4c1a0714d3a9e138e7fbde78753ce2babc3846" +dependencies = [ + "cc", ] [[package]] -name = "ident_case" -version = "1.0.1" +name = "openssl-sys" +version = "0.9.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" +dependencies = [ + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] [[package]] -name = "idna" -version = "1.1.0" +name = "opentelemetry" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +checksum = "b0142c63252a9e054e68a4c61a5778f7b14f576274d593f8ce883d191a099682" dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror 2.0.20", + "tracing", ] [[package]] -name = "idna_adapter" -version = "1.2.2" +name = "opentelemetry-http" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +checksum = "5683015d09e2df236ef005b17f6f196f0d5f6313c4fa43a7b6a53b52776e4331" dependencies = [ - "icu_normalizer", - "icu_properties", + "async-trait", + "bytes", + "http", + "opentelemetry", + "reqwest 0.13.4", ] [[package]] -name = "indexmap" -version = "1.9.3" +name = "opentelemetry-otlp" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "9966929966d17620d7c316c643ba62631826e10021409357772d5eea84f62c35" dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", + "http", + "opentelemetry", + "opentelemetry-http", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost", + "reqwest 0.13.4", + "thiserror 2.0.20", ] [[package]] -name = "indexmap" -version = "2.14.2" +name = "opentelemetry-proto" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +checksum = "56d658ba1faf63f7b9c492cfbe6e0ec365440a16132d3270c1065f7b33f1b638" dependencies = [ - "equivalent", - "hashbrown 0.17.1", + "base64 0.22.1", + "const-hex", + "opentelemetry", + "opentelemetry_sdk", + "prost", "serde", - "serde_core", + "tonic", + "tonic-prost", ] [[package]] -name = "infer" -version = "0.19.0" +name = "opentelemetry_sdk" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +checksum = "9b59f80e1ac4d5ff7a2db8fb6c80badb7f0f3f858211fba08dd9aaec750894f9" dependencies = [ - "cfb", + "futures-channel", + "futures-executor", + "futures-util", + "opentelemetry", + "percent-encoding", + "portable-atomic", + "rand 0.9.5", + "thiserror 2.0.20", ] [[package]] -name = "io-extras" -version = "0.19.0" +name = "option-ext" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20fd6de4ccfcc187e38bc21cfa543cb5a302cb86a8b114eb7f0bf0dc9f8ac00f" -dependencies = [ - "io-lifetimes 3.0.1", - "windows-sys 0.60.2", -] +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] -name = "io-lifetimes" -version = "2.0.4" +name = "ordered-float" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] [[package]] -name = "io-lifetimes" -version = "3.0.1" +name = "ordered-stream" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0fb0570afe1fed943c5c3d4102d5358592d8625fda6a0007fdbe65a92fba96" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] [[package]] -name = "ipnet" -version = "2.12.2" +name = "os_str_bytes" +version = "6.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" +dependencies = [ + "memchr", +] [[package]] -name = "is-docker" -version = "0.2.0" +name = "palette" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +checksum = "ddeed8580d347d2abf3dcf06a5f0b3dc020258338526b277847cd4248a70fc64" dependencies = [ - "once_cell", + "approx", + "libm", + "palette_derive", + "palette_math", ] [[package]] -name = "is-wsl" -version = "0.4.0" +name = "palette_derive" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +checksum = "88537020289b719d81be994ccf1bbf4990f477e2f69ee52fe3e45f43a02e56be" dependencies = [ - "is-docker", - "once_cell", + "by_address", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] -name = "itoa" -version = "1.0.18" +name = "palette_math" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +checksum = "6e6eb142958d64335fb0e345c5b9ead2ecd6fc438c307e9d7d3c4fd428dbaf12" +dependencies = [ + "libm", +] [[package]] -name = "javascriptcore-rs" -version = "1.1.2" +name = "pango" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" dependencies = [ - "bitflags 1.3.2", + "gio", "glib", - "javascriptcore-rs-sys", + "libc", + "once_cell", + "pango-sys", ] [[package]] -name = "javascriptcore-rs-sys" -version = "1.1.1" +name = "pango-sys" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" dependencies = [ "glib-sys", "gobject-sys", @@ -1916,986 +6158,1024 @@ dependencies = [ ] [[package]] -name = "jiff" -version = "0.2.35" +name = "papaya" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" +checksum = "da2442474a9404698c42509b8967f437249dbc7b50493e83020333d3943ec0ae" dependencies = [ - "defmt", - "jiff-core", - "jiff-static", - "jiff-tzdb-platform", - "log", - "portable-atomic", - "portable-atomic-util", - "serde_core", - "windows-link 0.2.1", + "equivalent", + "seize", ] [[package]] -name = "jiff-core" -version = "0.1.0" +name = "parking" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" -dependencies = [ - "defmt", -] +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] -name = "jiff-static" -version = "0.2.35" +name = "parking_lot" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ - "jiff-core", - "proc-macro2", - "quote", - "syn 2.0.119", + "lock_api", + "parking_lot_core", ] [[package]] -name = "jiff-tzdb" -version = "0.1.8" +name = "parking_lot_core" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] [[package]] -name = "jiff-tzdb-platform" -version = "0.1.3" +name = "password-hash" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ - "jiff-tzdb", + "base64ct", + "rand_core 0.6.4", + "subtle", ] [[package]] -name = "jni" -version = "0.21.1" +name = "paste" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys 0.3.1", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] -name = "jni-sys" -version = "0.3.1" +name = "pastey" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ - "jni-sys 0.4.1", + "digest 0.10.7", + "hmac", ] [[package]] -name = "jni-sys" -version = "0.4.1" +name = "pem-rfc7468" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" dependencies = [ - "jni-sys-macros", + "base64ct", ] [[package]] -name = "jni-sys-macros" -version = "0.4.1" +name = "percent-encoding" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pest" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45d3aca230fad2e6f6317ca0a72724338c4960cb97168a85cdee66df4a9a21a8" dependencies = [ - "quote", - "syn 2.0.119", + "memchr", + "ucd-trie", ] [[package]] -name = "js-sys" -version = "0.3.105" +name = "pest_derive" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" +checksum = "284b60557f2c4a2e72ad3f2d34d42685a2fa4a6a61d0d2a10c0ae2a5e916c2cf" dependencies = [ - "cfg-if", - "futures-util", - "wasm-bindgen", + "pest", + "pest_generator", ] [[package]] -name = "json-patch" -version = "3.0.1" +name = "pest_generator" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +checksum = "1d9d1f08a115309ee99268cf85e5228e0e56aa9caf8841ec12866b6be07c3109" dependencies = [ - "jsonptr", - "serde", - "serde_json", - "thiserror 1.0.69", + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] -name = "jsonptr" -version = "0.6.3" +name = "pest_meta" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +checksum = "ed93ba1a9ffcca32130a5188701c81c0c49cf00d4b7c5007d5148951d743adcb" dependencies = [ - "serde", - "serde_json", + "pest", ] [[package]] -name = "keyboard-types" -version = "0.7.0" +name = "petgraph" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ - "bitflags 2.13.1", - "serde", - "unicode-segmentation", + "fixedbitset 0.5.7", + "hashbrown 0.15.5", + "indexmap 2.14.2", ] [[package]] -name = "lazy_static" -version = "1.5.0" +name = "pharos" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" +dependencies = [ + "futures", + "rustc_version", +] [[package]] -name = "libappindicator" -version = "0.9.0" +name = "phf" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ - "glib", - "gtk", - "gtk-sys", - "libappindicator-sys", - "log", + "phf_macros 0.11.3", + "phf_shared 0.11.3", ] [[package]] -name = "libappindicator-sys" -version = "0.9.0" +name = "phf" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ - "gtk-sys", - "libloading", - "once_cell", + "phf_macros 0.13.1", + "phf_shared 0.13.1", + "serde", ] [[package]] -name = "libc" -version = "0.2.189" +name = "phf_codegen" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] [[package]] -name = "libdbus-sys" -version = "0.2.7" +name = "phf_codegen" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" dependencies = [ - "pkg-config", + "phf_generator 0.13.1", + "phf_shared 0.13.1", ] [[package]] -name = "libloading" -version = "0.7.4" +name = "phf_generator" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ - "cfg-if", - "winapi", + "phf_shared 0.11.3", + "rand 0.8.8", ] [[package]] -name = "libredox" -version = "0.1.23" +name = "phf_generator" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d8f1ea3f21fd3405dcaf6c9b5c1630af9afc422d9073ea39c5f6d6c772e08ed" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ - "libc", + "fastrand", + "phf_shared 0.13.1", ] [[package]] -name = "linux-raw-sys" -version = "0.12.1" +name = "phf_macros" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.119", +] [[package]] -name = "litemap" -version = "0.8.3" +name = "phf_macros" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", + "proc-macro2", + "quote", + "syn 2.0.119", +] [[package]] -name = "lock_api" -version = "0.4.14" +name = "phf_shared" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ - "scopeguard", + "siphasher", ] [[package]] -name = "log" -version = "0.4.34" +name = "phf_shared" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] [[package]] -name = "mac-notification-sys" -version = "0.6.15" +name = "pin-project" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd604973958ddcc11b561193c0fb96ba146506ef2f231ef2e7c35fd2cbc9beca" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" dependencies = [ - "cc", - "log", - "objc2", - "objc2-foundation", - "time", - "uuid", + "pin-project-internal", ] [[package]] -name = "markup5ever" -version = "0.38.0" +name = "pin-project-internal" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ - "log", - "tendril", - "web_atoms", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] -name = "maybe-owned" -version = "0.3.4" +name = "pin-project-lite" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] -name = "memchr" -version = "2.8.3" +name = "piper" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] [[package]] -name = "memoffset" -version = "0.9.1" +name = "pkcs8" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" dependencies = [ - "autocfg", + "der", + "spki", ] [[package]] -name = "mime" -version = "0.3.17" +name = "pkg-config" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" [[package]] -name = "miniz_oxide" -version = "0.8.9" +name = "plist" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +checksum = "2896bade328c13f7042a297ea5ac5b0951f6cf989dea5f32c2fd98da398195cb" dependencies = [ - "adler2", - "simd-adler32", + "base64 0.23.1", + "indexmap 2.14.2", + "quick-xml", + "serde", + "time", ] [[package]] -name = "miniz_oxide" -version = "0.9.1" +name = "png" +version = "0.17.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" dependencies = [ - "adler2", - "simd-adler32", + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.8.9", ] [[package]] -name = "mio" -version = "1.2.3" +name = "png" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", + "bitflags 2.13.1", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.8.9", ] [[package]] -name = "muda" -version = "0.19.3" +name = "polling" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" dependencies = [ - "crossbeam-channel", - "dpi", - "gtk", - "keyboard-types", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "once_cell", - "png 0.18.1", - "serde", - "thiserror 2.0.20", + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.1.4", "windows-sys 0.61.2", ] [[package]] -name = "ndk" -version = "0.9.0" +name = "poly1305" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ - "bitflags 2.13.1", - "jni-sys 0.3.1", - "log", - "ndk-sys", - "num_enum", - "raw-window-handle", - "thiserror 1.0.69", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", ] [[package]] -name = "ndk-sys" -version = "0.6.0+11769913" +name = "polyval" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ - "jni-sys 0.3.1", + "cfg-if", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", ] [[package]] -name = "new_debug_unreachable" -version = "1.0.6" +name = "portable-atomic" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" +dependencies = [ + "serde", +] [[package]] -name = "nix" -version = "0.28.0" +name = "portable-atomic-util" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" dependencies = [ - "bitflags 2.13.1", - "cfg-if", - "cfg_aliases", - "libc", + "portable-atomic", ] [[package]] -name = "num-conv" -version = "0.2.2" +name = "portable-pty" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" +checksum = "b4a596a2b3d2752d94f51fac2d4a96737b8705dddd311a32b9af47211f08671e" +dependencies = [ + "anyhow", + "bitflags 1.3.2", + "downcast-rs", + "filedescriptor", + "lazy_static", + "libc", + "log", + "nix 0.28.0", + "serial2", + "shared_library", + "shell-words", + "winapi", + "winreg 0.10.1", +] [[package]] -name = "num-traits" -version = "0.2.19" +name = "portmapper" +version = "0.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +checksum = "ca97242f016e090a25330613bc2382aab65eb22f9149dbe84d8f8e711d49a530" dependencies = [ - "autocfg", + "base64 0.22.1", + "bytes", + "derive_more", + "hyper-util", + "igd-next", + "iroh-metrics", + "libc", + "n0-error", + "n0-future", + "netwatch", + "num_enum", + "rand 0.10.3", + "serde", + "smallvec", + "socket2", + "time", + "tokio", + "tokio-util", + "tower-layer", + "tracing", + "url", ] [[package]] -name = "num_enum" -version = "0.7.6" +name = "postcard" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" dependencies = [ - "num_enum_derive", - "rustversion", + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "postcard-derive", + "serde", ] [[package]] -name = "num_enum_derive" -version = "0.7.6" +name = "postcard-derive" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +checksum = "e0232bd009a197ceec9cc881ba46f727fcd8060a2d8d6a9dde7a69030a6fe2bb" dependencies = [ - "proc-macro-crate 3.5.0", "proc-macro2", "quote", "syn 2.0.119", ] [[package]] -name = "objc2" -version = "0.6.4" +name = "potential_utf" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" dependencies = [ - "objc2-encode", - "objc2-exception-helper", + "zerovec", ] [[package]] -name = "objc2-app-kit" -version = "0.3.2" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" -dependencies = [ - "bitflags 2.13.1", - "block2", - "objc2", - "objc2-core-foundation", - "objc2-foundation", -] +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] -name = "objc2-cloud-kit" -version = "0.3.2" +name = "ppv-lite86" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "bitflags 2.13.1", - "objc2", - "objc2-foundation", + "zerocopy", ] [[package]] -name = "objc2-core-data" -version = "0.3.2" +name = "precomputed-hash" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" -dependencies = [ - "objc2", - "objc2-foundation", -] +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] -name = "objc2-core-foundation" -version = "0.3.2" +name = "prettyplease" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ - "bitflags 2.13.1", - "dispatch2", - "objc2", + "proc-macro2", + "syn 2.0.119", ] [[package]] -name = "objc2-core-graphics" -version = "0.3.2" +name = "prettyplease" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +checksum = "2bfe0f4c752e450fc2faf62654f1c134747922825d5b04ca717b8874f41a40c0" dependencies = [ - "bitflags 2.13.1", - "dispatch2", - "objc2", - "objc2-core-foundation", - "objc2-io-surface", + "proc-macro2", + "syn 3.0.5", ] [[package]] -name = "objc2-core-image" -version = "0.3.2" +name = "proc-macro-crate" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ - "objc2", - "objc2-foundation", + "once_cell", + "toml_edit 0.19.15", ] [[package]] -name = "objc2-core-location" -version = "0.3.2" +name = "proc-macro-crate" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" dependencies = [ - "objc2", - "objc2-foundation", + "toml_datetime 0.6.3", + "toml_edit 0.20.2", ] [[package]] -name = "objc2-core-text" -version = "0.3.2" +name = "proc-macro-crate" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "bitflags 2.13.1", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", + "toml_edit 0.25.13+spec-1.1.0", ] [[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-exception-helper" -version = "0.1.1" +name = "proc-macro-error" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ - "cc", + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", ] [[package]] -name = "objc2-foundation" -version = "0.3.2" +name = "proc-macro-error-attr" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "bitflags 2.13.1", - "block2", - "libc", - "objc2", - "objc2-core-foundation", + "proc-macro2", + "quote", + "version_check", ] [[package]] -name = "objc2-io-surface" -version = "0.3.2" +name = "proc-macro2" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ - "bitflags 2.13.1", - "objc2", - "objc2-core-foundation", + "unicode-ident", ] [[package]] -name = "objc2-quartz-core" -version = "0.3.2" +name = "process-wrap" +version = "9.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +checksum = "2e842efad9119158434d193c6682e2ebee4b44d6ad801d7b349623b3f57cdf55" dependencies = [ - "bitflags 2.13.1", - "objc2", - "objc2-core-foundation", - "objc2-foundation", + "futures", + "indexmap 2.14.2", + "nix 0.31.3", + "tokio", + "tracing", + "windows 0.62.2", ] [[package]] -name = "objc2-ui-kit" -version = "0.3.2" +name = "proptest" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ "bitflags 2.13.1", - "block2", - "objc2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-core-image", - "objc2-core-location", - "objc2-core-text", - "objc2-foundation", - "objc2-quartz-core", - "objc2-user-notifications", + "num-traits", + "rand 0.9.5", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax", + "unarray", ] [[package]] -name = "objc2-user-notifications" -version = "0.3.2" +name = "prost" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" dependencies = [ - "objc2", - "objc2-foundation", + "bytes", + "prost-derive", ] [[package]] -name = "objc2-web-kit" -version = "0.3.2" +name = "prost-build" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" +checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042" dependencies = [ - "bitflags 2.13.1", - "block2", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", + "heck 0.4.1", + "itertools", + "log", + "multimap", + "petgraph", + "prettyplease 0.2.37", + "prost", + "prost-types", + "regex", + "syn 2.0.119", + "tempfile", ] [[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "open" -version = "5.4.4" +name = "prost-derive" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa576c76302b7b808eecc68061e67336c47833ef9d22caa74dda10fa9675eebc" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" dependencies = [ - "dunce", - "is-wsl", - "libc", + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "ordered-stream" -version = "0.2.0" +name = "prost-types" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a" dependencies = [ - "futures-core", - "pin-project-lite", + "prost", ] [[package]] -name = "pango" -version = "0.18.3" +name = "protoc-bin-vendored" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +checksum = "d1c381df33c98266b5f08186583660090a4ffa0889e76c7e9a5e175f645a67fa" dependencies = [ - "gio", - "glib", - "libc", - "once_cell", - "pango-sys", + "protoc-bin-vendored-linux-aarch_64", + "protoc-bin-vendored-linux-ppcle_64", + "protoc-bin-vendored-linux-s390_64", + "protoc-bin-vendored-linux-x86_32", + "protoc-bin-vendored-linux-x86_64", + "protoc-bin-vendored-macos-aarch_64", + "protoc-bin-vendored-macos-x86_64", + "protoc-bin-vendored-win32", ] [[package]] -name = "pango-sys" -version = "0.18.0" +name = "protoc-bin-vendored-linux-aarch_64" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] +checksum = "c350df4d49b5b9e3ca79f7e646fde2377b199e13cfa87320308397e1f37e1a4c" [[package]] -name = "parking" -version = "2.2.1" +name = "protoc-bin-vendored-linux-ppcle_64" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" +checksum = "a55a63e6c7244f19b5c6393f025017eb5d793fd5467823a099740a7a4222440c" [[package]] -name = "parking_lot" -version = "0.12.5" +name = "protoc-bin-vendored-linux-s390_64" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] +checksum = "1dba5565db4288e935d5330a07c264a4ee8e4a5b4a4e6f4e83fad824cc32f3b0" [[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link 0.2.1", -] +name = "protoc-bin-vendored-linux-x86_32" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8854774b24ee28b7868cd71dccaae8e02a2365e67a4a87a6cd11ee6cdbdf9cf5" [[package]] -name = "percent-encoding" -version = "2.3.2" +name = "protoc-bin-vendored-linux-x86_64" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +checksum = "b38b07546580df720fa464ce124c4b03630a6fb83e05c336fea2a241df7e5d78" [[package]] -name = "phf" -version = "0.13.1" +name = "protoc-bin-vendored-macos-aarch_64" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" -dependencies = [ - "phf_macros", - "phf_shared", - "serde", -] +checksum = "89278a9926ce312e51f1d999fee8825d324d603213344a9a706daa009f1d8092" [[package]] -name = "phf_codegen" -version = "0.13.1" +name = "protoc-bin-vendored-macos-x86_64" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" -dependencies = [ - "phf_generator", - "phf_shared", -] +checksum = "81745feda7ccfb9471d7a4de888f0652e806d5795b61480605d4943176299756" [[package]] -name = "phf_generator" -version = "0.13.1" +name = "protoc-bin-vendored-win32" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +checksum = "95067976aca6421a523e491fce939a3e65249bac4b977adee0ee9771568e8aa3" + +[[package]] +name = "quick-xml" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b" dependencies = [ - "fastrand", - "phf_shared", + "memchr", ] [[package]] -name = "phf_macros" -version = "0.13.1" +name = "quinn" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11" dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2", - "quote", - "syn 2.0.119", + "bytes", + "cfg_aliases 0.2.2", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.20", + "tokio", + "tracing", + "web-time", ] [[package]] -name = "phf_shared" -version = "0.13.1" +name = "quinn-proto" +version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +checksum = "a9746dbde176634f4f2f1faf2404e30a31b2bc1e9cafb5329c95d8177a18c9fc" dependencies = [ - "siphasher", + "aws-lc-rs", + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand 0.10.3", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.20", + "tinyvec", + "tracing", + "web-time", ] [[package]] -name = "pin-project-lite" -version = "0.2.17" +name = "quinn-udp" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases 0.2.2", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] [[package]] -name = "piper" -version = "0.2.5" +name = "quote" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", + "proc-macro2", ] [[package]] -name = "pkg-config" -version = "0.3.34" +name = "r-efi" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] -name = "plist" -version = "1.10.1" +name = "r-efi" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896bade328c13f7042a297ea5ac5b0951f6cf989dea5f32c2fd98da398195cb" -dependencies = [ - "base64 0.23.1", - "indexmap 2.14.2", - "quick-xml", - "serde", - "time", -] +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] -name = "png" -version = "0.17.16" +name = "rand" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide 0.8.9", + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", ] [[package]] -name = "png" -version = "0.18.1" +name = "rand" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ - "bitflags 2.13.1", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide 0.8.9", + "rand_chacha 0.9.0", + "rand_core 0.9.5", ] [[package]] -name = "polling" -version = "3.11.0" +name = "rand" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix", - "windows-sys 0.61.2", + "chacha20 0.10.2", + "getrandom 0.4.3", + "rand_core 0.10.1", ] [[package]] -name = "portable-atomic" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" - -[[package]] -name = "portable-atomic-util" -version = "0.2.8" +name = "rand_chacha" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ - "portable-atomic", + "ppv-lite86", + "rand_core 0.6.4", ] [[package]] -name = "portable-pty" +name = "rand_chacha" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4a596a2b3d2752d94f51fac2d4a96737b8705dddd311a32b9af47211f08671e" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ - "anyhow", - "bitflags 1.3.2", - "downcast-rs", - "filedescriptor", - "lazy_static", - "libc", - "log", - "nix", - "serial2", - "shared_library", - "shell-words", - "winapi", - "winreg 0.10.1", + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] -name = "potential_utf" -version = "0.1.6" +name = "rand_core" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "zerovec", + "getrandom 0.2.17", ] [[package]] -name = "powerfmt" -version = "0.2.0" +name = "rand_core" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] [[package]] -name = "precomputed-hash" -version = "0.1.1" +name = "rand_core" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] -name = "proc-macro-crate" -version = "1.3.1" +name = "rand_pcg" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" dependencies = [ - "once_cell", - "toml_edit 0.19.15", + "rand_core 0.10.1", ] [[package]] -name = "proc-macro-crate" -version = "2.0.2" +name = "rand_xorshift" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ - "toml_datetime 0.6.3", - "toml_edit 0.20.2", + "rand_core 0.9.5", ] [[package]] -name = "proc-macro-crate" -version = "3.5.0" +name = "ratatui" +version = "0.30.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +checksum = "3274ba0a2c5e1bcad2a2005d20f4dc59dad26b2eb0940fb094500dba4099d57d" dependencies = [ - "toml_edit 0.25.13+spec-1.1.0", + "instability", + "ratatui-core", + "ratatui-crossterm", + "ratatui-macros", + "ratatui-termina", + "ratatui-termwiz", + "ratatui-widgets", + "serde", ] [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "ratatui-core" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "cbb175c433c8e28a809d1f5773a2ae96e68c0ce40db865cbab1020bf33ae479c" dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", + "bitflags 2.13.1", + "compact_str", + "critical-section", + "hashbrown 0.17.1", + "itertools", + "kasuari", + "lru", + "palette", + "serde", + "strum", + "thiserror 2.0.20", + "unicode-segmentation", + "unicode-truncate", + "unicode-width", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "ratatui-crossterm" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "567584a3b0e6a8203c23de40b4861497266725eb5363dbfd18a1edd603cca9f0" dependencies = [ - "proc-macro2", - "quote", - "version_check", + "cfg-if", + "crossterm 0.29.0", + "instability", + "ratatui-core", ] [[package]] -name = "proc-macro2" -version = "1.0.107" +name = "ratatui-macros" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +checksum = "ed7dc68daa7498a43e4d68e0eb078427e10c38fbcfbb1e42d955f1fa2140d814" dependencies = [ - "unicode-ident", + "ratatui-core", + "ratatui-widgets", ] [[package]] -name = "quick-xml" -version = "0.42.0" +name = "ratatui-termina" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b" +checksum = "c0bf912d9e66f057a759d92e386a280ea886b352ab757d6ac4d653c7ed2c43c2" dependencies = [ - "memchr", + "instability", + "ratatui-core", + "termina", ] [[package]] -name = "quote" -version = "1.0.47" +name = "ratatui-termwiz" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +checksum = "faf03e0380b7744054d6cb74224fe3adf062a029754933f575ca1e3b4c2ce977" dependencies = [ - "proc-macro2", + "ratatui-core", + "termwiz", ] [[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "r-efi" -version = "6.0.0" +name = "ratatui-widgets" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +checksum = "66e3d19bcc9130ca376277d93b60767ff121ace3be06f5f95f81dd68956407d1" +dependencies = [ + "bitflags 2.13.1", + "hashbrown 0.17.1", + "indoc", + "instability", + "itertools", + "line-clipping", + "ratatui-core", + "serde", + "strum", + "time", + "unicode-segmentation", + "unicode-width", +] [[package]] name = "raw-window-handle" @@ -2903,6 +7183,15 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "redb" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba239c1c1693315d3cc0e601db3b3965543afbf48c41730fdca2f069f510f4a" +dependencies = [ + "libc", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -2966,12 +7255,61 @@ dependencies = [ "regex-syntax", ] -[[package]] -name = "regex-syntax" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" - +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams 0.4.2", + "web-sys", +] + [[package]] name = "reqwest" version = "0.13.4" @@ -2980,21 +7318,33 @@ checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64 0.22.1", "bytes", + "encoding_rs", + "futures-channel", "futures-core", "futures-util", + "h2", "http", "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-util", "js-sys", "log", + "mime", + "mime_guess", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", "serde", "serde_json", + "serde_urlencoded", "sync_wrapper", "tokio", + "tokio-rustls", "tokio-util", "tower", "tower-http", @@ -3002,10 +7352,24 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", + "wasm-streams 0.5.0", "web-sys", ] +[[package]] +name = "reqwest-middleware" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc3f1384cffa4f274dad2d4ddd73aed32fed8f786d96c6be8aa4e5fd3c3b58" +dependencies = [ + "anyhow", + "async-trait", + "http", + "reqwest 0.13.4", + "thiserror 2.0.20", + "tower-service", +] + [[package]] name = "rfd" version = "0.16.0" @@ -3030,6 +7394,101 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rmcp" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1f571c72940a19d9532fe52dbea8bc9912bf1d766c2970bb824056b86f3f59" +dependencies = [ + "async-trait", + "base64 0.22.1", + "bytes", + "chrono", + "futures", + "http", + "http-body", + "http-body-util", + "pastey", + "pin-project-lite", + "process-wrap", + "rand 0.10.3", + "reqwest 0.13.4", + "rmcp-macros", + "schemars 1.2.2", + "serde", + "serde_json", + "sse-stream", + "thiserror 2.0.20", + "tokio", + "tokio-stream", + "tokio-util", + "tower-service", + "tracing", + "uuid", +] + +[[package]] +name = "rmcp-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aad0035b69380782d78ea95b508327e6deaa2235909053e596eea8f27b5e1d5" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "serde_json", + "syn 2.0.119", +] + +[[package]] +name = "rpassword" +version = "7.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da316a15f47e3d053de9cb2c439650bd8fa4aaeb9365f2e5f27f492ff73c196" +dependencies = [ + "libc", + "rtoolbox", + "windows-sys 0.61.2", +] + +[[package]] +name = "rtoolbox" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a1efe12a1469752d0e6ff5ebec0b6ef4924cc5c4c71046b0ec730040535819d" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "rusqlite" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" +dependencies = [ + "bitflags 2.13.1", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + [[package]] name = "rustc-hash" version = "2.1.3" @@ -3045,6 +7504,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.13.1", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + [[package]] name = "rustix" version = "1.1.4" @@ -3054,7 +7526,7 @@ dependencies = [ "bitflags 2.13.1", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.12.1", "windows-sys 0.61.2", ] @@ -3065,7 +7537,84 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fc84bf7e9aa16c4f2c758f27412dc9841341e16aa682d9c7ac308fe3ee12056" dependencies = [ "once_cell", - "rustix", + "rustix 1.1.4", +] + +[[package]] +name = "rustls" +version = "0.23.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.7.0", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni 0.22.4", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework 3.7.0", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] @@ -3074,6 +7623,27 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "safe-transmute" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3944826ff8fa8093089aba3acb4ef44b9446a99a16f3bf4e74af3f77d340ab7d" + +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + [[package]] name = "same-file" version = "1.0.6" @@ -3083,6 +7653,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "schemars" version = "0.8.22" @@ -3091,54 +7670,178 @@ checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" dependencies = [ "dyn-clone", "indexmap 1.9.3", - "schemars_derive", + "schemars_derive 0.8.22", + "serde", + "serde_json", + "url", + "uuid", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +dependencies = [ + "chrono", + "dyn-clone", + "ref-cast", + "schemars_derive 1.2.2", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals 0.29.1", + "syn 2.0.119", +] + +[[package]] +name = "schemars_derive" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98c67716b46af2f0b8cf752abc930f6f9aecfbf671ecfb531db8a31dbe4e2ba" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals 0.30.0", + "syn 3.0.5", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "password-hash", + "pbkdf2", + "salsa20", + "sha2 0.10.9", +] + +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "rand 0.8.8", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" +dependencies = [ + "bitcoin_hashes 0.14.101", + "rand 0.8.8", + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "secret-service" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4d35ad99a181be0a60ffcbe85d680d98f87bdc4d7644ade319b87076b9dbfd4" +dependencies = [ + "aes", + "cbc", + "futures-util", + "generic-array", + "hkdf", + "num", + "once_cell", + "rand 0.8.8", "serde", - "serde_json", - "url", - "uuid", + "sha2 0.10.9", + "zbus 4.4.0", ] [[package]] -name = "schemars" -version = "0.9.0" +name = "security-framework" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", + "bitflags 2.13.1", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", ] [[package]] -name = "schemars" -version = "1.2.2" +name = "security-framework" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", + "bitflags 2.13.1", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", ] [[package]] -name = "schemars_derive" -version = "0.8.22" +name = "security-framework-sys" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 2.0.119", + "core-foundation-sys", + "libc", ] [[package]] -name = "scopeguard" -version = "1.2.0" +name = "seize" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "5b55fb86dfd3a2f5f76ea78310a88f96c4ea21a3031f8d212443d56123fd0521" [[package]] name = "selectors" @@ -3151,8 +7854,8 @@ dependencies = [ "derive_more", "log", "new_debug_unreachable", - "phf", - "phf_codegen", + "phf 0.13.1", + "phf_codegen 0.13.1", "precomputed-hash", "rustc-hash", "servo_arc", @@ -3169,6 +7872,12 @@ dependencies = [ "serde_core", ] +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + [[package]] name = "serde" version = "1.0.229" @@ -3191,6 +7900,16 @@ dependencies = [ "typeid", ] +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_core" version = "1.0.229" @@ -3222,6 +7941,17 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "serde_derive_internals" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + [[package]] name = "serde_json" version = "1.0.151" @@ -3235,6 +7965,17 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + [[package]] name = "serde_repr" version = "0.1.21" @@ -3264,6 +8005,18 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_with" version = "3.22.0" @@ -3291,12 +8044,22 @@ version = "3.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8705578779c2b6bd90d84d66eb2e206b708b1a4d7b9f17641b293545bf1c7e46" dependencies = [ - "darling", + "darling 0.23.0", "proc-macro2", "quote", "syn 2.0.119", ] +[[package]] +name = "serdect" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" +dependencies = [ + "base16ct", + "serde", +] + [[package]] name = "serial2" version = "0.2.38" @@ -3339,6 +8102,23 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "sha1" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + [[package]] name = "sha2" version = "0.10.9" @@ -3346,8 +8126,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest 0.11.3", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", ] [[package]] @@ -3366,12 +8166,44 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" +[[package]] +name = "shellexpand" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" +dependencies = [ + "bstr", + "dirs", + "os_str_bytes", +] + [[package]] name = "shlex" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.8" @@ -3382,18 +8214,167 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" + [[package]] name = "simd-adler32" version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "simple-dns" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b6f884fa9a8d48101774bfbd3aeb81e968dd22cffd19a372da69f183db22c1a" +dependencies = [ + "bitflags 2.13.1", +] + [[package]] name = "siphasher" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" +[[package]] +name = "skippy-cache" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "anyhow", + "blake3", + "skippy-protocol", +] + +[[package]] +name = "skippy-coordinator" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "thiserror 2.0.20", +] + +[[package]] +name = "skippy-ffi" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "libloading 0.8.9", +] + +[[package]] +name = "skippy-metrics" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" + +[[package]] +name = "skippy-protocol" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "prost", + "prost-build", + "protoc-bin-vendored", + "serde", + "skippy-tokenizer", +] + +[[package]] +name = "skippy-runtime" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "anyhow", + "libc", + "serde", + "serde_json", + "sha2 0.10.9", + "skippy-ffi", + "tokio", +] + +[[package]] +name = "skippy-scheduler" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "skippy-runtime", + "thiserror 2.0.20", +] + +[[package]] +name = "skippy-server" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "ahash", + "anyhow", + "async-trait", + "axum", + "base64 0.22.1", + "blake3", + "clap", + "futures-util", + "libc", + "mesh-llm-events", + "mesh-native-serving-plugin-api", + "model-artifact", + "openai-frontend", + "opentelemetry-proto", + "serde", + "serde_json", + "sha2 0.10.9", + "skippy-cache", + "skippy-metrics", + "skippy-protocol", + "skippy-runtime", + "skippy-scheduler", + "skippy-tokenizer", + "skippy-topology", + "socket2", + "tokio", + "tonic", + "uuid", +] + +[[package]] +name = "skippy-tokenizer" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "serde", +] + +[[package]] +name = "skippy-topology" +version = "0.76.0-rc9" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.76.0-rc9#9f192c9d821991ba43d79bb15fd2f2f12e597571" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "slab" version = "0.4.12" @@ -3406,6 +8387,17 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f" +[[package]] +name = "socket-pktinfo" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927136cc2ae6a1b0e66ac6b1210902b75c3f726db004a73bc18686dcd0dcd22f" +dependencies = [ + "libc", + "socket2", + "windows-sys 0.60.2", +] + [[package]] name = "socket2" version = "0.6.5" @@ -3438,6 +8430,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "sorted-index-buffer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea06cc588e43c632923a55450401b8f25e628131571d4e1baea1bdfdb2b5ed06" + [[package]] name = "soup3" version = "0.5.0" @@ -3464,12 +8462,77 @@ dependencies = [ "system-deps", ] +[[package]] +name = "spez" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87e960f4dca2788eeb86bbdde8dd246be8948790b7618d656e68f9b720a86e8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spin" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3" + +[[package]] +name = "spki" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sse-stream" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c25ac7aff0abd1dbc474536e40416e1102c7dd9bfba0b9861c6d357f835dcfb4" +dependencies = [ + "bytes", + "futures-util", + "http-body", + "http-body-util", + "pin-project-lite", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "statrs" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a3fe7c28c6512e766b0874335db33c94ad7b8f9054228ae1c2abd47ce7d335e" +dependencies = [ + "approx", + "num-traits", +] + [[package]] name = "string_cache" version = "0.9.0" @@ -3478,7 +8541,7 @@ checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" dependencies = [ "new_debug_unreachable", "parking_lot", - "phf_shared", + "phf_shared 0.13.1", "precomputed-hash", ] @@ -3488,8 +8551,8 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.13.1", + "phf_shared 0.13.1", "proc-macro2", "quote", ] @@ -3500,6 +8563,33 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "swift-rs" version = "1.0.8" @@ -3511,6 +8601,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + [[package]] name = "syn" version = "1.0.109" @@ -3518,6 +8614,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", + "quote", "unicode-ident", ] @@ -3563,6 +8660,52 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "sysinfo" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows 0.62.2", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501336eb7ba9e417300a6a0fa985721065467aa83a6dcf0422a8e43e4c0328fa" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.10.1", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "system-deps" version = "6.2.2" @@ -3576,6 +8719,15 @@ dependencies = [ "version-compare", ] +[[package]] +name = "tabwriter" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce91f2f0ec87dff7e6bcbbeb267439aa1188703003c6055193c821487400432" +dependencies = [ + "unicode-width", +] + [[package]] name = "tao" version = "0.35.3" @@ -3584,7 +8736,7 @@ checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9" dependencies = [ "bitflags 2.13.1", "block2", - "core-foundation", + "core-foundation 0.10.1", "core-graphics", "crossbeam-channel", "dbus", @@ -3594,7 +8746,7 @@ dependencies = [ "gdkwayland-sys", "gdkx11-sys", "gtk", - "jni", + "jni 0.21.1", "libc", "log", "ndk", @@ -3627,6 +8779,17 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "tar" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "target-lexicon" version = "0.12.16" @@ -3650,7 +8813,7 @@ dependencies = [ "gtk", "heck 0.5.0", "http", - "jni", + "jni 0.21.1", "libc", "log", "mime", @@ -3663,7 +8826,7 @@ dependencies = [ "percent-encoding", "plist", "raw-window-handle", - "reqwest", + "reqwest 0.13.4", "serde", "serde_json", "serde_repr", @@ -3722,7 +8885,7 @@ dependencies = [ "semver", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "syn 2.0.119", "tauri-utils", "thiserror 2.0.20", @@ -3823,7 +8986,7 @@ dependencies = [ "thiserror 2.0.20", "url", "windows 0.61.3", - "zbus", + "zbus 5.19.0", ] [[package]] @@ -3836,7 +8999,7 @@ dependencies = [ "dpi", "gtk", "http", - "jni", + "jni 0.21.1", "objc2", "objc2-ui-kit", "objc2-web-kit", @@ -3859,7 +9022,7 @@ checksum = "4e6fac707727b7a2f48e4ded90976324267371073edbb415ffb73bb0458d203f" dependencies = [ "gtk", "http", - "jni", + "jni 0.21.1", "log", "objc2", "objc2-app-kit", @@ -3886,7 +9049,7 @@ dependencies = [ "anyhow", "brotli", "cargo_metadata", - "ctor", + "ctor 0.8.0", "dom_query", "dunce", "glob", @@ -3895,7 +9058,7 @@ dependencies = [ "json-patch", "log", "memchr", - "phf", + "phf 0.13.1", "plist", "proc-macro2", "quote", @@ -3946,7 +9109,7 @@ dependencies = [ "fastrand", "getrandom 0.4.3", "once_cell", - "rustix", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -3959,6 +9122,82 @@ dependencies = [ "new_debug_unreachable", ] +[[package]] +name = "termina" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9048a889effe34a5cddee0af7f53285198b16dca3be510858d38dfdb3e62a04e" +dependencies = [ + "bitflags 2.13.1", + "parking_lot", + "rustix 1.1.4", + "signal-hook", + "windows-sys 0.61.2", +] + +[[package]] +name = "terminfo" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662" +dependencies = [ + "fnv", + "nom", + "phf 0.11.3", + "phf_codegen 0.11.3", +] + +[[package]] +name = "termios" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" +dependencies = [ + "libc", +] + +[[package]] +name = "termwiz" +version = "0.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" +dependencies = [ + "anyhow", + "base64 0.22.1", + "bitflags 2.13.1", + "fancy-regex", + "filedescriptor", + "finl_unicode", + "fixedbitset 0.4.2", + "hex", + "lazy_static", + "libc", + "log", + "memmem", + "nix 0.29.0", + "num-derive", + "num-traits", + "ordered-float", + "pest", + "pest_derive", + "phf 0.11.3", + "sha2 0.10.9", + "signal-hook", + "siphasher", + "terminfo", + "termios", + "thiserror 1.0.69", + "ucd-trie", + "unicode-segmentation", + "vtparse", + "wezterm-bidi", + "wezterm-blob-leases", + "wezterm-color-types", + "wezterm-dynamic", + "wezterm-input-types", + "winapi", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -3999,6 +9238,15 @@ dependencies = [ "syn 3.0.5", ] +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + [[package]] name = "time" version = "0.3.55" @@ -4006,7 +9254,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", + "js-sys", + "libc", "num-conv", + "num_threads", "powerfmt", "serde_core", "time-core", @@ -4063,11 +9314,105 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", + "tokio-macros", "windows-sys 0.61.2", ] +[[package]] +name = "tokio-happy-eyeballs" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8564c32dfb6f4257f8bc6edfc178a34af97520e0b7b9815500c55eb3d092f29f" +dependencies = [ + "tokio", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-retry" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a129d95275ebf4c493ec53bf0f8cd95f5ac161bc4f381700809a54f595d4470" +dependencies = [ + "pin-project-lite", + "rand 0.10.3", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c85f2c3ef0b1cd58b36682f4b17aaa995f0e5db534d85692b4903abce21f67" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-socks" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7e2948f60dbe26b35f2c7fb74ac2854c1fddded0fe9d7548fcc674a246f7615" +dependencies = [ + "either", + "futures-util", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite", + "webpki-roots 0.26.11", +] + [[package]] name = "tokio-util" version = "0.7.19" @@ -4077,10 +9422,60 @@ dependencies = [ "bytes", "futures-core", "futures-sink", + "futures-util", + "libc", "pin-project-lite", "tokio", ] +[[package]] +name = "tokio-websockets" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52efb639344a7c6adb8e62c6f3d2c19c001ff1b79a5041ba1c6ed42e19c6aa5" +dependencies = [ + "aws-lc-rs", + "base64 0.22.1", + "bytes", + "futures-core", + "futures-sink", + "getrandom 0.4.3", + "http", + "httparse", + "rand 0.10.3", + "ring", + "rustls-pki-types", + "sha1_smol", + "simdutf8", + "tokio", + "tokio-rustls", + "tokio-util", +] + +[[package]] +name = "tokio_with_wasm" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34e40fbbbd95441133fe9483f522db15dbfd26dc636164ebd8f2dd28759a6aa6" +dependencies = [ + "js-sys", + "tokio", + "tokio_with_wasm_proc", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "tokio_with_wasm_proc" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d01145a2c788d6aae4cd653afec1e8332534d7d783d01897cefcafe4428de992" +dependencies = [ + "quote", + "syn 2.0.119", +] + [[package]] name = "toml" version = "0.8.2" @@ -4183,24 +9578,65 @@ dependencies = [ "indexmap 2.14.2", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", + "toml_writer", + "winnow 1.0.4", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ "winnow 1.0.4", ] [[package]] -name = "toml_parser" -version = "1.1.3+spec-1.1.0" +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + +[[package]] +name = "tonic" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" +dependencies = [ + "async-trait", + "axum", + "base64 0.22.1", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "socket2", + "sync_wrapper", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-prost" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" dependencies = [ - "winnow 1.0.4", + "bytes", + "prost", + "tonic", ] -[[package]] -name = "toml_writer" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" - [[package]] name = "tower" version = "0.5.3" @@ -4209,11 +9645,15 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", + "indexmap 2.14.2", "pin-project-lite", + "slab", "sync_wrapper", "tokio", + "tokio-util", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -4252,11 +9692,25 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" +dependencies = [ + "crossbeam-channel", + "symlink", + "thiserror 2.0.20", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" version = "0.1.31" @@ -4275,6 +9729,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", ] [[package]] @@ -4305,6 +9802,31 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.5", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror 2.0.20", + "utf-8", +] + +[[package]] +name = "twox-hash" +version = "2.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5283634e518fe9e82c7b20520bb4bc209009fd16c82077c802f8111ecbb0117a" + [[package]] name = "typeid" version = "1.0.3" @@ -4317,6 +9839,18 @@ version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" +[[package]] +name = "typewit" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "214ca0b2191785cbc06209b9ca1861e048e39b5ba33574b3cedd58363d5bb5f6" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + [[package]] name = "uds_windows" version = "1.2.1" @@ -4328,6 +9862,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unic-char-property" version = "0.9.0" @@ -4369,18 +9909,78 @@ dependencies = [ "unic-common", ] +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-segmentation" version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" +[[package]] +name = "unicode-truncate" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5" +dependencies = [ + "itertools", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "universal-time" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47a939edecc3c5a7b83c02e5f6b3c31d2bc69eabcc9a87ab12c6d37ee6dbc856" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.8" @@ -4394,6 +9994,12 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "urlpattern" version = "0.3.0" @@ -4406,24 +10012,49 @@ dependencies = [ "url", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" dependencies = [ + "atomic", "getrandom 0.4.3", "js-sys", "serde_core", "wasm-bindgen", ] +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version-compare" version = "0.2.1" @@ -4456,6 +10087,15 @@ dependencies = [ "libc", ] +[[package]] +name = "vtparse" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0" +dependencies = [ + "utf8parse", +] + [[package]] name = "walkdir" version = "2.5.0" @@ -4481,6 +10121,15 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasi" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + [[package]] name = "wasip2" version = "1.0.4+wasi-0.2.12" @@ -4490,6 +10139,15 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasite" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fe902b4a6b8028a753d5424909b764ccf79b7a209eac9bf97e59cda9f71a42" +dependencies = [ + "wasi 0.14.7+wasi-0.2.4", +] + [[package]] name = "wasm-bindgen" version = "0.2.128" @@ -4545,6 +10203,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasm-streams" version = "0.5.0" @@ -4568,14 +10239,24 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "web_atoms" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba8b815c1b593dc0baf78dd0f4fc8fdb2de53198fb1163738093e9a311c33fb3" dependencies = [ - "phf", - "phf_codegen", + "phf 0.13.1", + "phf_codegen 0.13.1", "string_cache", "string_cache_codegen", ] @@ -4624,6 +10305,33 @@ dependencies = [ "system-deps", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.9", +] + +[[package]] +name = "webpki-roots" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webview2-com" version = "0.38.2" @@ -4660,6 +10368,97 @@ dependencies = [ "windows-core 0.61.2", ] +[[package]] +name = "wezterm-bidi" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0a6e355560527dd2d1cf7890652f4f09bb3433b6aadade4c9b5ed76de5f3ec" +dependencies = [ + "log", + "wezterm-dynamic", +] + +[[package]] +name = "wezterm-blob-leases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7" +dependencies = [ + "getrandom 0.3.4", + "mac_address", + "sha2 0.10.9", + "thiserror 1.0.69", + "uuid", +] + +[[package]] +name = "wezterm-color-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296" +dependencies = [ + "csscolorparser", + "deltae", + "lazy_static", + "wezterm-dynamic", +] + +[[package]] +name = "wezterm-dynamic" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f2ab60e120fd6eaa68d9567f3226e876684639d22a4219b313ff69ec0ccd5ac" +dependencies = [ + "log", + "ordered-float", + "strsim", + "thiserror 1.0.69", + "wezterm-dynamic-derive", +] + +[[package]] +name = "wezterm-dynamic-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c0cf2d539c645b448eaffec9ec494b8b19bd5077d9e58cb1ae7efece8d575b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "wezterm-input-types" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7012add459f951456ec9d6c7e6fc340b1ce15d6fc9629f8c42853412c029e57e" +dependencies = [ + "bitflags 1.3.2", + "euclid", + "lazy_static", + "serde", + "wezterm-dynamic", +] + +[[package]] +name = "whoami" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626c4bac6755d76ffc12cb01b2eac751db1996b9e0041de9aa02c8c211ddc82c" +dependencies = [ + "libc", + "libredox", + "objc2-system-configuration", + "wasite", + "web-sys", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + [[package]] name = "winapi" version = "0.3.9" @@ -4851,6 +10650,17 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -4896,6 +10706,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -5195,6 +11014,21 @@ version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" +[[package]] +name = "wmi" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c81b85c57a57500e56669586496bf2abd5cf082b9d32995251185d105208b64" +dependencies = [ + "chrono", + "futures", + "log", + "serde", + "thiserror 2.0.20", + "windows 0.61.3", + "windows-core 0.61.2", +] + [[package]] name = "writeable" version = "0.6.4" @@ -5219,7 +11053,7 @@ dependencies = [ "gtk", "http", "javascriptcore-rs", - "jni", + "jni 0.21.1", "libc", "ndk", "objc2", @@ -5231,7 +11065,7 @@ dependencies = [ "once_cell", "percent-encoding", "raw-window-handle", - "sha2", + "sha2 0.10.9", "soup3", "tao-macros", "thiserror 2.0.20", @@ -5245,6 +11079,25 @@ dependencies = [ "x11-dl", ] +[[package]] +name = "ws_stream_wasm" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c173014acad22e83f16403ee360115b38846fe754e735c5d9d3803fe70c6abc" +dependencies = [ + "async_io_stream", + "futures", + "js-sys", + "log", + "pharos", + "rustc_version", + "send_wrapper", + "thiserror 2.0.20", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "x11" version = "2.21.0" @@ -5266,6 +11119,182 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix 1.1.4", +] + +[[package]] +name = "xdg-home" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "xet-client" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b8da8cc70aa2e3c500c0400e012df82c656ab9fca47f9f939fffc5afd89aca" +dependencies = [ + "anyhow", + "async-trait", + "base64 0.22.1", + "bytes", + "crc32fast", + "futures", + "http", + "hyper", + "more-asserts", + "rand 0.10.3", + "redb", + "reqwest 0.13.4", + "reqwest-middleware", + "serde", + "serde_json", + "serde_repr", + "statrs", + "tempfile", + "thiserror 2.0.20", + "tokio", + "tokio-retry", + "tokio_with_wasm", + "tracing", + "url", + "urlencoding", + "web-time", + "xet-core-structures", + "xet-runtime", +] + +[[package]] +name = "xet-core-structures" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73503c223783dccc864abde22115e09d12f190448a0baf58ab2c54bc709e2f99" +dependencies = [ + "async-trait", + "base64 0.22.1", + "blake3", + "bytemuck", + "bytes", + "countio", + "futures", + "futures-util", + "getrandom 0.4.3", + "heapify", + "itertools", + "lz4_flex", + "more-asserts", + "rand 0.10.3", + "regex", + "safe-transmute", + "serde", + "static_assertions", + "thiserror 2.0.20", + "tokio", + "tokio-util", + "tracing", + "uuid", + "web-time", + "xet-runtime", +] + +[[package]] +name = "xet-data" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89052ec5dec2187cad30b86af92cc24fd61c4a57a795f1ff7ff5f38d49184eb" +dependencies = [ + "anyhow", + "async-trait", + "bytes", + "chrono", + "gearhash", + "http", + "itertools", + "more-asserts", + "rand 0.10.3", + "serde", + "serde_json", + "sha2 0.11.0", + "tempfile", + "thiserror 2.0.20", + "tokio", + "tokio-util", + "tokio_with_wasm", + "tracing", + "url", + "uuid", + "web-time", + "xet-client", + "xet-core-structures", + "xet-runtime", +] + +[[package]] +name = "xet-runtime" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af5c60d5eed38ab4c576f4421bae835e7bd07631fb381705605529d2015c106b" +dependencies = [ + "anyhow", + "async-trait", + "bytes", + "chrono", + "colored", + "const-str", + "ctor 1.0.13", + "dirs", + "futures", + "git-version", + "humantime", + "konst", + "libc", + "more-asserts", + "oneshot", + "pin-project", + "rand 0.10.3", + "reqwest 0.13.4", + "serde", + "serde_json", + "shellexpand", + "sysinfo", + "thiserror 2.0.20", + "tokio", + "tokio-util", + "tokio_with_wasm", + "tracing", + "tracing-appender", + "tracing-subscriber", + "web-time", + "whoami", + "winapi", +] + +[[package]] +name = "xml-rs" +version = "0.8.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7" + +[[package]] +name = "xmltree" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb" +dependencies = [ + "xml-rs", +] + [[package]] name = "yoke" version = "0.8.3" @@ -5289,6 +11318,38 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zbus" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" +dependencies = [ + "async-broadcast", + "async-process", + "async-recursion", + "async-trait", + "enumflags2", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix 0.29.0", + "ordered-stream", + "rand 0.8.8", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "windows-sys 0.52.0", + "xdg-home", + "zbus_macros 4.4.0", + "zbus_names 3.0.0", + "zvariant 4.2.0", +] + [[package]] name = "zbus" version = "5.19.0" @@ -5311,7 +11372,7 @@ dependencies = [ "hex", "libc", "ordered-stream", - "rustix", + "rustix 1.1.4", "serde", "serde_repr", "tracing", @@ -5319,9 +11380,22 @@ dependencies = [ "uuid", "windows-sys 0.61.2", "winnow 1.0.4", - "zbus_macros", - "zbus_names", - "zvariant", + "zbus_macros 5.19.0", + "zbus_names 4.3.4", + "zvariant 5.15.0", +] + +[[package]] +name = "zbus_macros" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", + "zvariant_utils 2.1.0", ] [[package]] @@ -5334,9 +11408,20 @@ dependencies = [ "proc-macro2", "quote", "syn 3.0.5", - "zbus_names", - "zvariant", - "zvariant_utils", + "zbus_names 4.3.4", + "zvariant 5.15.0", + "zvariant_utils 4.2.0", +] + +[[package]] +name = "zbus_names" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" +dependencies = [ + "serde", + "static_assertions", + "zvariant 4.2.0", ] [[package]] @@ -5347,7 +11432,7 @@ checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" dependencies = [ "serde", "winnow 1.0.4", - "zvariant", + "zvariant 5.15.0", ] [[package]] @@ -5359,6 +11444,26 @@ dependencies = [ "serde", ] +[[package]] +name = "zerocopy" +version = "0.8.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "zerofrom" version = "0.1.8" @@ -5380,6 +11485,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "zerotrie" version = "0.2.5" @@ -5413,6 +11538,23 @@ dependencies = [ "syn 3.0.5", ] +[[package]] +name = "zip" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" +dependencies = [ + "arbitrary", + "crc32fast", + "crossbeam-utils", + "displaydoc", + "flate2", + "indexmap 2.14.2", + "memchr", + "thiserror 2.0.20", + "zopfli", +] + [[package]] name = "zlib-rs" version = "0.6.7" @@ -5425,6 +11567,31 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zvariant" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "zvariant_derive 4.2.0", +] + [[package]] name = "zvariant" version = "5.15.0" @@ -5436,8 +11603,21 @@ dependencies = [ "serde", "winnow 1.0.4", "zcheapstr", - "zvariant_derive", - "zvariant_utils", + "zvariant_derive 5.15.0", + "zvariant_utils 4.2.0", +] + +[[package]] +name = "zvariant_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", + "zvariant_utils 2.1.0", ] [[package]] @@ -5450,7 +11630,18 @@ dependencies = [ "proc-macro2", "quote", "syn 3.0.5", - "zvariant_utils", + "zvariant_utils 4.2.0", +] + +[[package]] +name = "zvariant_utils" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 2cadef9f7..489cde4c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,4 @@ [workspace] -members = ["crates/plugin-manager", "src-tauri"] +members = ["crates/plugin-manager", "crates/community-compute", "src-tauri"] default-members = ["crates/plugin-manager"] resolver = "2" diff --git a/crates/community-compute/Cargo.toml b/crates/community-compute/Cargo.toml new file mode 100644 index 000000000..c9dc77845 --- /dev/null +++ b/crates/community-compute/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "buzz-community-compute" +version = "0.0.0" +edition = "2021" +publish = false + +[dependencies] +anyhow = "1" +base64 = "0.22" +ed25519-dalek = "=3.0.0-rc.0" +hex = "0.4" +iroh = "1.0.3" +mesh-llm-host-runtime = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.76.0-rc9", default-features = false, features = ["dynamic-native-runtime"] } +nostr = "0.44" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +sha2 = "0.11" +url = "2" + +mesh-llm-sdk = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.76.0-rc9", default-features = false, features = ["serving"] } +mesh-llm-client = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.76.0-rc9", default-features = false } +mesh-llm-node = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.76.0-rc9", default-features = false } +mesh-llm-system = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.76.0-rc9", default-features = false } +mesh-llm-events = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.76.0-rc9", default-features = false } +tokio = { version = "1", features = ["rt-multi-thread", "time", "macros", "io-util", "io-std", "sync"] } +reqwest = { version = "0.13", default-features = false, features = ["json"] } diff --git a/crates/community-compute/src/broker.rs b/crates/community-compute/src/broker.rs new file mode 100644 index 000000000..e9901f0cd --- /dev/null +++ b/crates/community-compute/src/broker.rs @@ -0,0 +1,379 @@ +//! Worker-owned authenticated reads. Destination is the native app's configured +//! loopback development server, never an arbitrary URL supplied by a plugin. +use crate::{discovery, worker::StartRequest}; +use anyhow::{bail, Context}; +use nostr::Event; +use serde::Deserialize; +use serde_json::{json, Value}; +use std::{collections::BTreeSet, time::Duration}; + +#[derive(Clone)] +pub(crate) struct Broker { + client: reqwest::Client, + origin: String, + endpoint: String, + pub viewer: String, + pub authority: String, + pub relay: String, +} +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct Session { + viewer: String, + archive_authority: Option, + relay_url: String, + compute_status: bool, +} +impl Broker { + pub async fn connect(origin: &str, request: &StartRequest) -> anyhow::Result { + let url = url::Url::parse(origin)?; + if url.scheme() != "http" + || !matches!(url.host_str(), Some("localhost" | "127.0.0.1")) + || url.port().is_none() + || url.path() != "/" + || !url.username().is_empty() + || url.password().is_some() + || url.query().is_some() + || url.fragment().is_some() + { + bail!("Compute requires the configured local development broker"); + } + let client = reqwest::Client::builder() + .no_proxy() + .redirect(reqwest::redirect::Policy::none()) + .timeout(Duration::from_secs(10)) + .build()?; + let origin = url.origin().ascii_serialization(); + let encoded: String = + url::form_urlencoded::byte_serialize(request.community.as_bytes()).collect(); + let mut broker = Self { + client, + endpoint: format!("{origin}/api/relay/{encoded}"), + origin, + viewer: request.viewer.clone(), + authority: String::new(), + relay: String::new(), + }; + // Restoring a saved explicit opt-in also restores its exact community registration. + broker + .post_url( + &format!("{}/api/relay/register", broker.origin), + &json!({"url":request.community}), + ) + .await?; + let session = broker.session().await?; + if session.viewer != request.viewer || !session.compute_status { + bail!("The development broker identity changed; select your community and try again"); + } + broker.authority = session + .archive_authority + .context("Community has no explicit membership authority")?; + nostr::PublicKey::from_hex(&broker.authority)?; + broker.relay = session.relay_url; + Ok(broker) + } + async fn value(response: reqwest::Response) -> anyhow::Result { + if !response.status().is_success() { + bail!("Compute broker request failed ({})", response.status()); + } + if response + .content_length() + .is_some_and(|n| n > 8 * 1024 * 1024) + { + bail!("Compute response too large"); + } + let mut response = response; + let mut bytes = Vec::new(); + while let Some(chunk) = response.chunk().await? { + if bytes.len() + chunk.len() > 8 * 1024 * 1024 { + bail!("Compute response too large"); + } + bytes.extend_from_slice(&chunk); + } + Ok(serde_json::from_slice(&bytes)?) + } + async fn session(&self) -> anyhow::Result { + Ok(serde_json::from_value( + Self::value( + self.client + .get(format!("{}/session", self.endpoint)) + .send() + .await?, + ) + .await?, + )?) + } + async fn post_url(&self, url: &str, body: &Value) -> anyhow::Result { + Self::value( + self.client + .post(url) + .header("Origin", &self.origin) + .header("X-Buzz-Read-Priority", "background") + .json(body) + .send() + .await?, + ) + .await + } + async fn query(&self, filter: Value) -> anyhow::Result> { + let events: Vec = serde_json::from_value( + self.post_url(&format!("{}/query", self.endpoint), &json!([filter])) + .await?, + )?; + for event in &events { + event.verify().context("Invalid signed compute evidence")?; + } + Ok(events) + } + async fn roster(&self) -> anyhow::Result { + self.query(json!({"kinds":[13534],"authors":[self.authority],"limit":1})) + .await? + .into_iter() + .filter(|e| { + e.kind.as_u16() == 13534 + && e.pubkey.to_hex() == self.authority + && e.created_at <= nostr::Timestamp::now() + }) + .max_by(|a, b| { + a.created_at + .cmp(&b.created_at) + .then_with(|| b.id.cmp(&a.id)) + }) + .context("Current community membership is unavailable") + } + pub async fn evidence(&self) -> anyhow::Result> { + let session = self.session().await?; + if session.viewer != self.viewer + || session.archive_authority.as_deref() != Some(&self.authority) + || session.relay_url != self.relay + { + bail!("Compute account or community changed"); + } + let roster = self.roster().await?; + let members = + discovery::latest_membership_list(std::slice::from_ref(&roster)).unwrap_or_default(); + if !members.contains(&self.viewer) { + bail!("You are no longer a member of this community"); + } + let members: Vec<_> = members.into_iter().collect(); + let mut events = vec![roster.clone()]; + let mut bytes = roster.as_json().len(); + for authors in members.chunks(100) { + let mut filter = + json!({"kinds":[30003],"authors":authors,"#k":["buzz-mesh-status"],"limit":100}); + let mut seen = BTreeSet::new(); + loop { + let mut page = self.query(filter.clone()).await?; + if page.len() > 100 { + bail!("Invalid compute page"); + } + for e in &page { + let id = e.id.to_hex(); + if e.kind.as_u16() != 30003 + || !authors.contains(&e.pubkey.to_hex()) + || !seen.insert(id.clone()) + || !e + .tags + .iter() + .any(|t| t.as_slice() == ["k", "buzz-mesh-status"]) + || filter["until"].as_u64().is_some_and(|until| { + e.created_at.as_secs() > until + || (e.created_at.as_secs() == until + && id.as_str() <= filter["before_id"].as_str().unwrap_or("")) + }) + { + bail!("Invalid compute pagination"); + } + bytes += e.as_json().len(); + } + if bytes > 8 * 1024 * 1024 || events.len() + page.len() > 10000 { + bail!("Compute evidence exceeds limit"); + } + let done = page.len() < 100; + page.sort_by(|a, b| { + b.created_at + .cmp(&a.created_at) + .then_with(|| a.id.cmp(&b.id)) + }); + if let Some(last) = page.last() { + filter["until"] = json!(last.created_at.as_secs()); + filter["before_id"] = json!(last.id.to_hex()); + } + events.extend(page); + if done { + break; + } + } + } + if self.roster().await?.id != roster.id { + bail!("Community membership changed during discovery; retry sharing"); + } + Ok(events) + } + pub async fn publish(&self, payload: Value) -> anyhow::Result<()> { + let receipt = self + .post_url(&format!("{}/compute-status", self.endpoint), &payload) + .await?; + // The broker validates the exact event id before returning this receipt. + let accepted = receipt.get("accepted").and_then(Value::as_bool) == Some(true); + if !accepted { + bail!("Community did not acknowledge compute status"); + } + Ok(()) + } +} +use nostr::JsonUtil; + +#[cfg(test)] +mod tests { + use super::*; + use nostr::{EventBuilder, Keys, Kind, Tag}; + use std::{ + io::{BufRead, BufReader, Read, Write}, + net::{TcpListener, TcpStream}, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + }; + struct Fixture { + origin: String, + stop: Arc, + thread: Option>, + } + impl Drop for Fixture { + fn drop(&mut self) { + self.stop.store(true, Ordering::SeqCst); + let _ = TcpStream::connect(self.origin.trim_start_matches("http://")); + if let Some(thread) = self.thread.take() { + thread.join().unwrap(); + } + } + } + fn server(handler: impl Fn(&str, Value) -> Value + Send + 'static) -> Fixture { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let origin = format!("http://{}", listener.local_addr().unwrap()); + let stop = Arc::new(AtomicBool::new(false)); + let stopped = stop.clone(); + let thread = std::thread::spawn(move || { + for socket in listener.incoming() { + if stopped.load(Ordering::SeqCst) { + break; + } + let mut socket = socket.unwrap(); + socket + .set_read_timeout(Some(Duration::from_secs(2))) + .unwrap(); + let mut reader = BufReader::new(socket.try_clone().unwrap()); + let mut line = String::new(); + reader.read_line(&mut line).unwrap(); + let route = line.split_whitespace().nth(1).unwrap().to_string(); + let mut length = 0; + loop { + line.clear(); + reader.read_line(&mut line).unwrap(); + if line == "\r\n" { + break; + } + if let Some(value) = line.to_lowercase().strip_prefix("content-length:") { + length = value.trim().parse::().unwrap(); + } + } + let mut body = vec![0; length]; + reader.read_exact(&mut body).unwrap(); + let value = handler(&route, serde_json::from_slice(&body).unwrap_or(Value::Null)) + .to_string(); + write!(socket,"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nConnection: close\r\nContent-Length: {}\r\n\r\n{}",value.len(),value).unwrap(); + } + }); + Fixture { + origin, + stop, + thread: Some(thread), + } + } + fn request(viewer: &Keys) -> StartRequest { + StartRequest { + mode: crate::worker::Mode::Serve, + community: "https://community.example".into(), + viewer: viewer.public_key().to_hex(), + model_id: "test-model".into(), + max_vram_gb: None, + } + } + #[tokio::test] + async fn worker_uses_broker_identity_and_signed_member_evidence_not_renderer_rosters() { + let viewer = Keys::generate(); + let authority = Keys::generate(); + let roster = EventBuilder::new(Kind::Custom(13534), "") + .tags([Tag::parse(["member".to_string(), viewer.public_key().to_hex()]).unwrap()]) + .sign_with_keys(&authority) + .unwrap(); + let viewer_key = viewer.public_key().to_hex(); + let authority_key = authority.public_key().to_hex(); + let expected = roster.id; + let fixture = server(move |route, body| { + if route.ends_with("/register") { + return json!({"url":"https://community.example"}); + } + if route.ends_with("/session") { + return json!({"viewer":viewer_key,"archiveAuthority":authority_key,"relayUrl":"https://community.example","computeStatus":true}); + } + assert!(route.ends_with("/query")); + if body[0]["kinds"][0] == 13534 { + json!([roster]) + } else { + json!([]) + } + }); + let broker = Broker::connect(&fixture.origin, &request(&viewer)) + .await + .unwrap(); + let events = broker.evidence().await.unwrap(); + assert_eq!(events.len(), 1); + assert_eq!(events[0].id, expected); + assert!( + Broker::connect(&fixture.origin, &request(&Keys::generate())) + .await + .is_err() + ); + } + #[tokio::test] + async fn rejects_signed_roster_from_wrong_author_and_unlisted_viewer() { + for wrong_author in [true, false] { + let viewer = Keys::generate(); + let authority = Keys::generate(); + let roster = EventBuilder::new(Kind::Custom(13534), "") + .sign_with_keys(if wrong_author { &viewer } else { &authority }) + .unwrap(); + let viewer_key = viewer.public_key().to_hex(); + let authority_key = authority.public_key().to_hex(); + let fixture = server(move |route, _body| { + if route.ends_with("/register") { + json!({}) + } else if route.ends_with("/session") { + json!({"viewer":viewer_key,"archiveAuthority":authority_key,"relayUrl":"https://community.example","computeStatus":true}) + } else { + json!([roster]) + } + }); + let broker = Broker::connect(&fixture.origin, &request(&viewer)) + .await + .unwrap(); + assert!(broker.evidence().await.is_err()); + } + } + #[tokio::test] + async fn rejects_remote_or_credential_bearing_broker_origins_before_network() { + let req = request(&Keys::generate()); + for origin in [ + "http://example.com:1430", + "https://localhost:1430", + "http://user@localhost:1430", + "http://localhost:1430/path", + "http://localhost:1430/?secret=value", + ] { + assert!(Broker::connect(origin, &req).await.is_err()); + } + } +} diff --git a/crates/community-compute/src/catalog.rs b/crates/community-compute/src/catalog.rs new file mode 100644 index 000000000..21a492875 --- /dev/null +++ b/crates/community-compute/src/catalog.rs @@ -0,0 +1,347 @@ +//! Hardware-aware model catalog for the Share-compute picker. +//! +//! Same diagnose pattern as mesh-console: survey the machine's AI memory, +//! rank mesh-llm's curated `MODEL_CATALOG` by how each model fits, mark what +//! is already in the HuggingFace cache, and recommend a best fit. This +//! replaces guessing into a free-text model field. + +use serde::Serialize; + +use mesh_client::models::catalog::{parse_size_gb, MODEL_CATALOG}; +use mesh_llm_node::models::{default_huggingface_cache_dir, scan_installed_models}; +use mesh_llm_system::hardware; +use mesh_llm_system::vram::{format_rated_capacity, rated_capacity_gb}; + +/// Buzz-curated tier picks. These are the models we know survive the agent +/// harness on shared compute. +/// +/// The recommended ladder follows rated unified memory: +/// - below 32 GB: Gemma 4 E4B; +/// - 32 GB through the rated classes below 64 GB: Qwen3.5 9B; +/// - 64 GB and above: Qwen3.8 27B. +/// +/// The Qwen entries are canonicalized from mesh-llm's compiled +/// `MODEL_CATALOG` rather than synthesized. +const CURATED_LARGE: &str = "unsloth/Qwen3.8-27B-GGUF:Q4_K_M"; +const CURATED_LARGE_ALIAS: &str = "Qwen3.8-27B-Q4_K_M"; +const CURATED_MEDIUM: &str = "unsloth/Qwen3.5-9B-GGUF:Q4_K_M"; +const CURATED_MEDIUM_ALIAS: &str = "Qwen3.5-9B-Vision-Q4_K_M"; +const CURATED_SMALL: &str = "unsloth/gemma-4-E4B-it-GGUF:Q4_K_M"; +const CURATED_SMALL_ALIAS: &str = "Gemma-4-E4B-it-Q4_K_M"; +/// Superseded large alias retained only to preserve the historical string +/// canonicalization contract. Availability in a particular Mesh runtime is +/// determined by that runtime's compiled catalog. +const LEGACY_LARGE: &str = "unsloth/gemma-4-26B-A4B-it-GGUF:UD-Q4_K_M"; +const LEGACY_LARGE_ALIAS: &str = "gemma-4-26B-A4B-it-UD-Q4_K_M"; +/// Rated-capacity boundary for the balanced Qwen3.5 9B tier. +const CURATED_MEDIUM_MIN_RATED_GB: u64 = 32; +/// Qwen3.8 27B is recommended for 64 GB-and-larger rated capacity classes, +/// leaving substantial headroom beyond its observed working footprint for KV +/// cache and runtime use. +const CURATED_LARGE_MIN_RATED_GB: u64 = 64; + +/// The Buzz-curated recommendation for a machine's rated memory capacity. +fn buzz_recommended_model(rated_gb: Option) -> &'static str { + match rated_gb { + Some(gb) if gb >= CURATED_LARGE_MIN_RATED_GB => CURATED_LARGE, + Some(gb) if gb >= CURATED_MEDIUM_MIN_RATED_GB => CURATED_MEDIUM, + Some(_) | None => CURATED_SMALL, + } +} + +/// Convert Buzz's historical curated package aliases into the canonical model +/// ids advertised and accepted by Mesh's OpenAI ingress. +pub(crate) fn canonical_curated_model_id(model_id: &str) -> &str { + match model_id.trim() { + CURATED_SMALL_ALIAS => CURATED_SMALL, + CURATED_MEDIUM_ALIAS => CURATED_MEDIUM, + CURATED_LARGE_ALIAS => CURATED_LARGE, + LEGACY_LARGE_ALIAS => LEGACY_LARGE, + other => other, + } +} + +/// How a model sits inside this machine's usable AI memory. +/// Mirrors mesh-llm's private `fit_code_for_size_label` thresholds. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum ModelFit { + Comfortable, + Tight, + Tradeoff, + TooLarge, +} + +fn fit_code(model_gb: f64, vram_gb: f64) -> ModelFit { + if model_gb <= vram_gb * 0.6 { + ModelFit::Comfortable + } else if model_gb <= vram_gb * 0.9 { + ModelFit::Tight + } else if model_gb <= vram_gb * 1.1 { + ModelFit::Tradeoff + } else { + ModelFit::TooLarge + } +} + +fn fit_rank(fit: ModelFit) -> u8 { + match fit { + ModelFit::Comfortable => 0, + ModelFit::Tight => 1, + ModelFit::Tradeoff => 2, + ModelFit::TooLarge => 3, + } +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MeshCatalogEntry { + /// Catalog name — what the user serves (goes straight into the model field). + pub name: String, + /// Display size, e.g. "5.0GB". + pub size: String, + pub size_gb: f64, + pub description: String, + pub fit: ModelFit, + pub installed: bool, + pub recommended: bool, + /// Buzz-curated pick — known to survive the agent harness. Curated + /// entries render above the fold; everything else is "advanced". + pub curated: bool, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MeshModelCatalog { + /// e.g. "Apple M3 Max" + pub gpu_name: Option, + /// Usable AI memory, display-formatted (e.g. "96 GB"). + pub vram_display: String, + pub vram_gb: f64, + /// Best-fit catalog name for this hardware, if any. + pub recommended: Option, + /// Ranked: recommended first, then by fit, then larger first within a fit. + pub entries: Vec, +} + +/// Survey hardware and rank the curated catalog for this machine. +/// Draft (speculative-decoding) models are excluded — they are not something +/// a person shares directly. +pub fn model_catalog() -> MeshModelCatalog { + let survey = hardware::survey(); + let vram_gb = survey.vram_bytes as f64 / 1e9; + build_catalog( + survey.gpu_name.clone(), + survey.vram_bytes, + vram_gb, + &installed_names(), + ) +} + +fn installed_names() -> Vec<(String, String)> { + let cache = default_huggingface_cache_dir(); + scan_installed_models(cache) + .into_iter() + .map(|m| { + let file = m + .path + .file_name() + .and_then(|f| f.to_str()) + .unwrap_or_default() + .to_string(); + (file, m.model_ref) + }) + .collect() +} + +fn build_catalog( + gpu_name: Option, + vram_bytes: u64, + vram_gb: f64, + installed: &[(String, String)], +) -> MeshModelCatalog { + let is_installed = |file: &str, name: &str| { + installed + .iter() + .any(|(f, model_ref)| f == file || model_ref.contains(name)) + }; + let mut entries: Vec = MODEL_CATALOG + .iter() + .filter(|m| !is_draft_only(&m.name)) + .map(|m| { + let size_gb = parse_size_gb(&m.size); + let name = canonical_curated_model_id(&m.name).to_string(); + MeshCatalogEntry { + fit: fit_code(size_gb, vram_gb), + installed: is_installed(&m.file, &name) || is_installed(&m.file, &m.name), + recommended: false, + curated: false, + name, + size: m.size.clone(), + size_gb, + description: m.description.clone(), + } + }) + .collect(); + + let recommended = Some(buzz_recommended_model(rated_capacity_gb(vram_bytes)).to_string()); + for entry in &mut entries { + entry.recommended = recommended.as_deref() == Some(entry.name.as_str()); + // All curated tiers are always offered: the recommendation plus + // lighter and heavier alternatives appropriate to other machine tiers. + entry.curated = entry.name == CURATED_LARGE + || entry.name == CURATED_MEDIUM + || entry.name == CURATED_SMALL; + } + + entries.sort_by(|a, b| { + b.recommended + .cmp(&a.recommended) + .then(b.curated.cmp(&a.curated)) + .then(fit_rank(a.fit).cmp(&fit_rank(b.fit))) + .then(b.size_gb.total_cmp(&a.size_gb)) + }); + + MeshModelCatalog { + gpu_name, + vram_display: format_rated_capacity(vram_bytes), + vram_gb, + recommended, + entries, + } +} + +/// A model that exists in the catalog only as another model's draft +/// (speculative decoding helper) — identified by being referenced in any +/// `draft` field. People share chat models, not drafts. +fn is_draft_only(name: &str) -> bool { + MODEL_CATALOG + .iter() + .any(|m| m.draft.as_deref() == Some(name)) + && !MODEL_CATALOG + .iter() + .any(|m| m.name == name && m.draft.is_some()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn fit_thresholds_match_mesh_llm() { + // 10GB model on various machines. Thresholds are 0.6 / 0.9 / 1.1. + assert_eq!(fit_code(10.0, 20.0), ModelFit::Comfortable); + assert_eq!(fit_code(10.0, 12.0), ModelFit::Tight); + assert_eq!(fit_code(10.0, 10.0), ModelFit::Tradeoff); + assert_eq!(fit_code(10.0, 8.0), ModelFit::TooLarge); + } + + #[test] + fn catalog_ranks_recommended_first_then_fit() { + let catalog = build_catalog(Some("Test GPU".into()), 24_000_000_000, 24.0, &[]); + assert!( + !catalog.entries.is_empty(), + "curated catalog must not be empty" + ); + // The recommended entry (if present in the catalog) must be first. + if let Some(recommended) = &catalog.recommended { + if catalog.entries.iter().any(|e| &e.name == recommended) { + assert_eq!(&catalog.entries[0].name, recommended); + assert!(catalog.entries[0].recommended); + } + } + // Fit ranks must be non-decreasing after the recommended/curated head. + let ranks: Vec = catalog + .entries + .iter() + .skip_while(|e| e.recommended || e.curated) + .map(|e| fit_rank(e.fit)) + .collect(); + assert!( + ranks.windows(2).all(|w| w[0] <= w[1]), + "fit ranks out of order: {ranks:?}" + ); + } + + #[test] + fn recommendation_follows_buzz_curated_ladder() { + assert_eq!(CURATED_SMALL, "unsloth/gemma-4-E4B-it-GGUF:Q4_K_M"); + assert_eq!(CURATED_MEDIUM, "unsloth/Qwen3.5-9B-GGUF:Q4_K_M"); + assert_eq!(CURATED_LARGE, "unsloth/Qwen3.8-27B-GGUF:Q4_K_M"); + + // Qwen3.8 is recommended for 64 GB-and-larger rated capacity classes. + let large = build_catalog(None, 64_000_000_000, 64.0, &[]); + assert_eq!(large.recommended.as_deref(), Some(CURATED_LARGE)); + let large_80 = build_catalog(None, 80_000_000_000, 80.0, &[]); + assert_eq!(large_80.recommended.as_deref(), Some(CURATED_LARGE)); + let big = build_catalog(None, 128_000_000_000, 128.0, &[]); + assert_eq!(big.recommended.as_deref(), Some(CURATED_LARGE)); + + // The balanced Qwen3.5 tier covers every rated class from 32 GB up + // to (but not including) 64 GB. + let medium = build_catalog(None, 32_000_000_000, 32.0, &[]); + assert_eq!(medium.recommended.as_deref(), Some(CURATED_MEDIUM)); + let medium_max = build_catalog(None, 48_000_000_000, 48.0, &[]); + assert_eq!(medium_max.recommended.as_deref(), Some(CURATED_MEDIUM)); + + // Smaller and unknown machines use the light Gemma tier. + let small = build_catalog(None, 24_000_000_000, 24.0, &[]); + assert_eq!(small.recommended.as_deref(), Some(CURATED_SMALL)); + let unknown = build_catalog(None, 0, 0.0, &[]); + assert_eq!(unknown.recommended.as_deref(), Some(CURATED_SMALL)); + } + + #[test] + fn curated_package_aliases_migrate_to_openai_model_ids() { + assert_eq!( + canonical_curated_model_id(CURATED_SMALL_ALIAS), + CURATED_SMALL + ); + assert_eq!( + canonical_curated_model_id(CURATED_MEDIUM_ALIAS), + CURATED_MEDIUM + ); + assert_eq!( + canonical_curated_model_id(CURATED_LARGE_ALIAS), + CURATED_LARGE + ); + assert_eq!(canonical_curated_model_id(LEGACY_LARGE_ALIAS), LEGACY_LARGE); + assert_eq!( + canonical_curated_model_id("other/model:Q4"), + "other/model:Q4" + ); + } + + #[test] + fn curated_picks_lead_the_catalog() { + let catalog = build_catalog(None, 96_000_000_000, 96.0, &[]); + // Recommended curated entry first, then the other two curated tiers, + // with advanced entries after them. + assert_eq!(catalog.entries[0].name, CURATED_LARGE); + assert!(catalog.entries[0].recommended && catalog.entries[0].curated); + assert_eq!(catalog.entries[1].name, CURATED_MEDIUM); + assert!(catalog.entries[1].curated && !catalog.entries[1].recommended); + assert_eq!(catalog.entries[2].name, CURATED_SMALL); + assert!(catalog.entries[2].curated && !catalog.entries[2].recommended); + assert!(catalog.entries[3..].iter().all(|e| !e.curated)); + // The large pick comes from the compiled catalog with a real size, so + // fit ranking is meaningful rather than a placeholder. + assert!(catalog.entries[0].size_gb > 10.0); + } + + #[test] + fn installed_matches_by_file_or_model_ref() { + let installed = vec![( + "Qwen3-8B-Q4_K_M.gguf".to_string(), + "unsloth/Qwen3-8B-GGUF:Q4_K_M".to_string(), + )]; + let catalog = build_catalog(None, 96_000_000_000, 96.0, &installed); + let qwen8b = catalog.entries.iter().find(|e| e.name == "Qwen3-8B-Q4_K_M"); + if let Some(entry) = qwen8b { + assert!(entry.installed, "cached file must mark entry installed"); + } + // A machine with nothing installed marks nothing installed. + let empty = build_catalog(None, 96_000_000_000, 96.0, &[]); + assert!(empty.entries.iter().all(|e| !e.installed)); + } +} diff --git a/crates/community-compute/src/consumer.rs b/crates/community-compute/src/consumer.rs new file mode 100644 index 000000000..9f5202bce --- /dev/null +++ b/crates/community-compute/src/consumer.rs @@ -0,0 +1,122 @@ +//! Bounded test request through the owned consumer's loopback API. +use anyhow::{bail, Context}; +use serde_json::{json, Value}; +use std::time::Duration; + +async fn read(mut response: reqwest::Response) -> anyhow::Result { + let status = response.status(); + let mut bytes = Vec::new(); + while let Some(chunk) = response.chunk().await? { + if bytes.len() + chunk.len() > 65536 { + bail!("Compute response exceeds limit"); + } + bytes.extend_from_slice(&chunk); + } + if !status.is_success() { + bail!("Compute request failed ({status}); check provider and connection"); + } + Ok(serde_json::from_slice(&bytes)?) +} +pub async fn test_request( + api: &str, + prompt: &str, + is_current: impl Fn() -> bool, +) -> anyhow::Result { + if !is_current() { + bail!("Compute session changed"); + } + let cancelled = async { + while is_current() { + tokio::time::sleep(Duration::from_millis(100)).await; + } + }; + tokio::select! { + result = run_request(api, prompt) => result, + _ = cancelled => anyhow::bail!("Compute request cancelled"), + } +} +async fn run_request(api: &str, prompt: &str) -> anyhow::Result { + let url = url::Url::parse(api)?; + if url.scheme() != "http" + || url.host_str() != Some("127.0.0.1") + || url.port().is_none() + || url.path() != "/v1" + || !url.username().is_empty() + || url.password().is_some() + || url.query().is_some() + || url.fragment().is_some() + { + bail!("Invalid owned compute API"); + } + if prompt.trim().is_empty() || prompt.len() > 2000 { + bail!("Enter a prompt of up to 2,000 bytes"); + } + let client = reqwest::Client::builder() + .no_proxy() + .redirect(reqwest::redirect::Policy::none()) + .timeout(Duration::from_secs(60)) + .build()?; + let models = read(client.get(format!("{api}/models")).send().await?).await?; + let model = models.pointer("/data/0/id").and_then(Value::as_str).context("No remote model is available yet. Start the provider, then reconnect if admission changed.")?; + let response = read(client.post(format!("{api}/chat/completions")).json(&json!({"model":model,"messages":[{"role":"user","content":prompt}],"max_tokens":128,"stream":false})).send().await?).await?; + let text = response + .pointer("/choices/0/message/content") + .and_then(Value::as_str) + .context("The provider returned no text")?; + Ok(text.to_owned()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::{ + io::Read, + net::TcpListener, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + }; + #[tokio::test] + async fn refuses_non_owned_endpoints_and_cancels_pending_model_lookup() { + assert!(test_request("https://example.com/v1", "hello", || true) + .await + .is_err()); + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let api = format!( + "http://127.0.0.1:{}/v1", + listener.local_addr().unwrap().port() + ); + let current = Arc::new(AtomicBool::new(true)); + let (started, receive) = tokio::sync::oneshot::channel(); + let server_current = current.clone(); + let server = std::thread::spawn(move || { + let (mut stream, _) = listener.accept().unwrap(); + stream + .set_read_timeout(Some(Duration::from_secs(3))) + .unwrap(); + let mut buf = [0; 4096]; + let count = stream.read(&mut buf).unwrap(); + assert!(String::from_utf8_lossy(&buf[..count]).starts_with("GET /v1/models")); + started.send(()).unwrap(); + while server_current.load(Ordering::SeqCst) { + std::thread::yield_now(); + } + }); + let request_current = current.clone(); + let request = tokio::spawn(async move { + test_request(&api, "private test prompt", || { + request_current.load(Ordering::SeqCst) + }) + .await + }); + receive.await.unwrap(); + current.store(false, Ordering::SeqCst); + assert!(tokio::time::timeout(Duration::from_secs(2), request) + .await + .unwrap() + .unwrap() + .is_err()); + server.join().unwrap(); + } +} diff --git a/crates/community-compute/src/discovery.rs b/crates/community-compute/src/discovery.rs new file mode 100644 index 000000000..757045e04 --- /dev/null +++ b/crates/community-compute/src/discovery.rs @@ -0,0 +1,143 @@ +use ed25519_dalek::{Signature, Verifier, VerifyingKey}; +use sha2::{Digest, Sha256}; +use std::collections::BTreeSet; +pub(crate) const STATUS_FRESHNESS_SECS: u64 = 120; + +pub(crate) fn status_is_fresh(event: &nostr::Event, now: u64) -> bool { + event + .created_at + .as_secs() + .saturating_add(STATUS_FRESHNESS_SECS) + >= now +} + +pub(crate) fn latest_membership_list(events: &[nostr::Event]) -> Option> { + events + .iter() + .filter(|event| event.kind.as_u16() == 13_534) + .max_by(|a, b| { + a.created_at + .cmp(&b.created_at) + .then_with(|| b.id.cmp(&a.id)) + }) + .map(|event| { + event + .tags + .iter() + .filter_map(|tag| { + let slice = tag.as_slice(); + let name = slice.first()?; + if name != "member" && name != "p" { + return None; + } + slice + .get(1) + .map(|pubkey| pubkey.trim().to_ascii_lowercase()) + }) + .filter(|pubkey| nostr::PublicKey::from_hex(pubkey).is_ok()) + .collect() + }) +} + +pub(crate) fn owner_id_from_status_event(event: &nostr::Event) -> Option { + let content = serde_json::from_str::(&event.content).ok()?; + let owner_id = content + .get("ownerId") + .or_else(|| content.get("owner_id"))? + .as_str()? + .trim(); + let verifying_key_bytes: [u8; 32] = + hex::decode(content.get("ownerVerifyingKey")?.as_str()?.trim()) + .ok()? + .try_into() + .ok()?; + let derived_owner = hex::encode(Sha256::digest(verifying_key_bytes)); + if owner_id != derived_owner { + return None; + } + let signature_bytes = hex::decode(content.get("ownerBindingSig")?.as_str()?.trim()).ok()?; + let signature = Signature::from_slice(&signature_bytes).ok()?; + let verifying_key = VerifyingKey::from_bytes(&verifying_key_bytes).ok()?; + verifying_key + .verify( + &super::identity::member_binding_bytes(&event.pubkey.to_hex()), + &signature, + ) + .ok()?; + Some(owner_id.to_string()) +} + +pub(crate) fn endpoint_binding_is_valid(event: &nostr::Event, content: &serde_json::Value) -> bool { + let Some(endpoint_tokens) = super::identity::advertised_endpoint_tokens(content) else { + return false; + }; + let Some(verifying_key_bytes) = content + .get("ownerVerifyingKey") + .and_then(serde_json::Value::as_str) + .map(str::trim) + .and_then(|value| hex::decode(value).ok()) + .and_then(|value| <[u8; 32]>::try_from(value).ok()) + else { + return false; + }; + let Some(signature) = content + .get("ownerEndpointBindingSig") + .and_then(serde_json::Value::as_str) + .map(str::trim) + .and_then(|value| hex::decode(value).ok()) + .and_then(|value| Signature::from_slice(&value).ok()) + else { + return false; + }; + let Ok(verifying_key) = VerifyingKey::from_bytes(&verifying_key_bytes) else { + return false; + }; + verifying_key + .verify( + &super::identity::member_endpoint_binding_bytes( + &event.pubkey.to_hex(), + &endpoint_tokens, + ), + &signature, + ) + .is_ok() +} + +pub(crate) fn device_name_from_status( + payload: &serde_json::Value, + endpoint_id: Option<&str>, +) -> Option { + string_value(payload, "deviceName") + .or_else(|| string_value(payload, "device_name")) + .or_else(|| string_value(payload, "my_hostname")) + .or_else(|| string_value(payload, "hostname")) + .or_else(|| endpoint_id.map(short_endpoint_label)) +} + +pub(crate) fn endpoint_id_from_status( + payload: &serde_json::Value, + invite_token: Option<&str>, +) -> Option { + string_value(payload, "endpointId") + .or_else(|| string_value(payload, "endpoint_id")) + .or_else(|| string_value(payload, "node_id")) + .or_else(|| invite_token.and_then(endpoint_id_from_invite_token)) +} + +pub(crate) fn string_value(value: &serde_json::Value, key: &str) -> Option { + value + .get(key) + .and_then(serde_json::Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(ToString::to_string) +} + +pub(crate) fn short_endpoint_label(endpoint_id: &str) -> String { + endpoint_id.chars().take(12).collect() +} +fn endpoint_id_from_invite_token(token: &str) -> Option { + super::transport_policy::validate_advertised_endpoint(token) + .ok() + .map(|endpoint| endpoint.endpoint_id) +} diff --git a/crates/community-compute/src/identity.rs b/crates/community-compute/src/identity.rs new file mode 100644 index 000000000..b6d7f3e5d --- /dev/null +++ b/crates/community-compute/src/identity.rs @@ -0,0 +1,152 @@ +//! App-owned mesh identity, distinct from the Buzz/Nostr signing key. +//! The native parent chooses a persistent app-data path. Keeping this separate +//! from the old Buzz app prevents its stopped-status publisher from replacing +//! this worker's serving note for the same owner coordinate. + +use std::path::PathBuf; + +use mesh_llm_host_runtime::crypto::{keystore_exists, load_keystore, save_keystore, OwnerKeypair}; +use sha2::{Digest, Sha256}; + +#[derive(Debug, Clone)] +pub struct OwnerIdentity { + pub keystore_path: PathBuf, + pub owner_id: String, + pub verifying_key_hex: String, +} + +impl OwnerIdentity { + /// Sign a Buzz-to-MeshLLM ownership binding. The member's Nostr signature + /// authenticates the discovery event; this Ed25519 signature proves the + /// advertised owner id is backed by the MeshLLM owner key itself. + pub fn sign_member_binding(&self, member_pubkey: &str) -> anyhow::Result { + let keypair = load_keystore(&self.keystore_path, None).map_err(|error| { + anyhow::anyhow!("failed to load mesh owner keystore for binding: {error}") + })?; + Ok(hex::encode( + keypair.sign_bytes(&member_binding_bytes(member_pubkey)), + )) + } + + /// Sign the exact endpoint tokens advertised by this member. This prevents + /// a holder of only the Nostr member key from reusing a valid owner binding + /// while substituting an attacker-selected dial target. + pub fn sign_member_endpoint_binding( + &self, + member_pubkey: &str, + endpoint_tokens: &[String], + ) -> anyhow::Result { + let keypair = load_keystore(&self.keystore_path, None).map_err(|error| { + anyhow::anyhow!("failed to load mesh owner keystore for endpoint binding: {error}") + })?; + Ok(hex::encode(keypair.sign_bytes( + &member_endpoint_binding_bytes(member_pubkey, endpoint_tokens), + ))) + } +} + +pub fn member_binding_bytes(member_pubkey: &str) -> Vec { + format!( + "buzz-mesh-owner-binding-v1:{}", + member_pubkey.trim().to_ascii_lowercase() + ) + .into_bytes() +} + +/// Canonical bytes binding a member-associated node identity to the exact set +/// of endpoint tokens in its status event. +pub fn member_endpoint_binding_bytes(member_pubkey: &str, endpoint_tokens: &[String]) -> Vec { + let mut endpoints = endpoint_tokens + .iter() + .map(|token| token.trim()) + .filter(|token| !token.is_empty()) + .collect::>(); + endpoints.sort_unstable(); + endpoints.dedup(); + + let mut digest = Sha256::new(); + for endpoint in endpoints { + digest.update((endpoint.len() as u64).to_be_bytes()); + digest.update(endpoint.as_bytes()); + } + format!( + "buzz-mesh-owner-endpoint-binding-v1:{}:{}", + member_pubkey.trim().to_ascii_lowercase(), + hex::encode(digest.finalize()) + ) + .into_bytes() +} + +/// Extract endpoint tokens from a status payload using the same canonical +/// field rules for publication and verification. +pub fn advertised_endpoint_tokens(payload: &serde_json::Value) -> Option> { + let Some(targets) = payload + .get("serveTargets") + .or_else(|| payload.get("serve_targets")) + else { + return Some(Vec::new()); + }; + let targets = targets.as_array()?; + targets + .iter() + .map(|target| { + target + .get("endpointAddr") + .or_else(|| target.get("endpoint_addr"))? + .as_str() + .map(str::trim) + .filter(|token| !token.is_empty()) + .map(ToString::to_string) + }) + .collect() +} + +fn owner_identity(path: PathBuf, keypair: &OwnerKeypair) -> OwnerIdentity { + OwnerIdentity { + owner_id: keypair.owner_id(), + verifying_key_hex: hex::encode(keypair.verifying_key().as_bytes()), + keystore_path: path, + } +} + +/// Called only inside the explicitly started/restored worker, never for status reads. +pub fn ensure_owner_identity_at(path: PathBuf) -> anyhow::Result { + if keystore_exists(&path) { + let keypair = load_keystore(&path, None).map_err(|error| { + anyhow::anyhow!( + "failed to load mesh owner keystore at {}: {error}", + path.display() + ) + })?; + return Ok(owner_identity(path, &keypair)); + } + let keypair = OwnerKeypair::generate(); + save_keystore(&path, &keypair, None, false).map_err(|error| { + anyhow::anyhow!( + "failed to save mesh owner keystore at {}: {error}", + path.display() + ) + })?; + Ok(owner_identity(path, &keypair)) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn app_owner_is_persistent_and_isolated_from_other_runtime_keys() { + let root = std::env::temp_dir().join(format!( + "buzz-owner-test-{}", + nostr::Keys::generate().public_key() + )); + std::fs::create_dir_all(&root).unwrap(); + let first = ensure_owner_identity_at(root.join("app.json")).unwrap(); + let restored = ensure_owner_identity_at(root.join("app.json")).unwrap(); + let other = ensure_owner_identity_at(root.join("other.json")).unwrap(); + assert_eq!(first.owner_id, restored.owner_id); + assert_eq!(first.verifying_key_hex, restored.verifying_key_hex); + assert_ne!(first.owner_id, other.owner_id); + assert_eq!(first.keystore_path, root.join("app.json")); + std::fs::remove_dir_all(root).unwrap(); + } +} diff --git a/crates/community-compute/src/lib.rs b/crates/community-compute/src/lib.rs new file mode 100644 index 000000000..406a107f5 --- /dev/null +++ b/crates/community-compute/src/lib.rs @@ -0,0 +1,72 @@ +//! Read-only protocol projection adapted from block/buzz PR #7691 (Apache-2.0). +//! This crate does not create keys, start a node, or authorize mesh admission. +mod broker; +mod catalog; +pub mod consumer; +pub mod usage; +pub mod worker; +pub use catalog::{model_catalog, MeshModelCatalog}; +mod discovery; +mod identity; +mod snapshot; +mod transport_policy; + +use serde::Deserialize; +pub use snapshot::MeshSnapshot; +const MESH_STATUS_KIND: u64 = 30003; +const MESH_IROH_RELAYS_ENV: &str = "BUZZ_MESH_IROH_RELAYS"; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct MeshServeTarget { + model_id: String, + endpoint_addr: String, +} + +/// Project signed relay evidence for display. The host supplies its connected +/// community's explicit NIP-11 self; renderer input must never authorize serving. +pub fn project( + events: Vec, + authority: &str, + viewer: &str, +) -> Result { + let authority = + nostr::PublicKey::from_hex(authority).map_err(|_| "Invalid community authority")?; + nostr::PublicKey::from_hex(viewer).map_err(|_| "Invalid viewer")?; + if events.len() > 10000 { + return Err("Community status exceeds the display limit".into()); + } + let now = nostr::Timestamp::now(); + let mut verified = Vec::new(); + for event in events { + if event.content.len() > 256 * 1024 || event.tags.len() > 20000 { + return Err("Community status exceeds the display limit".into()); + } + event + .verify() + .map_err(|_| "Invalid community event signature")?; + if event.created_at > now { + continue; + } + match event.kind.as_u16() { + 13534 if event.pubkey == authority => verified.push(event), + 30003 + if event.tags.iter().any(|tag| { + let parts = tag.as_slice(); + parts.first().map(String::as_str) == Some("k") + && parts.get(1).map(String::as_str) == Some("buzz-mesh-status") + }) => + { + verified.push(event) + } + _ => {} + } + } + if !verified.iter().any(|event| event.kind.as_u16() == 13534) { + return Err("Waiting for a verified current member roster".into()); + } + Ok(snapshot::snapshot_from_events(verified, Some(viewer))) +} + +#[cfg(test)] +mod tests; diff --git a/crates/community-compute/src/snapshot.rs b/crates/community-compute/src/snapshot.rs new file mode 100644 index 000000000..11ff3b8d3 --- /dev/null +++ b/crates/community-compute/src/snapshot.rs @@ -0,0 +1,359 @@ +//! Community-wide mesh snapshot — an **allowlisted** projection of the member +//! status notes already on the relay. +//! +//! Every sharing member already publishes its whole SDK status payload as +//! kind:30003 content (see `mod.rs::status_report_payload`), but +//! `availability_from_events` reads only `serveTargets`/`models` and discards +//! the rest. This module projects the few extra fields the Share-compute UI +//! needs — per-device shared capacity, a coarse state, and a stable label — +//! and **nothing else**. +//! +//! Deliberately narrow, for two reasons: +//! 1. Routing must keep using [`super::availability_from_events`]. This +//! snapshot is presentation-only and must never select a serve target. +//! 2. The published payload also carries a hardware fingerprint, peer +//! topology, and operational counters. Naming each field we surface keeps +//! the UI from quietly growing into the rest of it. +//! +//! Capacity uses the payload's `my_vram_gb`, which mesh-llm derives from a +//! `vram_bytes` already clamped to the member's own `--max-vram` cap. Raw +//! `gpus[].vram_bytes` is *not* capped, so summing GPUs would advertise more +//! than the member agreed to share. + +use std::collections::BTreeMap; + +use serde::{Deserialize, Serialize}; + +use super::discovery::{ + device_name_from_status, endpoint_binding_is_valid, endpoint_id_from_status, + latest_membership_list, owner_id_from_status_event, status_is_fresh, STATUS_FRESHNESS_SECS, +}; +use super::{MeshServeTarget, MESH_STATUS_KIND}; + +/// Coarse per-device state. Intentionally not the SDK's `NodeState`: the UI +/// only needs "is this device giving compute, warming up, or just consuming?" +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum MeshDeviceState { + /// Advertising at least one routable model. + Serving, + /// Serve-mode, model not ready yet. + Loading, + /// Serve-mode participant with no model work (contributes capacity only). + Standby, + /// Client-mode: consuming someone else's compute, not sharing. + Consuming, +} + +/// One device visible on the mesh. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct MeshSnapshotDevice { + /// Stable endpoint/device id, when the note carries one. + pub device_id: Option, + /// Human label: hostname when published, else a short endpoint id. + pub label: String, + /// Shared AI memory in GB, honoring the member's own cap. `None` when the + /// member is not currently running a node (a stopped member publishes only + /// its owner binding). + pub capacity_gb: Option, + /// Models this device is advertising right now. + pub models: Vec, + pub state: MeshDeviceState, + /// Whether this is the local member's own device. + pub is_self: bool, + /// Community member who signed this status note. Safe for profile lookup; + /// endpoint tokens, owner signatures, and raw hardware remain excluded. + pub member_pubkey: Option, + /// Relay event creation time, in Unix seconds. + pub reported_at: Option, + /// Approximate hosted-model footprint reported by MeshLLM, when available. + pub model_size_gb: Option, +} + +/// What the community's shared compute looks like right now. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] +#[serde(rename_all = "camelCase")] +pub struct MeshSnapshot { + /// Devices advertising at least one routable model. + pub sharing_device_count: usize, + /// Unique current members with at least one ready serving device. + pub contributor_member_count: usize, + /// Summed shared capacity across sharing devices. `None` when no sharing + /// device published a figure — the UI then shows a count with no GB, never + /// a misleading `0 GB`. + pub shared_capacity_gb: Option, + /// Summed model footprint only when every sharing device reports it. + /// `None` preserves mixed-version honesty instead of presenting a partial sum. + pub allocated_capacity_gb: Option, + /// Distinct models ready to use, deduped across devices. + pub models: Vec, + /// Per-device rows, for the topology view. Sharing devices first. + pub devices: Vec, + /// True when the local member's own device is among the sharing devices. + pub includes_self: bool, + /// Members in the community right now (NIP-43 roster size). + /// + /// The denominator for "N of M sharing". Deliberately NOT used to estimate + /// unshared capacity: a member who never starts a node publishes no status + /// note, so their hardware is unknown by design — they never consented to + /// disclose it. Ghost nodes in the topology are a count, never GB. + pub member_count: usize, + /// When this projection was evaluated, in Unix seconds. + pub observed_at: u64, + /// Maximum age of a relay-reported serving status. + pub freshness_seconds: u64, + /// Why the snapshot is empty, when it is. Never a hard error: an empty + /// mesh is a normal, expected state. + pub reason: Option, +} + +impl MeshSnapshot { + fn empty(reason: impl Into) -> Self { + Self { + observed_at: nostr::Timestamp::now().as_secs(), + freshness_seconds: STATUS_FRESHNESS_SECS, + reason: Some(reason.into()), + ..Default::default() + } + } +} + +fn f64_field(content: &serde_json::Value, key: &str) -> Option { + content + .get(key) + .and_then(serde_json::Value::as_f64) + .filter(|value| value.is_finite() && *value > 0.0) +} + +/// Coarse state from the published `node_state`, falling back to whether the +/// note actually advertises routable targets. +fn device_state(content: &serde_json::Value, has_targets: bool) -> MeshDeviceState { + let raw = content + .get("node_state") + .and_then(serde_json::Value::as_str) + .map(str::trim) + .unwrap_or_default() + .to_ascii_lowercase(); + match raw.as_str() { + "serving" if has_targets => MeshDeviceState::Serving, + "serving" => MeshDeviceState::Standby, + "loading" => MeshDeviceState::Loading, + "client" => MeshDeviceState::Consuming, + "standby" => MeshDeviceState::Standby, + // Unknown/absent: trust the routable evidence rather than guessing. + _ if has_targets => MeshDeviceState::Serving, + _ => MeshDeviceState::Standby, + } +} + +/// Project validated member status notes into a presentation snapshot. +/// +/// Applies the same trust rules as routing — current NIP-43 membership, owner +/// binding, endpoint binding, and [`status_is_fresh`] — so a stale or +/// non-member note can never inflate the headline. Pure and total so it can be +/// unit-tested without a relay. +pub(crate) fn snapshot_from_events( + events: Vec, + self_member_pubkey: Option<&str>, +) -> MeshSnapshot { + if events.is_empty() { + return MeshSnapshot::empty("No shared compute has been published yet"); + } + let Some(members) = latest_membership_list(&events) else { + return MeshSnapshot::empty("Waiting for the current member roster"); + }; + + let self_pubkey = self_member_pubkey.map(str::to_ascii_lowercase); + let now = nostr::Timestamp::now().as_secs(); + // Status notes are replaceable per owner id, so key on that: two devices + // signed in as the same member each publish their own note. + let mut by_owner = BTreeMap::::new(); + + let mut events = events; + events.sort_by(|a, b| { + a.created_at + .cmp(&b.created_at) + .then_with(|| b.id.cmp(&a.id)) + }); + for event in events { + if event.kind.as_u16() as u64 != MESH_STATUS_KIND + || !status_is_fresh(&event, now) + || !members.contains(&event.pubkey.to_hex().to_ascii_lowercase()) + { + continue; + } + let Ok(content) = serde_json::from_str::(&event.content) else { + continue; + }; + let Some(owner_id) = owner_id_from_status_event(&event) else { + continue; + }; + if !endpoint_binding_is_valid(&event, &content) { + continue; + } + + // Reuse the routing-side target parse so "sharing" here means exactly + // what it means to the router: a validated, advertisable endpoint. + let targets = content + .get("serveTargets") + .or_else(|| content.get("serve_targets")) + .cloned() + .and_then(|value| serde_json::from_value::>(value).ok()) + .unwrap_or_default() + .into_iter() + .filter(|target| { + super::transport_policy::validate_advertised_endpoint(&target.endpoint_addr).is_ok() + }) + .collect::>(); + + let mut models = targets + .iter() + .map(|target| target.model_id.clone()) + .collect::>(); + models.sort(); + models.dedup(); + + let endpoint_id = endpoint_id_from_status(&content, None); + let is_self = self_pubkey + .as_deref() + .is_some_and(|pubkey| event.pubkey.to_hex().to_ascii_lowercase() == pubkey); + + by_owner.insert( + owner_id, + MeshSnapshotDevice { + label: device_name_from_status(&content, endpoint_id.as_deref()) + .unwrap_or_else(|| "Unknown device".to_string()), + device_id: endpoint_id, + capacity_gb: f64_field(&content, "my_vram_gb"), + state: device_state(&content, !models.is_empty()), + models, + is_self, + member_pubkey: Some(event.pubkey.to_hex()), + reported_at: Some(event.created_at.as_secs()), + model_size_gb: f64_field(&content, "model_size_gb"), + }, + ); + } + + let mut devices = by_owner.into_values().collect::>(); + // Sharing devices first, then the local device, then by label so the list + // is stable across polls (no jitter in the topology view). + devices.sort_by(|a, b| { + let sharing = |d: &MeshSnapshotDevice| d.state == MeshDeviceState::Serving; + sharing(b) + .cmp(&sharing(a)) + .then_with(|| b.is_self.cmp(&a.is_self)) + .then_with(|| a.label.cmp(&b.label)) + }); + + let sharing = devices + .iter() + .filter(|device| device.state == MeshDeviceState::Serving) + .collect::>(); + let sharing_device_count = sharing.len(); + let contributor_member_count = sharing + .iter() + .filter_map(|device| device.member_pubkey.as_ref()) + .collect::>() + .len(); + let includes_self = sharing.iter().any(|device| device.is_self); + // Sum only reported figures; keep `None` when nobody reported one so the + // UI can drop the number instead of printing `0 GB`. + let shared_capacity_gb = sharing + .iter() + .filter_map(|device| device.capacity_gb) + .fold(None::, |acc, gb| Some(acc.unwrap_or(0.0) + gb)); + let allocated_capacity_gb = (!sharing.is_empty() + && sharing.iter().all(|device| device.model_size_gb.is_some())) + .then(|| { + sharing + .iter() + .filter_map(|device| device.model_size_gb) + .sum() + }); + let mut models = sharing + .iter() + .flat_map(|device| device.models.iter().cloned()) + .collect::>(); + models.sort(); + models.dedup(); + + let reason = if sharing_device_count == 0 { + Some("No one is sharing compute yet".to_string()) + } else { + None + }; + + MeshSnapshot { + member_count: members.len(), + contributor_member_count, + sharing_device_count, + shared_capacity_gb, + allocated_capacity_gb, + models, + devices, + includes_self, + observed_at: now, + freshness_seconds: STATUS_FRESHNESS_SECS, + reason, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn empty_events_report_a_reason_not_an_error() { + let snapshot = snapshot_from_events(Vec::new(), None); + assert_eq!(snapshot.sharing_device_count, 0); + assert_eq!(snapshot.shared_capacity_gb, None); + assert!(snapshot.reason.is_some()); + assert_eq!(snapshot.freshness_seconds, STATUS_FRESHNESS_SECS); + assert!(snapshot.observed_at > 0); + } + + #[test] + fn missing_membership_snapshot_is_not_treated_as_an_empty_mesh() { + // A relay gap must not read as "nobody is sharing". + let event = nostr::EventBuilder::new(nostr::Kind::Custom(30003), "{}") + .sign_with_keys(&nostr::Keys::generate()) + .expect("test event signs"); + let snapshot = snapshot_from_events(vec![event], None); + assert_eq!( + snapshot.reason.as_deref(), + Some("Waiting for the current member roster") + ); + } + + #[test] + fn unknown_node_state_falls_back_to_routable_evidence() { + assert_eq!( + device_state(&serde_json::json!({}), true), + MeshDeviceState::Serving + ); + assert_eq!( + device_state(&serde_json::json!({}), false), + MeshDeviceState::Standby + ); + assert_eq!( + device_state(&serde_json::json!({"node_state": "client"}), false), + MeshDeviceState::Consuming + ); + } + + #[test] + fn capacity_ignores_zero_and_nonfinite_reports() { + // A node that has not surveyed yet publishes 0.0; that must not count + // as a real reported capacity. + assert_eq!( + f64_field(&serde_json::json!({"my_vram_gb": 0.0}), "my_vram_gb"), + None + ); + assert_eq!( + f64_field(&serde_json::json!({"my_vram_gb": 36.5}), "my_vram_gb"), + Some(36.5) + ); + } +} diff --git a/crates/community-compute/src/tests.rs b/crates/community-compute/src/tests.rs new file mode 100644 index 000000000..5b36215c4 --- /dev/null +++ b/crates/community-compute/src/tests.rs @@ -0,0 +1,181 @@ +use super::*; +use mesh_llm_host_runtime::crypto::OwnerKeypair; +use nostr::{Event, EventBuilder, Keys, Kind, Tag, Timestamp}; +use serde_json::{json, Value}; + +fn event(keys: &Keys, kind: u16, content: Value, tags: Vec>, age: u64) -> Event { + EventBuilder::new(Kind::Custom(kind), content.to_string()) + .tags(tags.into_iter().map(|tag| Tag::parse(tag).unwrap())) + .custom_created_at(Timestamp::from_secs(Timestamp::now().as_secs() - age)) + .sign_with_keys(keys) + .unwrap() +} +fn roster(authority: &Keys, members: &[&Keys]) -> Event { + event( + authority, + 13534, + json!(null), + members + .iter() + .map(|m| vec!["member".into(), m.public_key().to_hex()]) + .collect(), + 0, + ) +} +fn payload(owner: &OwnerKeypair, member: &Keys, targets: bool) -> Value { + let endpoint = transport_policy::endpoint_token_for_test([iroh::TransportAddr::Ip( + "192.168.1.20:47916".parse().unwrap(), + )]); + let tokens = if targets { + vec![endpoint.clone()] + } else { + vec![] + }; + let member = member.public_key().to_hex(); + json!({ + "ownerId": owner.owner_id(), + "ownerVerifyingKey": hex::encode(owner.verifying_key().as_bytes()), + "ownerBindingSig": hex::encode(owner.sign_bytes(&identity::member_binding_bytes(&member))), + "ownerEndpointBindingSig": hex::encode(owner.sign_bytes(&identity::member_endpoint_binding_bytes(&member, &tokens))), + "serveTargets": if targets { json!([{"modelId":"local-model", "endpointAddr":endpoint}]) } else { json!([]) }, + "node_state": "serving", "my_vram_gb":8.0, "model_size_gb":4.0, + "deviceName": "Test device", "gpus":[{"vram_bytes":999999999999_u64}], + "privateField": "must not be projected" + }) +} +fn status(member: &Keys, payload: Value, age: u64) -> Event { + event( + member, + 30003, + payload, + vec![vec!["k".into(), "buzz-mesh-status".into()]], + age, + ) +} +fn project_for(events: Vec, authority: &Keys, member: &Keys) -> MeshSnapshot { + project( + events, + &authority.public_key().to_hex(), + &member.public_key().to_hex(), + ) + .unwrap() +} +#[test] +fn verifies_both_bindings_and_projects_only_capped_display_fields() { + let authority = Keys::generate(); + let member = Keys::generate(); + let owner = OwnerKeypair::generate(); + let result = project_for( + vec![ + roster(&authority, &[&member]), + status(&member, payload(&owner, &member, true), 0), + ], + &authority, + &member, + ); + assert_eq!(result.sharing_device_count, 1); + assert_eq!(result.shared_capacity_gb, Some(8.0)); + assert_eq!(result.allocated_capacity_gb, Some(4.0)); + assert!(result.includes_self); + let serialized = serde_json::to_string(&result).unwrap(); + for private in [ + "ownerBindingSig", + "ownerVerifyingKey", + "endpointAddr", + "gpus", + "privateField", + ] { + assert!(!serialized.contains(private)); + } +} +#[test] +fn ignores_nonmembers_stale_notes_future_notes_and_tampered_owner_bindings() { + let authority = Keys::generate(); + let member = Keys::generate(); + let outsider = Keys::generate(); + let owner = OwnerKeypair::generate(); + let valid = payload(&owner, &member, true); + let mut bad_owner = valid.clone(); + bad_owner["ownerId"] = json!("00".repeat(32)); + let mut bad_binding = valid.clone(); + bad_binding["ownerBindingSig"] = json!("00".repeat(64)); + let mut bad_endpoint = valid.clone(); + bad_endpoint["serveTargets"][0]["endpointAddr"] = json!("substituted"); + let future = EventBuilder::new(Kind::Custom(30003), valid.to_string()) + .tags([Tag::parse(["k", "buzz-mesh-status"]).unwrap()]) + .custom_created_at(Timestamp::from_secs(Timestamp::now().as_secs() + 1000)) + .sign_with_keys(&member) + .unwrap(); + for invalid in [ + status(&outsider, payload(&owner, &outsider, true), 0), + status(&member, valid, 121), + status(&member, bad_owner, 0), + status(&member, bad_binding, 0), + status(&member, bad_endpoint, 0), + future, + ] { + let result = project_for( + vec![roster(&authority, &[&member]), invalid], + &authority, + &member, + ); + assert_eq!(result.sharing_device_count, 0); + } +} +#[test] +fn rejects_forged_rosters_and_event_signatures() { + let authority = Keys::generate(); + let member = Keys::generate(); + assert!(project( + vec![roster(&member, &[&member])], + &authority.public_key().to_hex(), + &member.public_key().to_hex() + ) + .is_err()); + let mut forged = roster(&authority, &[&member]); + forged.content.push('x'); + assert!(project( + vec![forged], + &authority.public_key().to_hex(), + &member.public_key().to_hex() + ) + .is_err()); +} +#[test] +fn latest_stopped_note_wins_regardless_of_input_order_and_serving_label_needs_targets() { + let authority = Keys::generate(); + let member = Keys::generate(); + let owner = OwnerKeypair::generate(); + let old = status(&member, payload(&owner, &member, true), 10); + let stopped = status(&member, payload(&owner, &member, false), 0); + for notes in [ + vec![old.clone(), stopped.clone()], + vec![stopped.clone(), old.clone()], + ] { + let mut events = vec![roster(&authority, &[&member])]; + events.extend(notes); + let result = project_for(events, &authority, &member); + assert_eq!(result.devices.len(), 1); + assert_eq!(result.sharing_device_count, 0); + } +} +#[test] +fn counts_devices_separately_but_members_once_and_keeps_unknown_allocation_unknown() { + let authority = Keys::generate(); + let member = Keys::generate(); + let first = payload(&OwnerKeypair::generate(), &member, true); + let mut second = payload(&OwnerKeypair::generate(), &member, true); + second.as_object_mut().unwrap().remove("model_size_gb"); + let result = project_for( + vec![ + roster(&authority, &[&member]), + status(&member, first, 0), + status(&member, second, 0), + ], + &authority, + &member, + ); + assert_eq!(result.sharing_device_count, 2); + assert_eq!(result.contributor_member_count, 1); + assert_eq!(result.allocated_capacity_gb, None); +} diff --git a/crates/community-compute/src/transport_policy.rs b/crates/community-compute/src/transport_policy.rs new file mode 100644 index 000000000..9ab3be253 --- /dev/null +++ b/crates/community-compute/src/transport_policy.rs @@ -0,0 +1,490 @@ +use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; +use iroh::{EndpointAddr, RelayUrl, TransportAddr}; +use mesh_llm_host_runtime::SignedBootstrapToken; + +use super::MESH_IROH_RELAYS_ENV; + +const MAX_INVITE_TOKEN_LEN: usize = 64 * 1024; +const MAX_BOOTSTRAP_ADDRS: usize = 8; +const MAX_ENDPOINT_TRANSPORT_ADDRS: usize = 16; + +/// Locally configured iroh relay policy. Remote discovery may only advertise +/// relay URLs that this node was already configured to contact. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(super) enum IrohRelayMode { + /// Direct QUIC only; advertised endpoint tokens must not contain relays. + Disabled, + /// Iroh's production relay set, enabled by default for NAT traversal. + Default, + /// An explicit, locally configured relay allowlist. + Custom(Vec), +} + +pub(super) fn iroh_relay_mode() -> anyhow::Result { + iroh_relay_mode_from(std::env::var(MESH_IROH_RELAYS_ENV).ok().as_deref()) +} + +pub(super) fn iroh_relay_mode_from(raw: Option<&str>) -> anyhow::Result { + match raw.map(str::trim) { + Some("0") => Ok(IrohRelayMode::Disabled), + None | Some("") | Some("1") | Some("default") => Ok(IrohRelayMode::Default), + Some(list) => { + let urls = list + .split(',') + .map(str::trim) + .filter(|url| !url.is_empty()) + .map(parse_configured_relay_url) + .collect::>>()?; + if urls.is_empty() { + anyhow::bail!("{MESH_IROH_RELAYS_ENV} must contain at least one relay URL"); + } + Ok(IrohRelayMode::Custom(urls)) + } + } +} + +pub(super) fn parse_configured_relay_url(raw: &str) -> anyhow::Result { + let parsed = url::Url::parse(raw) + .map_err(|error| anyhow::anyhow!("invalid relay URL {raw:?}: {error}"))?; + let secure = parsed.scheme() == "https"; + let local_http = parsed.scheme() == "http" + && parsed.host().is_some_and(|host| match host { + url::Host::Domain(domain) => domain.eq_ignore_ascii_case("localhost"), + url::Host::Ipv4(ip) => ip.is_loopback(), + url::Host::Ipv6(ip) => ip.is_loopback(), + }); + if !secure && !local_http { + anyhow::bail!( + "relay URL {raw:?} must use https (http is allowed only for loopback development)" + ); + } + if !parsed.username().is_empty() + || parsed.password().is_some() + || parsed.query().is_some() + || parsed.fragment().is_some() + || !matches!(parsed.path(), "" | "/") + { + anyhow::bail!( + "relay URL {raw:?} must be an origin without credentials, path, query, or fragment" + ); + } + raw.parse::() + .map_err(|error| anyhow::anyhow!("invalid iroh relay URL {raw:?}: {error}")) +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(super) struct ValidatedEndpoint { + pub endpoint_id: String, + pub join_token: String, +} + +pub(super) fn validate_advertised_endpoint( + invite_token: &str, +) -> anyhow::Result { + let mode = iroh_relay_mode()?; + validate_advertised_endpoint_with_mode(invite_token, &mode) +} + +pub(super) fn validate_advertised_endpoint_with_mode( + invite_token: &str, + mode: &IrohRelayMode, +) -> anyhow::Result { + let token = invite_token.trim(); + if token.is_empty() { + anyhow::bail!("mesh invite token is empty"); + } + if token.len() > MAX_INVITE_TOKEN_LEN { + anyhow::bail!("mesh invite token exceeds {MAX_INVITE_TOKEN_LEN} bytes"); + } + let payload = URL_SAFE_NO_PAD + .decode(token) + .map_err(|error| anyhow::anyhow!("invalid mesh invite encoding: {error}"))?; + + if let Ok(mut addr) = serde_json::from_slice::(&payload) { + retain_usable_transports(&mut addr, mode)?; + let endpoint_id = addr.id.to_string(); + let join_token = URL_SAFE_NO_PAD.encode(serde_json::to_vec(&addr)?); + return Ok(ValidatedEndpoint { + endpoint_id, + join_token, + }); + } + + let signed = serde_json::from_slice::(&payload) + .map_err(|error| anyhow::anyhow!("invalid mesh invite payload: {error}"))?; + signed + .verify() + .map_err(|reason| anyhow::anyhow!("invalid signed mesh invite: {}", reason.code()))?; + if signed.serialized_addrs.is_empty() || signed.serialized_addrs.len() > MAX_BOOTSTRAP_ADDRS { + anyhow::bail!( + "signed mesh invite must contain 1..={MAX_BOOTSTRAP_ADDRS} endpoint addresses" + ); + } + let addrs = signed + .serialized_addrs + .iter() + .map(|bytes| { + serde_json::from_slice::(bytes) + .map_err(|error| anyhow::anyhow!("invalid signed endpoint address: {error}")) + }) + .collect::>>()?; + + // Rewriting a signed token would invalidate its signature. Keep the signed + // envelope intact only when every advertised transport is policy-approved; + // mixed signed tokens fail closed rather than leaking rejected dial targets. + for addr in &addrs { + validate_signed_transports(addr, mode)?; + } + Ok(ValidatedEndpoint { + endpoint_id: addrs[0].id.to_string(), + join_token: token.to_string(), + }) +} + +fn retain_usable_transports(addr: &mut EndpointAddr, mode: &IrohRelayMode) -> anyhow::Result<()> { + validate_transport_count(addr)?; + let mut rejections = Vec::new(); + addr.addrs + .retain(|transport| match validate_transport(transport, mode) { + Ok(()) => true, + Err(error) => { + rejections.push(error.to_string()); + false + } + }); + if addr.addrs.is_empty() { + anyhow::bail!( + "mesh endpoint has no usable transport address (all rejected: {})", + rejections.join("; ") + ); + } + Ok(()) +} + +fn validate_signed_transports(addr: &EndpointAddr, mode: &IrohRelayMode) -> anyhow::Result<()> { + validate_transport_count(addr)?; + let mut usable = 0usize; + for transport in &addr.addrs { + match validate_transport(transport, mode) { + Ok(()) => usable += 1, + // mesh-llm currently signs its own port-0 placeholder alongside a + // valid relay. It is non-dialable, so preserving it is safe and + // necessary for stock signed tokens to remain usable. + Err(_) if matches!(transport, TransportAddr::Ip(socket) if socket.port() == 0) => {} + Err(error) => return Err(error), + } + } + if usable == 0 { + anyhow::bail!("signed mesh endpoint has no usable transport address"); + } + Ok(()) +} + +fn validate_transport_count(addr: &EndpointAddr) -> anyhow::Result<()> { + if addr.addrs.is_empty() || addr.addrs.len() > MAX_ENDPOINT_TRANSPORT_ADDRS { + anyhow::bail!( + "mesh endpoint must contain 1..={MAX_ENDPOINT_TRANSPORT_ADDRS} transport addresses" + ); + } + Ok(()) +} + +fn validate_transport(transport: &TransportAddr, mode: &IrohRelayMode) -> anyhow::Result<()> { + match transport { + TransportAddr::Relay(relay) if relay_allowed(relay, mode) => Ok(()), + TransportAddr::Relay(relay) => anyhow::bail!("unapproved relay URL {relay}"), + TransportAddr::Ip(socket) => validate_direct_socket(*socket), + _ => anyhow::bail!("unsupported transport address"), + } +} + +/// mesh-llm's default public relay set (`RelayPolicy::DefaultPublic` in +/// `mesh-llm-host-runtime`). A stock mesh-llm server with no custom relay +/// config advertises endpoints on exactly these relays, so buzz's `Default` +/// mode MUST accept them — otherwise shared compute rejects every out-of-the-box +/// mesh-llm serving node (they are not in iroh's own prod relay map). +/// +/// Kept in sync with `effective_relay_urls(RelayPolicy::DefaultPublic, &[])`. +pub(super) const MESH_LLM_DEFAULT_RELAYS: &[&str] = &[ + "https://usw1-2.relay.michaelneale.mesh-llm.iroh.link./", + "https://aps1-1.relay.michaelneale.mesh-llm.iroh.link./", + "https://euc1-1.relay.michaelneale.mesh-llm.iroh.link./", + "https://use1-1.relay.michaelneale.mesh-llm.iroh.link./", +]; + +/// Whether `relay` is one of mesh-llm's baked-in default public relays. +/// Parses each known URL to a `RelayUrl` so comparison is normalization-safe +/// (matches regardless of trailing-dot / trailing-slash formatting). +fn is_mesh_llm_default_relay(relay: &RelayUrl) -> bool { + MESH_LLM_DEFAULT_RELAYS.iter().any(|candidate| { + candidate + .parse::() + .map(|known| &known == relay) + .unwrap_or(false) + }) +} + +fn relay_allowed(relay: &RelayUrl, mode: &IrohRelayMode) -> bool { + match mode { + IrohRelayMode::Disabled => false, + // `Default` covers both iroh's own production relays AND mesh-llm's + // default public relays. Without the latter, a stock mesh-llm serving + // node is unreachable by default and shared compute silently fails with + // "no live member is serving this model" even though discovery found it. + IrohRelayMode::Default => { + iroh::defaults::prod::default_relay_map().contains(relay) + || is_mesh_llm_default_relay(relay) + } + IrohRelayMode::Custom(urls) => urls.contains(relay), + } +} + +fn validate_direct_socket(socket: std::net::SocketAddr) -> anyhow::Result<()> { + let ip = socket.ip(); + let unsafe_target = socket.port() == 0 + || ip.is_unspecified() + || ip.is_loopback() + || ip.is_multicast() + || match ip { + std::net::IpAddr::V4(ip) => ip.is_link_local() || ip.is_broadcast(), + std::net::IpAddr::V6(ip) => ip.is_unicast_link_local(), + }; + if unsafe_target { + anyhow::bail!("mesh endpoint advertises unsafe direct address {socket}"); + } + Ok(()) +} + +#[cfg(test)] +pub(super) fn endpoint_token_for_test( + transports: impl IntoIterator, +) -> String { + let mut addr = EndpointAddr::new(iroh::SecretKey::generate().public()); + addr.addrs.extend(transports); + URL_SAFE_NO_PAD.encode(serde_json::to_vec(&addr).expect("serialize test endpoint")) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn configured_relays_require_safe_origins() { + assert!(iroh_relay_mode_from(Some("https://relay.example")).is_ok()); + assert!(iroh_relay_mode_from(Some("http://127.0.0.1:3340")).is_ok()); + for invalid in [ + "http://relay.example", + "https://user@relay.example", + "https://relay.example/path", + "https://relay.example?token=secret", + ] { + assert!( + iroh_relay_mode_from(Some(invalid)).is_err(), + "accepted unsafe relay {invalid}" + ); + } + } + + #[test] + fn remote_relay_must_match_local_policy() { + let allowed: RelayUrl = "https://relay.example".parse().unwrap(); + let other: RelayUrl = "https://other.example".parse().unwrap(); + let token = endpoint_token_for_test([TransportAddr::Relay(allowed.clone())]); + assert!(validate_advertised_endpoint_with_mode( + &token, + &IrohRelayMode::Custom(vec![allowed]) + ) + .is_ok()); + assert!(validate_advertised_endpoint_with_mode( + &token, + &IrohRelayMode::Custom(vec![other]) + ) + .is_err()); + assert!(validate_advertised_endpoint_with_mode(&token, &IrohRelayMode::Disabled).is_err()); + } + + #[test] + fn default_mode_accepts_meshllm_default_relays() { + // Regression: a stock mesh-llm serving node advertises endpoints on + // mesh-llm's OWN default public relays (not iroh's prod relay map). + // Under `Default` mode these MUST be accepted, or shared compute rejects + // every out-of-the-box mesh-llm server with "no live member is serving + // this model" even though discovery found it. See mesh-llm + // effective_relay_urls(RelayPolicy::DefaultPublic, &[]). + for relay_url in MESH_LLM_DEFAULT_RELAYS { + let relay: RelayUrl = relay_url + .parse() + .unwrap_or_else(|e| panic!("mesh-llm default relay {relay_url:?} must parse: {e}")); + assert!( + relay_allowed(&relay, &IrohRelayMode::Default), + "Default mode must accept mesh-llm default relay {relay_url}" + ); + + // And end-to-end through the advertised-endpoint validator. + let token = endpoint_token_for_test([TransportAddr::Relay(relay)]); + assert!( + validate_advertised_endpoint_with_mode(&token, &IrohRelayMode::Default).is_ok(), + "Default mode must validate an endpoint on mesh-llm default relay {relay_url}" + ); + } + } + + #[test] + fn endpoint_with_one_good_and_one_junk_candidate_is_sanitized() { + // A mesh-llm endpoint can advertise a usable relay alongside an + // unusable direct IP. Keep the endpoint reachable, but never pass the + // rejected candidate through to iroh's parallel dialer. + let good_relay: RelayUrl = MESH_LLM_DEFAULT_RELAYS[0].parse().unwrap(); + let unsafe_socket = "169.254.169.254:80".parse().unwrap(); + let token = endpoint_token_for_test([ + TransportAddr::Relay(good_relay.clone()), + TransportAddr::Ip(unsafe_socket), + ]); + let validated = + validate_advertised_endpoint_with_mode(&token, &IrohRelayMode::Default).unwrap(); + let payload = URL_SAFE_NO_PAD.decode(validated.join_token).unwrap(); + let sanitized: EndpointAddr = serde_json::from_slice(&payload).unwrap(); + assert_eq!( + sanitized.addrs, + [TransportAddr::Relay(good_relay)].into_iter().collect() + ); + } + + #[test] + fn endpoint_with_all_junk_candidates_is_rejected() { + // Guard: if EVERY candidate is unusable, the endpoint must still fail. + let token = endpoint_token_for_test([ + TransportAddr::Ip("180.181.228.108:0".parse().unwrap()), // port 0 + TransportAddr::Ip("127.0.0.1:9337".parse().unwrap()), // loopback + ]); + assert!( + validate_advertised_endpoint_with_mode(&token, &IrohRelayMode::Default).is_err(), + "endpoint with no usable candidate must be rejected" + ); + } + + #[test] + fn default_mode_still_rejects_unknown_relay() { + // Guard the fix doesn't over-open: a relay that is neither iroh-prod nor + // a mesh-llm default must still be rejected under Default mode. + let unknown: RelayUrl = "https://not-a-real-relay.example".parse().unwrap(); + assert!(!relay_allowed(&unknown, &IrohRelayMode::Default)); + let token = endpoint_token_for_test([TransportAddr::Relay(unknown)]); + assert!(validate_advertised_endpoint_with_mode(&token, &IrohRelayMode::Default).is_err()); + } + + #[test] + fn remote_endpoint_rejects_unsafe_direct_targets_and_oversized_tokens() { + for socket in ["127.0.0.1:9337", "169.254.169.254:80", "0.0.0.0:1"] { + let token = endpoint_token_for_test([TransportAddr::Ip(socket.parse().unwrap())]); + assert!( + validate_advertised_endpoint_with_mode(&token, &IrohRelayMode::Default).is_err(), + "accepted unsafe target {socket}" + ); + } + let valid = + endpoint_token_for_test([TransportAddr::Ip("192.168.1.20:47916".parse().unwrap())]); + assert!(validate_advertised_endpoint_with_mode(&valid, &IrohRelayMode::Default).is_ok()); + assert!(validate_advertised_endpoint_with_mode( + &"a".repeat(MAX_INVITE_TOKEN_LEN + 1), + &IrohRelayMode::Default + ) + .is_err()); + } + + #[test] + fn accepts_verified_meshllm_signed_bootstrap_token_and_rejects_tampering() { + use mesh_llm_host_runtime::crypto::OwnerKeypair; + use mesh_llm_host_runtime::{ + MeshGenesisPolicy, MeshRequirements, SignedBootstrapToken, SignedMeshGenesisPolicy, + }; + + let owner = OwnerKeypair::generate(); + let policy = MeshGenesisPolicy::new( + owner.owner_id(), + 1_717_171_717_000, + MeshRequirements::default(), + ) + .expect("create test mesh policy"); + let signed_policy = + SignedMeshGenesisPolicy::sign(policy, &owner).expect("sign test mesh policy"); + let endpoint = EndpointAddr { + id: iroh::SecretKey::generate().public(), + addrs: [TransportAddr::Ip("192.168.1.20:47916".parse().unwrap())] + .into_iter() + .collect(), + }; + let signed = SignedBootstrapToken::sign( + vec![serde_json::to_vec(&endpoint).unwrap()], + &signed_policy, + None, + &owner, + ) + .expect("sign test bootstrap token"); + let token = URL_SAFE_NO_PAD.encode(serde_json::to_vec(&signed).unwrap()); + assert_eq!( + validate_advertised_endpoint_with_mode(&token, &IrohRelayMode::Default) + .unwrap() + .endpoint_id, + endpoint.id.to_string() + ); + + let good_relay: RelayUrl = MESH_LLM_DEFAULT_RELAYS[0].parse().unwrap(); + let placeholder_endpoint = EndpointAddr { + id: endpoint.id, + addrs: [ + TransportAddr::Relay(good_relay.clone()), + TransportAddr::Ip("180.181.228.108:0".parse().unwrap()), + ] + .into_iter() + .collect(), + }; + let placeholder_signed = SignedBootstrapToken::sign( + vec![serde_json::to_vec(&placeholder_endpoint).unwrap()], + &signed_policy, + None, + &owner, + ) + .expect("sign placeholder test bootstrap token"); + let placeholder_token = + URL_SAFE_NO_PAD.encode(serde_json::to_vec(&placeholder_signed).unwrap()); + assert!( + validate_advertised_endpoint_with_mode(&placeholder_token, &IrohRelayMode::Default) + .is_ok(), + "signed stock token with a port-0 placeholder must remain usable" + ); + + let mixed_endpoint = EndpointAddr { + id: endpoint.id, + addrs: [ + TransportAddr::Relay(good_relay), + TransportAddr::Ip("169.254.169.254:80".parse().unwrap()), + ] + .into_iter() + .collect(), + }; + let mixed_signed = SignedBootstrapToken::sign( + vec![serde_json::to_vec(&mixed_endpoint).unwrap()], + &signed_policy, + None, + &owner, + ) + .expect("sign mixed test bootstrap token"); + let mixed_token = URL_SAFE_NO_PAD.encode(serde_json::to_vec(&mixed_signed).unwrap()); + assert!( + validate_advertised_endpoint_with_mode(&mixed_token, &IrohRelayMode::Default).is_err(), + "signed mixed-candidate tokens must fail closed because they cannot be rewritten" + ); + + let mut tampered = signed; + tampered.serialized_addrs[0] = serde_json::to_vec(&EndpointAddr { + id: endpoint.id, + addrs: [TransportAddr::Ip("192.168.1.21:47916".parse().unwrap())] + .into_iter() + .collect(), + }) + .unwrap(); + let token = URL_SAFE_NO_PAD.encode(serde_json::to_vec(&tampered).unwrap()); + assert!(validate_advertised_endpoint_with_mode(&token, &IrohRelayMode::Default).is_err()); + } +} diff --git a/crates/community-compute/src/usage.rs b/crates/community-compute/src/usage.rs new file mode 100644 index 000000000..3e5277836 --- /dev/null +++ b/crates/community-compute/src/usage.rs @@ -0,0 +1,49 @@ +//! Local session counters adapted from block/buzz mesh_llm/usage.rs. +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Usage { + pub tokens_served: u64, + pub inflight: u64, + pub tokens_per_second: Option, + pub peers: Option, +} +impl Usage { + // Missing counters mean unavailable, not a fabricated zero-token session. + pub fn from_payload(payload: &Value) -> Option { + let metrics = payload.get("routing_metrics")?; + Some(Self { + tokens_served: metrics.get("completion_tokens_observed")?.as_u64()?, + inflight: metrics + .pointer("/local_node/current_inflight_requests") + .or_else(|| payload.get("inflight_requests"))? + .as_u64()?, + tokens_per_second: metrics + .get("avg_tokens_per_second") + .and_then(Value::as_f64) + .filter(|v| v.is_finite() && *v >= 0.0), + peers: payload.get("peers").and_then(Value::as_array).map(Vec::len), + }) + } +} +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + #[test] + fn projects_local_counters_without_inventing_missing_usage() { + assert!(Usage::from_payload(&json!({})).is_none()); + let payload = json!({"routing_metrics":{"completion_tokens_observed":123,"avg_tokens_per_second":-1,"local_node":{"current_inflight_requests":2}},"peers":[{},{}],"private":"not projected"}); + let usage = Usage::from_payload(&payload).unwrap(); + assert_eq!(usage.tokens_served, 123); + assert_eq!(usage.inflight, 2); + assert_eq!(usage.peers, Some(2)); + assert_eq!(usage.tokens_per_second, None); + assert!(serde_json::to_value(usage) + .unwrap() + .get("private") + .is_none()); + } +} diff --git a/crates/community-compute/src/worker.rs b/crates/community-compute/src/worker.rs new file mode 100644 index 000000000..6983085a8 --- /dev/null +++ b/crates/community-compute/src/worker.rs @@ -0,0 +1,548 @@ +//! Dedicated worker process: SDK threads and native libraries die with this +//! process, so cancelling a download/start never strands a listener in the app. +use crate::{ + broker::Broker, + discovery, + identity::{self, OwnerIdentity}, + transport_policy, +}; +use anyhow::{bail, Context}; +use mesh_llm_events::{ConsoleSessionMode, OutputEvent, OutputSink}; +use mesh_llm_sdk::{client, serve, EmbeddedNodeHandle, MeshDiscoveryMode, TrustPolicy}; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; +use std::{ + io::{BufRead, Read, Write}, + sync::Arc, + time::Duration, +}; + +pub const WORKER_ARG: &str = "--buzz-community-compute-worker"; +pub const OUTPUT_PREFIX: &str = "BUZZ_COMPUTE "; +#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum Mode { + #[default] + Serve, + Client, +} +impl Mode { + pub fn as_str(self) -> &'static str { + match self { + Self::Serve => "serve", + Self::Client => "client", + } + } +} +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct StartRequest { + #[serde(default)] + pub mode: Mode, + pub community: String, + pub viewer: String, + pub model_id: String, + pub max_vram_gb: Option, +} +impl StartRequest { + pub fn validate(&self) -> anyhow::Result<()> { + nostr::PublicKey::from_hex(&self.viewer)?; + let url = url::Url::parse(&self.community)?; + if !matches!(url.scheme(), "https" | "wss") + && !(matches!(url.scheme(), "http" | "ws") + && matches!(url.host_str(), Some("localhost" | "127.0.0.1" | "[::1]"))) + { + bail!("Select a secure community URL"); + } + if url.host_str().is_none() + || !url.username().is_empty() + || url.password().is_some() + || url.query().is_some() + || url.fragment().is_some() + || url.path() != "/" + { + bail!("Invalid community URL"); + } + if self.model_id.trim().is_empty() + || self.model_id.len() > 512 + || self.model_id.chars().any(char::is_control) + || self.model_id.trim() == "auto" + { + bail!("Choose a local model to share"); + } + if self + .max_vram_gb + .is_some_and(|gb| !gb.is_finite() || gb <= 0.0 || gb > 100000.0) + { + bail!("Memory limit must be positive"); + } + Ok(()) + } +} +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct WorkerInput { + pub api_port: u16, + pub console_port: u16, + pub broker_origin: String, + pub owner_key: std::path::PathBuf, + pub request: StartRequest, +} +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Download { + pub received: u64, + pub total: Option, +} +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Update { + #[serde(default)] + pub usage_sample: bool, + #[serde(default)] + pub usage: Option, + pub state: String, + pub detail: Option, + pub download: Option, +} +fn emit(update: Update) { + if let Ok(json) = serde_json::to_string(&update) { + let mut out = std::io::stdout().lock(); + let _ = writeln!(out, "{OUTPUT_PREFIX}{json}"); + let _ = out.flush(); + } +} +fn state(value: &str, detail: &str) { + emit(Update { + usage_sample: false, + usage: None, + state: value.into(), + detail: Some(detail.into()), + download: None, + }); +} +struct Progress; +impl OutputSink for Progress { + fn emit_event(&self, event: OutputEvent) -> std::io::Result<()> { + if let OutputEvent::ModelDownloadProgress { + downloaded_bytes, + total_bytes, + .. + } = event + { + emit(Update { + usage_sample: false, + usage: None, + state: "starting".into(), + detail: Some("Downloading model or native runtime…".into()), + download: Some(Download { + received: downloaded_bytes.unwrap_or(0), + total: total_bytes, + }), + }); + } + Ok(()) + } + fn console_session_mode(&self) -> Option { + Some(ConsoleSessionMode::InteractiveDashboard) + } +} +fn bind(owner: &OwnerIdentity, viewer: &str, mut payload: Value) -> anyhow::Result { + let tokens = identity::advertised_endpoint_tokens(&payload) + .context("Malformed local compute targets")?; + payload["ownerId"] = json!(owner.owner_id); + payload["ownerVerifyingKey"] = json!(owner.verifying_key_hex); + payload["ownerBindingSig"] = json!(owner.sign_member_binding(viewer)?); + payload["ownerEndpointBindingSig"] = + json!(owner.sign_member_endpoint_binding(viewer, &tokens)?); + Ok(payload) +} +fn idle(owner: &OwnerIdentity, viewer: &str) -> anyhow::Result { + bind( + owner, + viewer, + json!({"serveTargets":[],"models":[],"node_state":"standby"}), + ) +} +fn owners(events: &[nostr::Event], self_owner: &str) -> Vec { + let members = discovery::latest_membership_list(events).unwrap_or_default(); + let mut owners = events + .iter() + .filter(|e| { + e.kind.as_u16() == 30003 + && members.contains(&e.pubkey.to_hex()) + && e.created_at <= nostr::Timestamp::now() + }) + .filter_map(discovery::owner_id_from_status_event) + .collect::>(); + owners.push(self_owner.into()); + owners.sort(); + owners.dedup(); + owners +} +fn mesh_name(relay: &str) -> String { + use sha2::{Digest, Sha256}; + let normalized = relay + .replacen("https://", "wss://", 1) + .trim_end_matches('/') + .to_string(); + let digest = hex::encode(Sha256::digest(normalized.as_bytes())); + format!("buzz-community-{}", &digest[..32]) +} +fn preflight(api: u16, console: u16) -> anyhow::Result<()> { + if api == 0 || console == 0 || api == console { + bail!("Compute ports must be distinct nonzero ports"); + } + // Bind both simultaneously and drop only after both are proven available. + let _api = std::net::TcpListener::bind(("127.0.0.1", api)) + .with_context(|| format!("Compute API port {api} is already in use"))?; + let _console = std::net::TcpListener::bind(("127.0.0.1", console)) + .with_context(|| format!("Compute console port {console} is already in use"))?; + Ok(()) +} +fn status_payload( + status: mesh_llm_sdk::EmbeddedNodeStatus, + owner: &OwnerIdentity, + viewer: &str, +) -> anyhow::Result { + let models = models::models_from_status_payload(Some(&status.payload)); + let targets = if let Some(token) = status + .invite_token + .filter(|token| transport_policy::validate_advertised_endpoint(token).is_ok()) + { + models + .iter() + .map(|model| json!({"modelId":model.id,"endpointAddr":token})) + .collect::>() + } else { + vec![] + }; + let mut payload = json!({"serveTargets":targets,"models":models,"node_state":if targets.is_empty(){"loading"}else{"serving"}}); + for field in ["my_vram_gb", "model_size_gb"] { + if let Some(value) = status.payload[field] + .as_f64() + .filter(|v| v.is_finite() && *v > 0.0) + { + payload[field] = json!(value); + } + } + if let Some(label) = discovery::device_name_from_status(&status.payload, None) { + payload["deviceName"] = json!(label.chars().take(128).collect::()); + } + bind(owner, viewer, payload) +} +async fn fresh_evidence(broker: &Broker) -> anyhow::Result> { + tokio::time::timeout(Duration::from_secs(12), broker.evidence()) + .await + .context("Community admission refresh timed out; sharing stopped")? +} +async fn run( + input: WorkerInput, + cleanup: Arc>>, +) -> anyhow::Result<()> { + input.request.validate()?; + preflight(input.api_port, input.console_port)?; + let broker = Broker::connect(&input.broker_origin, &input.request).await?; + fresh_evidence(&broker).await?; // Membership before keys, downloads, or publication. + let owner = identity::ensure_owner_identity_at(input.owner_key)?; + *cleanup.lock().await = Some((broker.clone(), owner.clone())); + broker.publish(idle(&owner, &broker.viewer)?).await?; + if input.request.mode == Mode::Serve { + state("starting", "Preparing native compute runtime…"); + mesh_llm_host_runtime::initialize_host_runtime().await?; + mesh_llm_host_runtime::models::download_model_ref_with_progress_details( + &input.request.model_id, + true, + ) + .await?; + } + preflight(input.api_port, input.console_port)?; + let events = fresh_evidence(&broker).await?; + let trusted = owners(&events, &owner.owner_id); + let (disabled, relays) = match transport_policy::iroh_relay_mode()? { + transport_policy::IrohRelayMode::Disabled => (true, vec![]), + transport_policy::IrohRelayMode::Default => ( + false, + transport_policy::MESH_LLM_DEFAULT_RELAYS + .iter() + .map(|s| s.to_string()) + .collect(), + ), + transport_policy::IrohRelayMode::Custom(urls) => { + (false, urls.into_iter().map(|u| u.to_string()).collect()) + } + }; + let startup = async { + if input.request.mode == Mode::Client { + state("starting", "Connecting to community compute…"); + let builder = client::EmbeddedClientConfig::builder() + .api_port(input.api_port) + .console_port(input.console_port) + .publish(false) + .auto_join(false) + .discovery_mode(MeshDiscoveryMode::Nostr) + .startup_timeout(Duration::from_secs(180)) + .console_ui(true) + .mesh_name(mesh_name(&broker.relay)) + .disable_iroh_relays(disabled) + .iroh_relays(relays) + .owner_key(owner.keystore_path.clone()) + .owner_required(true) + .trust_policy(TrustPolicy::Allowlist) + .trust_owners(trusted.clone()); + client::start(builder.build()).await + } else { + let mut builder = serve::EmbeddedServeConfig::builder() + .model(input.request.model_id.clone()) + .api_port(input.api_port) + .console_port(input.console_port) + .publish(false) + .auto_join(false) + .discovery_mode(MeshDiscoveryMode::Nostr) + .startup_timeout(Duration::from_secs(180)) + .console_ui(true) + .mesh_name(mesh_name(&broker.relay)) + .disable_iroh_relays(disabled) + .iroh_relays(relays) + .owner_key(owner.keystore_path.clone()) + .owner_required(true) + .trust_policy(TrustPolicy::Allowlist) + .trust_owners(trusted.clone()); + if let Some(gb) = input.request.max_vram_gb { + builder = builder.max_vram_gb(gb); + } + state("starting", "Loading the model…"); + serve::start(builder.build()).await + } + }; + tokio::pin!(startup); + let mut checks = tokio::time::interval(Duration::from_secs(15)); + checks.tick().await; + let runtime = loop { + tokio::select! { + result=&mut startup=>break result?, + _=checks.tick()=>{ + let evidence=fresh_evidence(&broker).await?; + if owners(&evidence,&owner.owner_id)!=trusted {bail!("Community admission changed during startup; retry sharing");} + } + } + }; + let sharing = std::sync::atomic::AtomicBool::new(false); + let admission = async { + loop { + // No serving heartbeat is published until membership is freshly verified. + let evidence = fresh_evidence(&broker).await?; + if owners(&evidence, &owner.owner_id) != trusted { + bail!( + "Community admission changed; sharing stopped. Turn sharing off and on to refresh." + ); + } + let status = tokio::time::timeout(Duration::from_secs(8), runtime.status()).await??; + let payload = if input.request.mode == Mode::Client { + // Consumers publish ownership only, never remote models as their own serving targets. + idle(&owner, &broker.viewer)? + } else { + status_payload(status, &owner, &broker.viewer)? + }; + let ready = input.request.mode == Mode::Client || payload["node_state"] == "serving"; + broker.publish(payload).await?; + sharing.store(ready, std::sync::atomic::Ordering::SeqCst); + emit(Update { + usage_sample: false, + state: if ready { "running" } else { "starting" }.into(), + detail: Some( + if input.request.mode == Mode::Client { + "Connected locally; waiting for a verified provider" + } else if ready { + "Sharing with verified community members" + } else { + "Waiting for the model to become ready…" + } + .into(), + ), + download: None, + usage: None, + }); + join_peers(&runtime, &evidence, &owner.owner_id).await; + checks.tick().await; + } + }; + let samples = async { + loop { + tokio::time::sleep(Duration::from_secs(1)).await; + if !sharing.load(std::sync::atomic::Ordering::SeqCst) { + continue; + } + let sample = tokio::time::timeout(Duration::from_secs(2), runtime.status()).await; + if !sharing.load(std::sync::atomic::Ordering::SeqCst) { + continue; + } + let usage = sample + .ok() + .and_then(Result::ok) + .and_then(|status| crate::usage::Usage::from_payload(&status.payload)); + emit(Update { + usage_sample: true, + state: "running".into(), + detail: Some("Sharing with verified community members".into()), + download: None, + usage, + }); + } + }; + tokio::select! { + result = admission => result, + _ = samples => unreachable!(), + } +} + +async fn join_peers(runtime: &EmbeddedNodeHandle, events: &[nostr::Event], self_owner: &str) { + let members = discovery::latest_membership_list(events).unwrap_or_default(); + // Bounded peer bootstrap. Every endpoint is owner-bound, member-scoped and sanitized. + let mut joined = std::collections::BTreeSet::new(); + for e in events { + if e.kind.as_u16() != 30003 + || !members.contains(&e.pubkey.to_hex()) + || e.created_at > nostr::Timestamp::now() + || !discovery::status_is_fresh(e, nostr::Timestamp::now().as_secs()) + { + continue; + } + let Some(owner) = discovery::owner_id_from_status_event(e) else { + continue; + }; + if owner == self_owner { + continue; + } + let Ok(payload) = serde_json::from_str::(&e.content) else { + continue; + }; + if !discovery::endpoint_binding_is_valid(e, &payload) { + continue; + } + for token in identity::advertised_endpoint_tokens(&payload).unwrap_or_default() { + let Ok(endpoint) = transport_policy::validate_advertised_endpoint(&token) else { + continue; + }; + if !joined.insert(endpoint.endpoint_id) { + continue; + } + let _ = tokio::time::timeout( + Duration::from_secs(2), + runtime.join_token(endpoint.join_token), + ) + .await; + if joined.len() >= 3 { + return; + } + } + } +} +/// Called before Tauri setup. Stdin EOF also stops the child after a parent crash. +pub fn entry() -> ! { + if !cfg!(debug_assertions) { + std::process::exit(2); + } + let mut line = String::new(); + if std::io::stdin() + .lock() + .take(8193) + .read_line(&mut line) + .is_err() + || line.len() > 8192 + { + std::process::exit(2); + } + let input: WorkerInput = match serde_json::from_str(&line) { + Ok(input) => input, + Err(_) => std::process::exit(2), + }; + let rt = tokio::runtime::Builder::new_multi_thread() + .worker_threads(4) + .thread_stack_size(8 * 1024 * 1024) + .enable_all() + .build() + .expect("compute worker runtime"); + let (stop_tx, stop_rx) = tokio::sync::oneshot::channel(); + std::thread::spawn(move || { + let mut line = String::new(); + let _ = std::io::stdin().lock().read_line(&mut line); + let _ = stop_tx.send(()); + }); + mesh_llm_events::set_output_sink(Arc::new(Progress)); + rt.block_on(async move { + let cleanup = Arc::new(tokio::sync::Mutex::new(None)); + let result = tokio::select! { + result=run(input,cleanup.clone())=>result, + _=stop_rx=>Ok(()), + }; + state("stopping", "Stopping shared compute…"); + if let Some((broker, owner)) = cleanup.lock().await.as_ref() { + if let Ok(payload) = idle(owner, &broker.viewer) { + let _ = tokio::time::timeout(Duration::from_secs(4), broker.publish(payload)).await; + } + } + match result { + Ok(()) => std::process::exit(0), + Err(error) => { + state("failed", &format!("{error:#}")); + std::process::exit(1); + } + } + }) +} +mod models; + +#[cfg(test)] +mod tests { + use super::*; + use nostr::{EventBuilder, Keys, Kind, Tag, Timestamp}; + #[test] + fn admission_uses_membership_and_owner_proof_but_not_device_liveness() { + let member = Keys::generate(); + let stranger = Keys::generate(); + let owner = mesh_llm_host_runtime::crypto::OwnerKeypair::generate(); + let payload = json!({"ownerId":owner.owner_id(),"ownerVerifyingKey":hex::encode(owner.verifying_key().as_bytes()),"ownerBindingSig":hex::encode(owner.sign_bytes(&identity::member_binding_bytes(&member.public_key().to_hex())))}); + let status = EventBuilder::new(Kind::Custom(30003), payload.to_string()) + .custom_created_at(Timestamp::from_secs(1)) + .sign_with_keys(&member) + .unwrap(); + let roster = EventBuilder::new(Kind::Custom(13534), "") + .tags([Tag::parse(["member".to_string(), member.public_key().to_hex()]).unwrap()]) + .sign_with_keys(&stranger) + .unwrap(); + // Broker independently verifies the roster authority before this pure projection. + let trusted = owners(&[roster.clone(), status.clone()], "self"); + assert!(trusted.contains(&owner.owner_id())); + let removed = EventBuilder::new(Kind::Custom(13534), "") + .sign_with_keys(&stranger) + .unwrap(); + assert_eq!(owners(&[removed, status], "self"), vec!["self"]); + } + #[test] + fn validates_start_intent_and_preserves_old_community_mesh_name() { + let request = StartRequest { + community: "https://community.example".into(), + viewer: Keys::generate().public_key().to_hex(), + mode: Mode::Serve, + model_id: "local-model".into(), + max_vram_gb: Some(8.0), + }; + assert!(request.validate().is_ok()); + for model in ["", "auto", "bad\nmodel"] { + let mut bad = request.clone(); + bad.model_id = model.into(); + assert!(bad.validate().is_err()); + } + for cap in [0.0, -1.0, f64::NAN, f64::INFINITY] { + let mut bad = request.clone(); + bad.max_vram_gb = Some(cap); + assert!(bad.validate().is_err()); + } + use sha2::{Digest, Sha256}; + let digest = hex::encode(Sha256::digest(b"wss://community.example")); + assert_eq!( + mesh_name("https://community.example"), + format!("buzz-community-{}", &digest[..32]) + ); + } +} diff --git a/crates/community-compute/src/worker/models.rs b/crates/community-compute/src/worker/models.rs new file mode 100644 index 000000000..8176c9e7e --- /dev/null +++ b/crates/community-compute/src/worker/models.rs @@ -0,0 +1,117 @@ +use serde::Serialize; +use std::collections::BTreeMap; +#[derive(Serialize)] +pub(super) struct MeshModelOption { + pub id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, +} +pub fn models_from_status_payload(payload: Option<&serde_json::Value>) -> Vec { + let mut out = Vec::new(); + if let Some(payload) = payload { + // The SDK's raw status uses `hosted_models` plus ready entries under + // `runtime.models`. Buzz-authored status reports use `models`. Do not + // use `serving_models`: MeshLLM fills it with the requested model while + // the runtime is still in standby, before inference is available. + for key in ["models", "hosted_models"] { + if let Some(value) = payload.get(key) { + collect_model_options(value, &mut out); + } + } + if let Some(runtime_models) = payload + .get("runtime") + .and_then(|runtime| runtime.get("models")) + .and_then(serde_json::Value::as_array) + { + for model in runtime_models { + if model.get("status").and_then(serde_json::Value::as_str) == Some("ready") { + collect_model_options(model, &mut out); + } + } + } + } + dedupe_models(out) +} + +fn collect_model_options(value: &serde_json::Value, out: &mut Vec) { + match value { + serde_json::Value::Object(map) => { + if let Some(id) = map + .get("model_id") + .or_else(|| map.get("modelId")) + .or_else(|| map.get("model_ref")) + .or_else(|| map.get("modelRef")) + .or_else(|| map.get("id")) + .or_else(|| map.get("name")) + .and_then(serde_json::Value::as_str) + { + let name = map + .get("display_name") + .or_else(|| map.get("displayName")) + .and_then(serde_json::Value::as_str) + .map(ToString::to_string); + push_model(out, id, name); + } else { + for child in map.values().filter(|child| { + matches!( + child, + serde_json::Value::Array(_) | serde_json::Value::Object(_) + ) + }) { + collect_model_options(child, out); + } + } + } + serde_json::Value::Array(values) => { + for child in values { + collect_model_options(child, out); + } + } + serde_json::Value::String(value) => { + push_model(out, value, None); + } + _ => {} + } +} + +fn push_model(out: &mut Vec, id: &str, name: Option) { + let id = id.trim(); + if id.is_empty() || id.starts_with("http://") || id.starts_with("https://") { + return; + } + out.push(MeshModelOption { + id: id.to_string(), + name, + }); +} + +/// Canonical model id for equality/dedup. A serving node advertises the same +/// model under two strings — `org/model@main:Q4` (serveTargets[].modelId) and +/// `org/model:Q4` (available_models) — so keying on the raw string leaves BOTH +/// in the picker. Strip the `@main` ref-qualifier (matching the selection-time +/// rule in `pick_serve_target_for_model`) so the two collapse to one entry. +pub(super) fn canonical_model_id(value: &str) -> String { + value.trim().replace("@main", "") +} + +pub(super) fn dedupe_models(models: Vec) -> Vec { + // Key by canonical id so `@main` / non-`@main` forms of the same model + // dedup together. Display the canonical (stripped) id so the UI shows one + // stable label; selection still matches because the picker canonicalizes + // both sides too. + let mut by_id = BTreeMap::>::new(); + for model in models { + by_id + .entry(canonical_model_id(&model.id)) + .and_modify(|name| { + if name.is_none() { + *name = model.name.clone(); + } + }) + .or_insert(model.name); + } + by_id + .into_iter() + .map(|(id, name)| MeshModelOption { id, name }) + .collect() +} diff --git a/crates/plugin-manager/src/lib.rs b/crates/plugin-manager/src/lib.rs index cc5c97cad..60a3c0485 100644 --- a/crates/plugin-manager/src/lib.rs +++ b/crates/plugin-manager/src/lib.rs @@ -73,6 +73,10 @@ pub fn bundled_manifests() -> Vec { .expect("agents manifest"), serde_json::from_str(include_str!("../../../src/bundled/workflows/manifest.json")) .expect("workflows manifest"), + serde_json::from_str(include_str!( + "../../../src/bundled/community-compute/manifest.json" + )) + .expect("community compute manifest"), ] } fn is_bundled(id: &str) -> bool { diff --git a/crates/plugin-manager/tests/management.rs b/crates/plugin-manager/tests/management.rs index 15dbc1e5b..9f90524fd 100644 --- a/crates/plugin-manager/tests/management.rs +++ b/crates/plugin-manager/tests/management.rs @@ -276,6 +276,7 @@ fn bundled_plugins_have_independent_flags_and_all_ids_are_reserved() { "buzz.bestie", "buzz.projects", "buzz.agents", + "buzz.community-compute", "buzz.emoji", "buzz.mentions", "buzz.links", diff --git a/dev/compute-status.mjs b/dev/compute-status.mjs new file mode 100644 index 000000000..72dd1a4de --- /dev/null +++ b/dev/compute-status.mjs @@ -0,0 +1,161 @@ +import { setTimeout as delay } from "node:timers/promises"; +import { createHash, createPublicKey, verify } from "node:crypto"; +import { finalizeEvent } from "nostr-tools"; + +const fields = new Set([ + "ownerId", + "ownerVerifyingKey", + "ownerBindingSig", + "ownerEndpointBindingSig", + "serveTargets", + "models", + "node_state", + "my_vram_gb", + "model_size_gb", + "deviceName", + "endpointId", +]); +const hex = (value, length) => + typeof value === "string" && new RegExp(`^[0-9a-f]{${length}}$`).test(value); +const label = (value) => + typeof value === "string" && value.length > 0 && value.length <= 512; + +/** Dedicated native-worker publication; never extends the generic event signer. */ +export function signComputeStatus(payload, viewer, key) { + if ( + !payload || + typeof payload !== "object" || + Array.isArray(payload) || + Object.keys(payload).some((name) => !fields.has(name)) || + !hex(payload.ownerId, 64) || + !hex(payload.ownerVerifyingKey, 64) || + !hex(payload.ownerBindingSig, 128) || + !hex(payload.ownerEndpointBindingSig, 128) || + !Array.isArray(payload.serveTargets) || + payload.serveTargets.length > 32 || + !Array.isArray(payload.models) || + payload.models.length > 32 || + !["serving", "loading", "standby"].includes(payload.node_state) + ) + throw new Error("Invalid compute status"); + for (const name of ["my_vram_gb", "model_size_gb"]) + if ( + payload[name] !== undefined && + (!Number.isFinite(payload[name]) || payload[name] <= 0) + ) + throw new Error("Invalid compute capacity"); + for (const name of ["deviceName", "endpointId"]) + if (payload[name] !== undefined && !label(payload[name])) + throw new Error("Invalid device label"); + for (const model of payload.models) + if ( + !model || + Object.keys(model).some((name) => name !== "id" && name !== "name") || + !label(model.id) || + (model.name !== undefined && !label(model.name)) + ) + throw new Error("Invalid compute model"); + const tokens = payload.serveTargets.map((target) => { + if ( + !target || + Object.keys(target).some( + (name) => name !== "modelId" && name !== "endpointAddr", + ) || + !label(target.modelId) || + typeof target.endpointAddr !== "string" || + !target.endpointAddr.trim() || + target.endpointAddr.length > 49152 + ) + throw new Error("Invalid compute target"); + return target.endpointAddr.trim(); + }); + const rawKey = Buffer.from(payload.ownerVerifyingKey, "hex"); + if (createHash("sha256").update(rawKey).digest("hex") !== payload.ownerId) + throw new Error("Invalid compute owner"); + const publicKey = createPublicKey({ + key: Buffer.concat([ + Buffer.from("302a300506032b6570032100", "hex"), + rawKey, + ]), + format: "der", + type: "spki", + }); + const digest = createHash("sha256"); + for (const token of [...new Set(tokens)].sort()) { + const bytes = Buffer.from(token); + const length = Buffer.alloc(8); + length.writeBigUInt64BE(BigInt(bytes.length)); + digest.update(length); + digest.update(bytes); + } + const bindings = [ + [payload.ownerBindingSig, `buzz-mesh-owner-binding-v1:${viewer}`], + [ + payload.ownerEndpointBindingSig, + `buzz-mesh-owner-endpoint-binding-v1:${viewer}:${digest.digest("hex")}`, + ], + ]; + if ( + bindings.some( + ([signature, message]) => + !verify( + null, + Buffer.from(message), + publicKey, + Buffer.from(signature, "hex"), + ), + ) + ) + throw new Error("Invalid compute owner binding"); + return finalizeEvent( + { + kind: 30003, + created_at: Math.floor(Date.now() / 1000), + tags: [ + ["d", `buzz-mesh-member-status:${payload.ownerId}`], + ["k", "buzz-mesh-status"], + ], + content: JSON.stringify(payload), + }, + key, + ); +} + +/** Serialize this broker's worker heartbeats so Stop cannot lose a same-second + * replaceable-event tie to the immediately preceding serving note. */ +export function createComputeStatusSigner( + viewer, + key, + now = Date.now, + wait = delay, +) { + let tail = Promise.resolve(); + let last = -1; + let queued = 0; + return async (payload, signal) => { + // Reject malformed data before it can take a queue slot. + signComputeStatus(payload, viewer, key); + if (queued >= 4) throw new Error("Compute publication queue full"); + queued++; + const previous = tail; + let release; + tail = new Promise((resolve) => { + release = resolve; + }); + try { + await previous; + signal.throwIfAborted(); + const until = (last + 1) * 1000; + if (now() < until) await wait(until - now(), undefined, { signal }); + signal.throwIfAborted(); + const event = signComputeStatus(payload, viewer, key); + if (event.created_at <= last) + throw new Error("Compute publication clock did not advance"); + last = event.created_at; + return event; + } finally { + queued--; + release(); + } + }; +} diff --git a/dev/compute-status.test.mjs b/dev/compute-status.test.mjs new file mode 100644 index 000000000..3c96de342 --- /dev/null +++ b/dev/compute-status.test.mjs @@ -0,0 +1,164 @@ +import { createHash, generateKeyPairSync, sign } from "node:crypto"; +import { createServer } from "node:http"; +import { generateSecretKey, getPublicKey, verifyEvent } from "nostr-tools"; +import { afterEach, expect, it, vi } from "vitest"; +import { + signComputeStatus, + createComputeStatusSigner, +} from "./compute-status.mjs"; +import { relayBrokerPlugin } from "./relay-broker.mjs"; +const key = generateSecretKey(); +const viewer = getPublicKey(key); +function payload(targets = []) { + const owner = generateKeyPairSync("ed25519"); + const raw = owner.publicKey + .export({ format: "der", type: "spki" }) + .subarray(-32); + const digest = createHash("sha256"); + for (const token of [ + ...new Set(targets.map((t) => t.endpointAddr.trim())), + ].sort()) { + const bytes = Buffer.from(token); + const length = Buffer.alloc(8); + length.writeBigUInt64BE(BigInt(bytes.length)); + digest.update(length); + digest.update(bytes); + } + return { + ownerId: createHash("sha256").update(raw).digest("hex"), + ownerVerifyingKey: raw.toString("hex"), + ownerBindingSig: sign( + null, + Buffer.from(`buzz-mesh-owner-binding-v1:${viewer}`), + owner.privateKey, + ).toString("hex"), + ownerEndpointBindingSig: sign( + null, + Buffer.from( + `buzz-mesh-owner-endpoint-binding-v1:${viewer}:${digest.digest("hex")}`, + ), + owner.privateKey, + ).toString("hex"), + serveTargets: targets, + models: [], + node_state: "standby", + }; +} +afterEach(() => vi.useRealTimers()); +it("signs only the reserved owner coordinate with real member and owner signatures", () => { + const body = payload([{ modelId: "model", endpointAddr: "endpoint" }]); + const event = signComputeStatus(body, viewer, key); + expect(verifyEvent(event)).toBe(true); + expect(event.pubkey).toBe(viewer); + expect(event.kind).toBe(30003); + expect(event.tags).toEqual([ + ["d", `buzz-mesh-member-status:${body.ownerId}`], + ["k", "buzz-mesh-status"], + ]); + expect(JSON.parse(event.content)).toEqual(body); +}); +it("rejects owner substitution, wrong member, target tampering, and extra data", () => { + const body = payload([{ modelId: "model", endpointAddr: "endpoint" }]); + for (const changed of [ + { ...body, ownerId: "0".repeat(64) }, + { ...body, ownerBindingSig: "0".repeat(128) }, + { ...body, serveTargets: [{ modelId: "model", endpointAddr: "other" }] }, + { ...body, gpus: [{ vram_bytes: 123 }] }, + { ...body, my_vram_gb: Infinity }, + ]) + expect(() => signComputeStatus(changed, viewer, key)).toThrow(); + expect(() => + signComputeStatus(body, getPublicKey(generateSecretKey()), key), + ).toThrow(/binding/); +}); +it("orders same-second publications and cancels queued status without signing", async () => { + vi.useFakeTimers(); + vi.setSystemTime(100000); + const signer = createComputeStatusSigner(viewer, key); + const body = payload(); + const signal = new AbortController(); + const first = await signer(body, signal.signal); + const second = signer(body, signal.signal); + await vi.advanceTimersByTimeAsync(1000); + expect((await second).created_at).toBe(first.created_at + 1); + const controller = new AbortController(); + const cancelled = signer(body, controller.signal); + const assertion = expect(cancelled).rejects.toThrow(); + controller.abort(); + await assertion; +}); +it("publishes through the real scoped broker route without exposing a general mesh signer", async () => { + let handler; + const events = []; + const server = createServer((req, res) => + handler(req, res, () => { + res.writeHead(404); + res.end(); + }), + ); + await relayBrokerPlugin({ + relayUrl: "wss://compute.example", + identity: () => key.slice(), + authority: async () => ({ relayAuthor: viewer, archiveAuthority: viewer }), + upstreamFetch: async (url, init) => { + expect(url).toBe("https://compute.example/events"); + const event = JSON.parse(init.body); + expect(verifyEvent(event)).toBe(true); + events.push(event); + const auth = JSON.parse( + Buffer.from(init.headers.Authorization.slice(6), "base64").toString(), + ); + expect(verifyEvent(auth)).toBe(true); + expect(auth.tags).toContainEqual([ + "payload", + createHash("sha256").update(init.body).digest("hex"), + ]); + return Response.json({ accepted: true, event_id: event.id }); + }, + }).configureServer({ + httpServer: server, + config: { logger: { info() {}, error() {} } }, + middlewares: { + use(fn) { + handler = fn; + }, + }, + }); + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); + const base = `http://127.0.0.1:${server.address().port}`; + const post = (route, body, origin = base) => + fetch(`${base}/api/relay/${route}`, { + method: "POST", + headers: { Origin: origin, "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + try { + expect( + (await post("register", { url: "wss://compute.example" })).status, + ).toBe(200); + const route = `${encodeURIComponent("https://compute.example")}/compute-status`; + const body = payload(); + expect((await post(route, body)).status).toBe(200); + expect(events).toHaveLength(1); + expect( + (await post(route, { ...body, ownerBindingSig: "0".repeat(128) })).status, + ).toBe(400); + expect((await post(route, body, "https://foreign.example")).status).toBe( + 403, + ); + expect( + ( + await post("sign", { + kind: 30003, + created_at: 100, + content: "{}", + tags: [], + }) + ).status, + ).toBe(400); + expect(events).toHaveLength(1); + } finally { + server.closeAllConnections(); + await new Promise((resolve) => server.close(resolve)); + } +}); diff --git a/dev/compute-widget.test.mjs b/dev/compute-widget.test.mjs new file mode 100644 index 000000000..055252b61 --- /dev/null +++ b/dev/compute-widget.test.mjs @@ -0,0 +1,128 @@ +import { readFileSync } from "node:fs"; +import vm from "node:vm"; +import { JSDOM } from "jsdom"; +import { expect, it } from "vitest"; + +it("keeps four designs tied to fresh native usage and resets totals on replacement", async () => { + const html = readFileSync( + new URL("../public/compute-widget.html", import.meta.url), + "utf8", + ); + const script = readFileSync( + new URL("../public/compute-widget.js", import.meta.url), + "utf8", + ); + const dom = new JSDOM(html, { + runScripts: "outside-only", + url: "http://localhost", + }); + try { + let status = { + generation: 1, + state: "running", + usage: { tokensServed: 120, inflight: 0, tokensPerSecond: 5, peers: 2 }, + }; + const win = dom.window; + win.matchMedia = () => ({ matches: true }); + win.HTMLCanvasElement.prototype.getContext = () => + new Proxy( + {}, + { + get: () => () => {}, + }, + ); + win.requestAnimationFrame = () => 0; + win.setTimeout = () => 0; + win.clearTimeout = () => {}; + let read = async () => status; + win.__TAURI_INTERNALS__ = { invoke: () => read() }; + const context = dom.getInternalVMContext(); + const run = (code) => vm.runInContext(code, context); + run(script); + await run("poll()"); + expect(run("model.total")).toBe(120); + expect(run("model.phase")).toBe("online"); + // Exercise the real moving bee, not only the reduced-motion fallback. + const movingFrames = [0, 350, 700, 1100, 2200, 3800, 6500].map((elapsed) => + run( + `draw("orbit", {phase:phases[3], index:3, elapsed:${elapsed}}, false)`, + ), + ); + for (const frame of movingFrames) { + expect(frame.sprite.length).toBeGreaterThan(0); + expect(frame.sprite.flat().every(Number.isFinite)).toBe(true); + expect(frame.streaks.flat().every(Number.isFinite)).toBe(true); + } + expect(movingFrames[3].sprite).not.toEqual(movingFrames[4].sprite); + + for (const name of ["orbit", "signal", "original", "bee"]) { + win.dispatchEvent( + new win.KeyboardEvent("keydown", { key: "ArrowRight" }), + ); + expect(run("variation")).toBe(name); + const frame = run( + "variation==='original'?drawOriginal({phase:phases[3],index:3,elapsed:1100},true):variation==='bee'?draw('orbit',{phase:phases[3],index:3,elapsed:1100},true):drawInstrument({phase:phases[3],index:3,elapsed:1100},true)", + ); + expect(frame.white.every(Number.isFinite)).toBe(true); + for (let key = 0; key <= 9; key++) { + win.dispatchEvent( + new win.KeyboardEvent("keydown", { key: String(key) }), + ); + run("render(performance.now())"); + expect( + win.document.getElementById("display").getAttribute("aria-label"), + ).toContain("Visual state preview"); + } + } + expect(win.document.querySelector("select")).toBeNull(); + win.dispatchEvent(new win.KeyboardEvent("keydown", { key: "ArrowLeft" })); + expect(run("variation")).toBe("original"); + status = { generation: 1, state: "running", usage: null }; + await run("poll()"); + expect(run("model.valid")).toBe(false); + expect(run("model.phase")).toBe("offline"); + status = { generation: 2, state: "starting", usage: null }; + await run("poll()"); + expect(run("model.total")).toBe(null); + expect(run("model.phase")).toBe("link"); + status = { generation: 2, state: "failed", usage: null }; + await run("poll()"); + expect(run("model.phase")).toBe("error"); + const timers = new Map(); + win.setTimeout = (callback, delay) => { + timers.set(delay, callback); + return delay; + }; + win.clearTimeout = (delay) => timers.delete(delay); + win.console.warn = () => {}; + let finishLate; + read = () => + new Promise((resolve) => { + finishLate = resolve; + }); + const pending = run("poll()"); + timers.get(8000)(); + await pending; + expect(timers.has(1000)).toBe(true); + read = async () => ({ + generation: 3, + state: "running", + usage: { tokensServed: 7, inflight: 0 }, + }); + await run("poll()"); + finishLate({ + generation: 1, + state: "running", + usage: { tokensServed: 999, inflight: 1 }, + }); + await Promise.resolve(); + expect(run("model.total")).toBe(7); + win.dispatchEvent(new win.KeyboardEvent("keydown", { key: "4" })); + expect(run("inspectedState.index")).toBe(3); + expect(win.document.body.textContent).not.toContain("PREVIEW"); + win.dispatchEvent(new win.KeyboardEvent("keydown", { key: "Escape" })); + expect(run("inspectedState")).toBeNull(); + } finally { + dom.window.close(); + } +}); diff --git a/dev/relay-broker.d.mts b/dev/relay-broker.d.mts index 3dd0d8823..9c4a42213 100644 --- a/dev/relay-broker.d.mts +++ b/dev/relay-broker.d.mts @@ -1,6 +1,8 @@ import type { Plugin } from "vite"; export function relayBrokerPlugin(options?: { authorizedViewer?: string | undefined; + credentialService?: string | undefined; + agentLibraryPath?: string | undefined; agentLibrary?: () => Promise< import("../src/features/agents/library").AgentLibrary >; diff --git a/dev/relay-broker.mjs b/dev/relay-broker.mjs index 4c3a45452..256fdc641 100644 --- a/dev/relay-broker.mjs +++ b/dev/relay-broker.mjs @@ -1,3 +1,4 @@ +import { createComputeStatusSigner } from "./compute-status.mjs"; import { SocketRequestError } from "../src/features/relay/socket-requests.ts"; import { validateWorkflowEvent, @@ -130,7 +131,14 @@ export function createUpstream(base) { }; } -function loadIdentity(authorizedViewer) { +export function loadIdentity( + authorizedViewer, + { + service = "buzz-desktop", + platform = process.platform, + read = execFileSync, + } = {}, +) { // Validate the explicit public pin before prompting for any credential access. const configured = authorizedViewer?.trim() ?? ""; let expected; @@ -147,6 +155,14 @@ function loadIdentity(authorizedViewer) { throw new Error( "Set BUZZ_DEV_VIEWER in .env.local to your existing Buzz public key (hex or npub, never nsec). See README.md#relay-channels.", ); + if ( + !/^buzz-desktop(?:-dev(?:\.[a-z0-9][a-z0-9._-]{0,79})?|-demo\.[a-z0-9][a-z0-9._-]{0,79})?$/.test( + service, + ) + ) + throw new Error( + "Choose an existing Buzz credential service; no credential fallback", + ); // The installed Buzz desktop keeps its secrets blob in the OS credential // store under service `buzz-desktop`, username `secrets`: the macOS Keychain, // or the freedesktop secret service on Linux (read through libsecret's @@ -155,31 +171,24 @@ function loadIdentity(authorizedViewer) { const readers = { darwin: { command: "/usr/bin/security", - args: [ - "find-generic-password", - "-s", - "buzz-desktop", - "-a", - "secrets", - "-w", - ], + args: ["find-generic-password", "-s", service, "-a", "secrets", "-w"], failure: "Keychain read unavailable or declined; no credential fallback", }, linux: { command: "secret-tool", - args: ["lookup", "service", "buzz-desktop", "username", "secrets"], + args: ["lookup", "service", service, "username", "secrets"], failure: "Secret service read unavailable (needs libsecret-tools, an unlocked keyring in this desktop session, and Buzz desktop signed in); no credential fallback", }, }; - const reader = readers[process.platform]; + const reader = readers[platform]; if (!reader) throw new Error( - `Live identity is read from the OS credential store on macOS or Linux only (this is ${process.platform}); no credential fallback`, + `Live identity is read from the OS credential store on macOS or Linux only (this is ${platform}); no credential fallback`, ); let raw; try { - raw = execFileSync(reader.command, reader.args, { + raw = read(reader.command, reader.args, { stdio: ["ignore", "pipe", "pipe"], timeout: 120000, }) @@ -313,11 +322,14 @@ export function relayBrokerPlugin({ authorizedViewer, relayUrl, communityAliases, - identity = () => loadIdentity(authorizedViewer), + credentialService = "buzz-desktop", + identity = () => + loadIdentity(authorizedViewer, { service: credentialService }), authority = relayAuthority, upstreamFetch, socketFactory, - agentLibrary = readAgentLibrary, + agentLibraryPath, + agentLibrary = () => readAgentLibrary(agentLibraryPath), } = {}) { const aliases = parseCommunityAliases(communityAliases); const defaultRelay = relayUrl?.trim() ? relayOrigin(relayUrl) : undefined; @@ -326,6 +338,7 @@ export function relayBrokerPlugin({ async configureServer(server) { const key = identity(); const viewer = getPublicKey(key); + const signComputeStatus = createComputeStatusSigner(viewer, key); const upstream = createUpstream(); // Injected fixtures bypass the pool; the live relay always uses the warm agent. const fetchUpstream = upstreamFetch ?? upstream.fetch; @@ -578,6 +591,7 @@ export function relayBrokerPlugin({ viewer, ...(await getAuthority(relay)), relayUrl: relay, + computeStatus: true, writeKinds: [7, 9, ...WORKFLOW_KINDS], workflowReads: true, sidebarPreferences: true, @@ -925,6 +939,7 @@ export function relayBrokerPlugin({ } if ( ![ + "/api/relay/compute-status", "/api/relay/query", "/api/relay/sign", "/api/relay/publish", @@ -1057,6 +1072,18 @@ export function relayBrokerPlugin({ sent: false, }); const timings = []; + const computeStatus = route === "/api/relay/compute-status"; + if (computeStatus) { + try { + cancel.signal.throwIfAborted(); + filters = await signComputeStatus(filters, cancel.signal); + } catch { + return json(res, 400, { + error: "Compute status rejected", + sent: false, + }); + } + } const signing = route === "/api/relay/sign"; const publishing = route === "/api/relay/publish"; if (signing || publishing) { @@ -1097,6 +1124,7 @@ export function relayBrokerPlugin({ if (filters.pubkey !== viewer || !verifyEvent(filters)) return json(res, 400, { error: "Invalid outgoing signature" }); } else if ( + !computeStatus && !profile && !claim && !policy && @@ -1144,7 +1172,7 @@ export function relayBrokerPlugin({ workflowPath ?? (gifs ? gifSearchPath - : profile + : profile || computeStatus ? "/events" : claim ? "/api/invites/claim" @@ -1257,14 +1285,14 @@ export function relayBrokerPlugin({ } return json(res, response.status, failure); } - if (profile) { + if (profile || computeStatus) { const receipt = JSON.parse(text); if ( receipt.event_id !== filters.id || typeof receipt.accepted !== "boolean" ) return json(res, 502, { - error: "Profile publication could not be confirmed", + error: "Publication could not be confirmed", }); } res.writeHead(200, { diff --git a/dev/relay-identity.test.mjs b/dev/relay-identity.test.mjs new file mode 100644 index 000000000..45ab21bfe --- /dev/null +++ b/dev/relay-identity.test.mjs @@ -0,0 +1,53 @@ +import { expect, it, vi } from "vitest"; +import { getPublicKey, nip19 } from "nostr-tools"; +import { loadIdentity } from "./relay-broker.mjs"; +const key = new Uint8Array(32).fill(7); +const pubkey = getPublicKey(key); +const secret = JSON.stringify({ identity: nip19.nsecEncode(key) }); +it("reads only the selected Buzz service and enforces its pinned public identity", () => { + const read = vi.fn(() => Buffer.from(secret)); + const result = loadIdentity(pubkey, { + service: "buzz-desktop-demo.provider", + platform: "darwin", + read, + }); + expect(getPublicKey(result)).toBe(pubkey); + expect(read.mock.calls[0][1]).toEqual([ + "find-generic-password", + "-s", + "buzz-desktop-demo.provider", + "-a", + "secrets", + "-w", + ]); + result.fill(0); + expect(() => + loadIdentity(getPublicKey(new Uint8Array(32).fill(8)), { + service: "buzz-desktop-dev.consumer", + platform: "darwin", + read, + }), + ).toThrow("does not match"); + expect(read).toHaveBeenCalledTimes(2); +}); +it("refuses invalid public pins or unrelated credential services before reading", () => { + const read = vi.fn(); + expect(() => loadIdentity("invalid", { read })).toThrow("public key"); + expect(() => + loadIdentity(pubkey, { service: "unrelated-service", read }), + ).toThrow("existing Buzz"); + expect(read).not.toHaveBeenCalled(); +}); +it("does not fall back when the chosen service is missing or declined", () => { + const read = vi.fn(() => { + throw new Error("unavailable"); + }); + expect(() => + loadIdentity(pubkey, { + service: "buzz-desktop-dev.consumer", + platform: "darwin", + read, + }), + ).toThrow("no credential fallback"); + expect(read).toHaveBeenCalledTimes(1); +}); diff --git a/docs/agents.md b/docs/agents.md index 42cbdaf1e..cb3c5c375 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -4,7 +4,46 @@ V1 **reuses the current Buzz library and mentions existing agents in channels and threads**. No migration to relay-only storage. Creation, editing, -add-existing membership, Save/recovery and all runner management are out of V1. +add-existing membership and Save/recovery remain out of V1. The local development +runner extension below adds start/stop for one explicitly configured existing agent. + +### Local shared-compute runner (macOS development builds) + +The native `agent_runner` host can run an existing owner-only `buzz-agent` +identity independently of the old desktop app. The consumer bundle contains +`buzz-acp`, `buzz-agent` and `buzz-dev-mcp`; old Buzz does not need to remain open. +`Agents` exposes Start/Stop on the card containing the configured identity, matched +by public key rather than display name. This is not a general agent editor or release installer. + +The development broker uses the launcher's `BUZZ_AGENT_LIBRARY` override, falling +back to `BUZZ_RUNNER_LIBRARY` and then the installed Buzz library. The paired +consumer therefore displays the same library its runner uses. Saved definitions +and other identities remain visible; only the configured identity has runner controls. + +Trusted launcher configuration selects `BUZZ_RUNNER_AGENT`, +`BUZZ_RUNNER_LIBRARY`, `BUZZ_RUNNER_WORKDIR` and the existing +`BUZZ_DEV_CREDENTIAL_SERVICE`. The renderer cannot supply executable paths, +credentials, prompts or arbitrary environment variables. The host reads the legacy +library without migration/writeback, resolves the linked definition, verifies its +unconditional owner attestation against the connected compute account, and reads +only the selected agent credential from the existing Keychain blob. Credentials +stay native and are never returned to the renderer or copied into the bundle. +Conditional attestations and non-shared-compute/non-owner-only configurations fail +closed in this initial compatibility slice. + +The runner holds the consumer generation lease until its process group is gone. +Compute replacement cannot reuse its endpoint while the agent is alive. Disconnect +stops the agent; reconnection requires an explicit Start. A pipe-supervised group +leader handles desktop death and holds the identity lock until group cleanup. +Existing legacy PID receipts block duplicate launches; stop old Buzz's runner +before handing it over. Do not restart the old runner concurrently: old Buzz does +not understand the new app's lock. App launch does not automatically start agents. + +The pair builder accepts `BUZZ_AGENT_RUNTIME_SOURCE` pointing to the trusted +existing Buzz binary directory and `BUZZ_COMPUTE_BUILD_ROLE=client` to rebuild only +the consumer. Executables are replaced atomically to avoid macOS rejecting an +overwritten running executable. The live consumer remains a development build +with the existing local library/Keychain and source checkout dependencies. ### Implemented compatibility view @@ -106,6 +145,12 @@ not a substitute for relay authorization, a membership transaction, or the ACP listener's own admission rules. Network changes after transport dispatch remain possible. No ownership or running status is inferred from a member's name/profile. +In a relay-identified one-to-one DM, the composer also addresses the sole other +participant from the verified roster, without requiring a typed mention. This +applies to root messages and thread replies and uses the same publication +preflight. It allows mention-subscribed agent runners to receive ordinary DMs. +Group DMs, streams, self-DMs and unknown metadata do not infer recipients. + Wire compatibility is kind 9 + `h` + exact `p`; direct replies also carry `["e", root, "", "reply"]`. Existing buzz-acp owns mention admission, replay, channel membership, pool wake and harness execution. This slice adds no wake loop, diff --git a/docs/community-compute.md b/docs/community-compute.md new file mode 100644 index 000000000..1ba02b78c --- /dev/null +++ b/docs/community-compute.md @@ -0,0 +1,198 @@ +# Compute plugin + +Bundled UI, verified status, and development-native sharing implementation for porting [block/buzz#7691](https://github.com/block/buzz/pull/7691) +to this app. The bundled `buzz.community-compute` plugin contributes a Settings +section and an active-provider widget launcher, and participates in the normal +browser and desktop enable/disable catalog. + +For another developer to build and test this branch with their own accounts, use +the [Compute tester setup guide](compute-testing.md). + +## Implemented + +- Ported snapshot types, map projection, contiguous hex layout and original + map regression coverage from PR head `372e5407f937c13801cc80ce6d16e33c97000d02`. + Source array access was adapted to this repository's stricter TypeScript settings. +- Sharing controls with recommended/custom model input, memory limit, startup + and download progress, failure/retry presentation and a successful sharing state. +- Serve/client distinction: consuming a remote model does not select that model + for a local download or permit the share switch to stop the consuming runtime. +- A startup completion is retired once authoritative running/failed status arrives; + a later Stop remains locked until its actual completion, even if status is off. +- Community map at six contributing members, host theme/typography, and keyboard + territory disclosure. Missing capacity remains unknown rather than showing zero. +- A separate sample-data preview that never starts native compute or accesses an account. + +The normal **desktop** page now reads live community status through its connected +relay session. `src/features/community-compute/status.ts` owns demand-driven reads, +complete author-batched pagination, cancellation, retry and expiry. It rechecks the +roster after pagination. The Tauri command projects signed evidence through +`crates/community-compute`; the plugin receives only display fields. Browsers +without that native validator explicitly report that desktop is required. + +The native projection verifies event signatures and the host-provided explicit +NIP-11 `self` authority, current membership, owner and endpoint bindings, freshness, +and the old transport policy. It ignores future notes; newer owner reports replace +older ones deterministically. A serving label without usable targets cannot inflate +the headline. Raw GPU totals, endpoint tokens, signatures and operational payloads +are not returned to the page. This display command does **not** authorize admission +or accept instructions to run a node. + +Sharing is implemented in the development desktop app using its existing authorized +relay broker. Packaged sign-in, agent creation, and a separately distributed external +artifact remain separate work. Status reads never create keys, download a model, or +start sharing. No sample values replace live status. + +## Try the UI + +From the feature worktree, activate Hermit and run: + +```sh +BUZZ_DEV_VIEWER='' bin/pnpm exec vite --host 127.0.0.1 --port 1442 --strictPort +``` + +Open `/tests/fixtures/community-compute.html`. The explicit empty viewer override +keeps the development broker off even when bootstrap copied a public viewer pin. +Choose a sample community, turn on sharing, and use **Finish sample download** to +advance to the sharing state. **Show startup failure** and **Show consuming state** +exercise other presentation states. **Switch to dark/light** changes the preview +theme. All values and transitions on this fixture are simulated and labeled. + +In a rebuilt desktop development app with its existing authorized relay broker, +select a community and open Compute to read live status. The page polls +every 15 seconds while observed; leaving it or disabling the plugin cancels reads. +Switching communities binds the view to that community and session generation. +Any read failure removes the headline instead of retaining a stale success; device +reports also expire independently of an in-flight refresh. Settings → Plugins can +disable/re-enable the page. The browser fixture above remains sample data. + +## Native sharing ownership + +The approved first integration uses `dev/relay-broker.mjs`; sharing is unavailable +in packaged builds until a native account/signing service exists. Rebuild the desktop +app and restart its matching development broker to use the new commands and narrow +compute-status endpoint. The sample browser fixture cannot exercise native sharing. + +`src-tauri/src/compute_host.rs` owns one dedicated child of the desktop executable. +The worker in `crates/community-compute/src/worker.rs` loads the hardware-aware model +catalog, downloads the selected model and signed native runtime through the pinned +SDK, starts serving, and publishes verified status. A saved opt-in restores sharing +on the next development app launch. User Stop and plugin disable clear that opt-in; +app exit stops the process but retains it. Leaving the page only stops observation. +Stop remains reachable when the selected community disconnects. Cancel startup uses +the same cleanup path, with a bounded graceful shutdown followed by killing and +reaping only the owned worker. Occupied runtime ports fail clearly without stopping +another app's runtime. + +The native parent supplies an app-specific persistent owner-key path. The renderer +cannot choose it, and the Buzz account signing key stays in the broker. This avoids +old Buzz idle notes replacing this app's owner status. The broker accepts only the +narrow compute-status schema, verifies owner/member/endpoint proofs, checks the +publication receipt, and serializes signing timestamps so a stopped note supersedes +an immediately preceding serving note. The generic event signer is unchanged. + +Admission is independently derived from signed, current authority rosters and owner +proofs fetched through the authorized loopback broker. The worker rechecks evidence +during startup and serving; membership/owner roster changes or verification failures +stop sharing and require a manual retry. Peer discovery additionally requires fresh, +verified endpoint bindings and the existing transport policy. Renderer snapshots do +not authorize peers. No account key or endpoint token is exposed to the plugin UI. + +`CommunityComputeView` still takes ordinary presentation props and controls, not a +promised public SDK. Agent creation remains separate: the host currently reads the +existing Buzz agent library, and the old flow needs real creation and runner +capabilities before the disabled action can be enabled. See [Agents](agents.md). + +## Activity widgets + +The page and plugin catalog are named **Compute**; the stable plugin ID remains +`buzz.community-compute` so existing enable/disable preferences keep working. +**Open activity widget** opens one reusable, draggable, always-on-top native window. +Left/Right cycles LED, Flying Bee, Orbit, or Signal. Number keys preview visual +states; Escape returns to live activity. There is no dropdown or preview caption. +Closing the widget does not stop sharing; +disabling Compute closes the widget and stops the worker. + +The widget reads the native host's worker generation and sampled usage, never the +old app's console port. All designs show runtime-session totals, not lifetime +contribution or payments. Missing counters stay unknown; stale samples expire in +five seconds, runtime replacement resets totals, and delayed reads cannot overwrite +newer polling results. A single sampler owns counters independently of admission +heartbeats so slow broker publication cannot rewind activity. + +Source: old `block/buzz` branch `mesh-buddy-window`, head +`49510696044dec38fa770058d93325d92694a788`, plus the four-design native integration +study in `design-explorations/mesh-buddy/native-integration/mesh-buddy.html` +in the old Buzz working tree. +`public/compute-widget.js` preserves those dot-matrix designs with a new scoped +host-status adapter, native controls and no simulated token totals. + +## Verification + +Focused map tests cover stable placement, connected/nonoverlapping territories, +scale buckets, device/model identities and dense samples. Mounted UI tests use real +React/StrictMode and shared controls for input validation, serve/client distinction, +progress, errors and late completion. App composition tests cover actual plugin +activation/removal; Rust management tests cover native catalog toggles and reserved IDs. + +The new read path has focused tests for complete same-second pagination, author +batching, missing/empty/changed membership, invalid pages, read errors and retry, +missing explicit authority, unsubscribe/disposal fencing, and expiry during a hung +refresh. Mounted page tests cover community replacement and correct retry ownership. +Native tests use generated in-memory signing keys to check roster and event forgery, +owner/endpoint tampering, nonmembers, stale/future notes, stopped-note replacement, +distinct-device/member counts and unknown allocation, plus the ported endpoint-policy +tests (including signed-token verification). + +The current focused suite passes 55 frontend/broker tests, 29 native protocol, +catalog, identity and worker tests, and five owned-process lifecycle tests. A separate 51-test broker regression run +also passes (including the four compute publication tests above). Fixtures +exercise real local HTTP broker publication and signing, process cancellation, +forced cleanup, persistence/restore, stale generations, crash reporting and a +reachable Stop while disconnected. Independent review covered the native lifecycle, +broker signing boundary and app-specific owner identity. + +A native debug binary build and an invalid-input worker-entry smoke test pass without +creating an identity or starting a runtime. Subsequent manual macOS testing verified +two distinct accounts, real shared-model completions, and an agent reply hosted by +the new consumer while the old agent desktop was closed. The activity widget +reflected real requests and token totals. These are development checks, not release +or cross-platform acceptance. The macOS debug +linker reports a large unwind-table warning; the binary builds and the entry smoke +check succeeds. + +## Source attribution + +The original map algorithms, fixtures and map tests come from the contributors to +[PR #7691](https://github.com/block/buzz/pull/7691) / #7014 in `block/buzz` (Apache-2.0). +The new view and adapted SVG presentation replace imports from the old settings, +agent-dialog and Tauri API modules with this host's UI components and explicit props. + +Native discovery, binding, owner identity, hardware-aware catalogue, model status +extraction and transport validation were adapted from the local old Buzz checkout. Signed bootstrap validation deliberately uses the same pinned SDK +(`v0.76.0-rc9`, resolved in Cargo.lock) instead of duplicating its signature format. +That SDK brings a substantial native dependency tree even with default features off. + +## Two-app local test + +`scripts/build-compute-pair.mjs [output-directory]` builds macOS Provider and +Consumer development app bundles from the activated Hermit environment. Each has +a distinct compiled identifier/dev URL, launcher-owned broker, plugin home, owner +key and runtime ports. The launcher uses the existing authorized account rather +than copying keys. The bundles remain dependent on this checkout and are not +signed distributable releases. + +The client worker uses SDK client mode with the same verified admission and endpoint +rules. It skips local model/runtime downloads and publishes ownership-only notes, +never another node's models as its own capacity. The Consumer screen connects, +disconnects and sends a bounded text request through its owned loopback API. A test +lease prevents port reuse until the request is cancelled/settled, while Stop remains +available. Session changes cancel pending model lookup or generation and discard +late results. The model comes from the mesh API's first advertised model; a shared +community with other providers can route to those providers too. + +For first pairing connect Consumer before Provider; retry if initial registration +changes the verified owner roster. Neither app starts compute on first launch. +Saved opt-in on later launches behaves as documented above. The local pair is a +two-device test and can use separate accounts through `account-env.sh`; see the +tester guide. Accounts and community membership must already be provisioned. diff --git a/docs/compute-testing.md b/docs/compute-testing.md new file mode 100644 index 000000000..c73829b21 --- /dev/null +++ b/docs/compute-testing.md @@ -0,0 +1,167 @@ +# Test the Compute development branch + +This is a branch for developer testing, not an organization rollout or a released +installer. Build it locally on macOS. Do not send the generated app bundles to +another person: their launchers reference your checkout, and each tester needs +their own account configuration. No pull request is required to try the branch. + +## Requirements + +- A macOS development machine with Xcode command-line tools and the repository's + [Tauri prerequisites and Hermit setup](contributing.md). +- Access to `block/buzz-app` and its pinned Mesh LLM Git dependencies. +- Existing Buzz accounts signed in through the old desktop app. This development + integration reads existing credentials from macOS Keychain; it has no independent + packaged sign-in flow. Do not export or paste private keys into this checkout. +- Both test accounts admitted to the same compatible Buzz community, with signed + membership/owner records and explicit relay authority. Ask the community owner + for membership if needed. A community URL alone does not grant access. +- Enough RAM and disk for the provider's model. First sharing startup can download + the model and verified native runtime. The consumer does not load a local model. + +Two apps on one Mac test separate identities and isolated runtimes. For a real +cross-machine test, build locally on both Macs and use the Provider on one and +Consumer on the other. Cross-machine/network recovery is not yet fully validated. + +## Get the branch and build + +```sh +git clone https://github.com/block/buzz-app.git buzz-app-compute +cd buzz-app-compute +git switch --track origin/feat/community-compute-plugin +source bin/activate-hermit +pnpm install --frozen-lockfile +node scripts/build-compute-pair.mjs +``` + +The builder creates `.scratch/compute-pair/Buzz Compute Provider.app` and +`Buzz Compute Consumer.app`. Keep the checkout and dependencies in place. The +build may take a while; the SDK adds a substantial Rust dependency tree. These are +local debug bundles, not signed distributable releases. Follow local development +policy if macOS prevents launch; do not disable system security protections. + +## Configure your own accounts + +Create `.scratch/compute-pair/account-env.sh` locally. The launcher sources this +file after setting `BUZZ_COMPUTE_ROLE`. Replace every placeholder below. Use +**public** user IDs (hex or npub) and the exact existing Keychain service name for +each signed-in profile. The ordinary installed Buzz account uses `buzz-desktop`; +development profiles can use `buzz-desktop-dev.`. Do not create a service +name by guessing: check the profile's configuration or its Keychain item metadata. + +```sh +export BUZZ_RELAY_URL='wss://YOUR-COMMUNITY-HOST' +if [[ "$BUZZ_COMPUTE_ROLE" == serve ]]; then + export BUZZ_DEV_VIEWER='PROVIDER_PUBLIC_USER_ID' + export BUZZ_DEV_CREDENTIAL_SERVICE='EXISTING_PROVIDER_KEYCHAIN_SERVICE' +else + export BUZZ_DEV_VIEWER='CONSUMER_PUBLIC_USER_ID' + export BUZZ_DEV_CREDENTIAL_SERVICE='EXISTING_CONSUMER_KEYCHAIN_SERVICE' +fi +``` + +Keychain may ask for access when the broker starts. The account pin must match the +credential; a mismatch fails closed. Never commit this local file, Keychain exports, +runtime state, logs, or generated bundles. Each app has separate plugin preferences, +owner identity and ports, but uses the existing account you selected. + +## Connect and prove inference + +1. Open Consumer, then Provider. In **each** app use the community selector to + select/add the same community. Account configuration and selected community are + separate; do not leave either app on Personal space. +2. Enable Compute in Settings → Plugins if needed. +3. In Consumer, open Settings → Compute and choose **Connect to community compute**. +4. In Provider, open Settings → Compute, choose an available model, then turn on + sharing. Wait for model startup and **You're sharing compute**. +5. Initial device registration may change the verified owner roster and stop an + existing session. Reconnect Consumer and toggle Provider off/on if the app asks + you to refresh admission. This does not require new keys. +6. Send a test request in Consumer. Verify a reply and activity/token growth on the + Provider widget. The CPU icon appears only on the actively sharing Provider; + Consumer has Compute settings but no sharing widget icon. + +The built-in test currently allows only 128 output tokens. A reasoning model can +use that budget before producing visible text, yielding “The provider returned no +text.” To separate this from a connection failure, try the consumer's local API +with a larger budget (while its connection is running): + +```sh +curl --fail-with-body --max-time 120 http://127.0.0.1:19338/v1/chat/completions \ + -H 'Content-Type: application/json' \ + -d '{"model":"mesh","messages":[{"role":"user","content":"Say hello in one short sentence."}],"max_tokens":1024,"stream":false}' +``` + +Only use this endpoint locally. The consumer can route to other verified providers +in the community too; a reply alone does not prove which machine served it. Watch +your Provider's activity and counters, or test in a community with only that provider. +Do not share raw runtime status responses: they may contain operational credentials. + +## Optional: run an existing agent in Consumer + +Direct shared inference above does not need an agent. Agent hosting currently +supports **one explicitly configured existing owner-only `buzz-agent` identity** +using the shared-compute provider. It is not a general editor or launcher for every +saved agent/harness. See [agent runner constraints](agents.md). + +Build trusted old Buzz runtime binaries using that repository's build instructions. +Supply a directory containing `buzz-acp`, `buzz-agent`, and `buzz-dev-mcp` when +building the Consumer bundle: + +```sh +BUZZ_COMPUTE_BUILD_ROLE=client \ +BUZZ_AGENT_RUNTIME_SOURCE='/absolute/path/to/old-buzz/target/debug' \ + node scripts/build-compute-pair.mjs +``` + +Add these variables inside the Consumer `else` block of `account-env.sh`: + +```sh +export BUZZ_RUNNER_AGENT='EXISTING_AGENT_PUBLIC_KEY_HEX' +export BUZZ_RUNNER_LIBRARY='/absolute/path/to/profile/agents/managed-agents.json' +export BUZZ_RUNNER_WORKDIR='/absolute/path/to/agent/workspace' +``` + +Use that profile's existing definition and Keychain service, owned by the consumer +account. Its linked definition must select `relay-mesh`; its authorization must +be an unconditional owner attestation. The host verifies these before launch. +The Agents page uses this library too; `BUZZ_AGENT_LIBRARY` is an optional explicit +library override. Do not point the grid at a different account's library. + +Stop the identity's runner in old Buzz, then close that old app. Reopen Consumer to +load launcher changes, connect Compute, and choose **Start agent** on the matching +Agents card. The bundled runtime now hosts it without the old desktop being open. +Do not run both hosts for the same identity. After disconnect/restart, reconnect +Compute and explicitly start the agent again; there is no agent auto-start. + +Open the DM for the exact running identity and send a new message. Normal +one-to-one DMs address that member without needing an @ mention. Replies appear +in the message thread. If two agents have the same name, check their identities; +the wrong one will not wake. Conversation options → **Hide from sidebar** hides +an unused DM locally; **Restore hidden DMs** restores it without deleting history. + +## Widget and troubleshooting + +- Open the Provider widget from the top-right CPU icon or Compute settings. + Left/Right cycles designs. Number keys preview states; **Escape returns to live + activity**. A manual preview can otherwise mask a real request. +- A timeout or changed community admission stops compute rather than continuing + with stale permission. Reconnect and restart the agent. Automatic recovery after + sleep or network interruption is not yet implemented/verified. +- The pair uses web ports 1451/1452, API ports 19337/19338, and console ports + 13131/13132. Close an older copy of these test apps if a port is occupied; the + launcher deliberately refuses to attach to an unrelated process. +- Blank windows named Buzz Foundation may belong to a separate development app. + The pair is named **Buzz Compute Provider** and **Buzz Compute Consumer**. +- Local logs live next to the bundles as `serve-broker.log`, `client-broker.log`, + `serve-app.log`, and `client-app.log`. Runner logs live in the Consumer app-data + directory under `agent-runner/runner.log`. Inspect locally; redact credentials, + account identifiers and conversation content before sharing excerpts. + +## Report a test result + +Include the branch commit, macOS/hardware, one-Mac or two-Mac setup, model, +whether direct inference worked, whether the agent replied, widget behavior, and +steps to reproduce a failure. Do not include keys or full account/runtime dumps. +Current evidence is a local macOS two-account test with real inference and agent +replies; this is not production readiness, cross-platform validation, or a release. diff --git a/public/compute-widget.html b/public/compute-widget.html new file mode 100644 index 000000000..7a29ceef5 --- /dev/null +++ b/public/compute-widget.html @@ -0,0 +1,3 @@ +Compute \ No newline at end of file diff --git a/public/compute-widget.js b/public/compute-widget.js new file mode 100644 index 000000000..eb002cfd2 --- /dev/null +++ b/public/compute-widget.js @@ -0,0 +1,1502 @@ +// Adapted from block/buzz mesh-buddy-window and the four-design native-integration study (Apache-2.0). + +const font = { + " ": "000000000000000", + 0: "111101101101111", + 1: "010110010010111", + 2: "111001111100111", + 3: "111001111001111", + 4: "101101111001001", + 5: "111100111001111", + 6: "111100111101111", + 7: "111001010010010", + 8: "111101111101111", + 9: "111101111001111", + A: "010101111101101", + B: "110101110101110", + C: "111100100100111", + D: "110101101101110", + E: "111100110100111", + F: "111100110100100", + G: "111100101101111", + H: "101101111101101", + I: "111010010010111", + J: "001001001101111", + K: "101101110101101", + L: "100100100100111", + M: "101111111101101", + N: "101111111111101", + O: "111101101101111", + P: "111101111100100", + Q: "111101101111001", + R: "110101110101101", + S: "111100111001111", + T: "111010010010010", + U: "101101101101111", + V: "101101101101010", + W: "101101111111101", + X: "101101010101101", + Y: "101101010010010", + Z: "111001010100111", + "/": "001001010100100", + "-": "000000111000000", + "+": "000010111010000", +}; + +const phases = [ + { id: "sleep", label: "Sleep", word: "SLEEP", duration: 2000 }, + { id: "link", label: "Connecting", word: "LINK", duration: 2000 }, + { id: "warm", label: "Warming", word: "WARM", duration: 2000 }, + { id: "active", label: "Generating", word: "WORKING", duration: 5000 }, + { id: "complete", label: "Complete", word: "SERVED", duration: 2000 }, + { id: "error", label: "Error", word: "ERROR", duration: 2500 }, + { id: "offline", label: "Disconnected", word: "OFFLINE", duration: 2000 }, + { id: "recovery", label: "Recovery", word: "LINK", duration: 2500 }, + { id: "online", label: "Recovered", word: "READY", duration: 2000 }, + { id: "rest", label: "Sleep again", word: "SLEEP", duration: 2000 }, +]; + +const northPose = [ + [0, -3], + [0, -2], + [0, 0], + [-4, 0], + [6, 0], + [-4, -1], + [4, -1], + [-4, 2], + [4, 2], + [-4, 1], + [6, 1], + [0, 2], + [0, 4], + [0, -1], + [1, -4], + [1, -2], + [1, 0], + [1, 2], + [1, 4], + [2, -2], + [2, -1], + [2, 0], + [2, 1], + [2, 2], + [2, 3], + [-1, -4], + [-1, -2], + [-1, 0], + [-5, 0], + [5, 0], + [-5, -1], + [5, -1], + [-5, 2], + [-5, 1], + [5, 1], + [5, 2], + [-1, 2], + [-1, 4], + [-2, -2], + [-2, -1], + [-2, 0], + [-6, 0], + [4, 0], + [-6, 1], + [4, 1], + [-2, 1], + [-2, 2], + [-2, 3], +]; +const northeastPose = [ + [2.1212999999999993, -2.1212999999999993], + [1.4141999999999997, -1.4141999999999997], + [0, 0], + [-2.8284499999999992, -2.8284499999999992], + [4.24265, 4.24265], + [-2.1212999999999993, -3.5355500000000006], + [3.5355500000000006, 2.1212999999999993], + [-4.24265, -1.4141999999999997], + [1.4141999999999997, 4.24265], + [-3.5355500000000006, -2.1212999999999993], + [3.5355500000000006, 4.94975], + [-1.4141999999999997, 1.4141999999999997], + [-2.8284499999999992, 2.828450000000001], + [0.7070999999999998, -0.7070999999999998], + [3.5355500000000006, -2.1212999999999993], + [2.1212999999999993, -0.7070999999999998], + [0.7070999999999998, 0.7070999999999998], + [-0.7070999999999998, 2.1212999999999993], + [-2.1212999999999993, 3.5355500000000006], + [2.8283999999999994, 0], + [2.1212999999999993, 0.7070999999999998], + [1.4141999999999997, 1.4141999999999997], + [0.7070999999999998, 2.1212999999999993], + [0, 2.828450000000001], + [-0.7070999999999998, 3.5355500000000006], + [2.1212999999999993, -3.5355500000000006], + [0.7070999999999998, -2.1212999999999993], + [-0.7070999999999998, -0.7070999999999998], + [-3.5355500000000006, -3.5355500000000006], + [3.5355500000000006, 3.5355500000000006], + [-2.8284499999999992, -4.24265], + [4.24265, 2.828450000000001], + [-4.94975, -2.1212999999999993], + [-4.24265, -2.8284499999999992], + [2.8283999999999994, 4.24265], + [2.1212999999999993, 4.94975], + [-2.1212999999999993, 0.7070999999999998], + [-3.5355500000000006, 2.1212999999999993], + [0, -2.8284499999999992], + [-0.7070999999999998, -2.1212999999999993], + [-1.4141999999999997, -1.4141999999999997], + [-4.24265, -4.24265], + [2.8283999999999994, 2.828450000000001], + [-4.94975, -3.5355500000000006], + [2.1212999999999993, 3.5355500000000006], + [-2.1212999999999993, -0.7070999999999998], + [-2.8284499999999992, 0], + [-3.5355500000000006, 0.7070999999999998], +]; +function beePose(heading, plot, wingFold = 0) { + const source = heading % 2 ? northeastPose : northPose; + const turns = Math.floor(heading / 2); + for (const [pointIndex, point] of source.entries()) { + if (wingFold > 0 && Math.abs(northPose[pointIndex][0]) >= 7 - wingFold) + continue; + let [horizontal, vertical] = point; + for (let index = 0; index < turns; index += 1) { + const prior = horizontal; + horizontal = -vertical; + vertical = prior; + } + plot(horizontal, vertical, 217 / 255); + } +} + +function buffer() { + return { + white: new Float32Array(4096), + red: new Float32Array(4096), + sprite: [], + streaks: [], + }; +} +function dot(frame, horizontal, vertical, brightness = 1, color = "white") { + const column = Math.round(horizontal), + row = Math.round(vertical); + if (column < 0 || column > 63 || row < 0 || row > 63) return; + const address = row * 64 + column; + frame[color][address] = Math.max(frame[color][address], brightness); +} +function label( + frame, + value, + vertical, + scale = 1, + brightness = 0.9, + color = "white", + align = "center", +) { + value = String(value); + let left = + align === "left" + ? 4 + : align === "right" + ? 60 - (value.length * 4 - 1) * scale + : Math.round((64 - (value.length * 4 - 1) * scale) / 2); + for (const character of value) { + const glyph = font[character] || font[" "]; + for (let row = 0; row < 5; row += 1) + for (let column = 0; column < 3; column += 1) { + if (glyph[row * 3 + column] !== "1") continue; + for (let offsetY = 0; offsetY < scale; offsetY += 1) + for (let offsetX = 0; offsetX < scale; offsetX += 1) + dot( + frame, + left + column * scale + offsetX, + vertical + row * scale + offsetY, + brightness, + color, + ); + } + left += 4 * scale; + } +} +function arc( + frame, + centerY, + radius, + start, + end, + brightness = 0.4, + color = "white", +) { + if (radius === 23 && color === "white") { + const fullTurn = Math.PI * 2; + const span = end - start; + for (let index = 0; index < 96; index++) { + const angle = (index * fullTurn) / 96; + const relative = (((angle - start) % fullTurn) + fullTurn) % fullTurn; + if (span <= 0 || (span < fullTurn && relative >= span)) continue; + const horizontal = 32 + Math.sin(angle) * 20, + vertical = centerY - Math.cos(angle) * 20; + const existing = frame.sprite.find( + (dot) => dot[0] === horizontal && dot[1] === vertical && dot[3] === 2.9, + ); + if (existing) existing[2] = Math.max(existing[2], brightness); + else frame.sprite.push([horizontal, vertical, brightness, 2.9]); + } + return; + } + for (let angle = start; angle < end; angle += 0.055) + dot( + frame, + 31.5 + Math.sin(angle) * radius, + centerY - Math.cos(angle) * radius, + brightness, + color, + ); +} +function bee( + frame, + elapsed, + reduced, + sleeping = false, + awake = sleeping ? 0 : 1, +) { + const motion = reduced ? 0 : elapsed; + const lift = (1 - awake) * Math.sin(motion / 600) * 0.6; + const retainedHeading = + returnHeading !== null && + ["complete", "online", "rest"].includes(model.phase); + const angle = retainedHeading + ? model.phase === "rest" + ? awake * returnHeading + ((1 - awake) * Math.PI) / 4 + : returnHeading + : ((1 - awake) * Math.PI) / 4; + const returnProgress = reduced ? 1 : Math.min(1, Math.max(0, elapsed / 700)); + const returnEase = returnProgress * returnProgress * (3 - 2 * returnProgress); + const returning = model.phase === "complete" && returnScale !== null; + const size = returning ? returnScale + (1 - returnScale) * returnEase : 1; + const face = [ + [-2, -2], + [0, -2], + [2, -2], + [-2, -1], + [-1, -1], + [1, -1], + [2, -1], + [-2, 0], + [0, 0], + [2, 0], + ]; + const pose = sleeping + ? northPose + .filter( + ([horizontal, vertical]) => + !(Math.abs(horizontal) <= 2 && vertical >= -2 && vertical <= 0), + ) + .concat(face) + : northPose; + for (const [horizontal, vertical] of pose) { + const rotatedX = horizontal * Math.cos(angle) - vertical * Math.sin(angle); + const rotatedY = horizontal * Math.sin(angle) + vertical * Math.cos(angle); + frame.sprite.push([ + 32 + rotatedX * 2 * size, + 32 + rotatedY * 2 * size + lift, + 0.58 + 0.27 * awake, + 5.6 * size, + ]); + } + if (sleeping) { + for (let index = 0; index < 3; index += 1) { + const age = reduced + ? 0.2 + index * 0.27 + : ((motion + [0, 1530, 3070][index]) % 4600) / 4600; + const horizontal = + 36 + age * 8 + (reduced ? 0 : Math.sin(age * 3 + index) * 0.35); + const vertical = 25 - age * 12; + const ink = + Math.sin(Math.PI * age) ** 6 * 0.65 + + 0.14 * Math.sin(Math.PI * Math.min(1, age / 0.4)) ** 2; + const glyph = font.Z; + for (let row = 0; row < 5; row += 1) + for (let column = 0; column < 3; column += 1) + if (glyph[row * 3 + column] === "1") + dot(frame, horizontal + column, vertical + row, ink); + } + } +} +function draw(_design, current, reduced) { + const { phase, index, elapsed } = current; + const frame = buffer(); + const data = metrics(index, elapsed); + const motion = reduced + ? 900 + : phase.id === "active" + ? Math.max(0, elapsed - 700) * 0.78 + : elapsed; + const active = phase.id === "active"; + const linking = ["link", "recovery"].includes(phase.id); + const failed = phase.id === "error"; + const offline = phase.id === "offline"; + const completed = phase.id === "complete"; + const resting = ["sleep", "rest"].includes(phase.id); + const warming = phase.id === "warm"; + let statusWord = phase.word; + if (active) { + const breath = reduced ? 0.18 : 0.18 + Math.sin(motion / 1400) * 0.04; + if (!linking && !completed) + arc(frame, 32, 23, 0, Math.PI * 2, failed ? 0.14 : breath); + if (linking) + arc( + frame, + 32, + 23, + 0, + (reduced ? 1 : Math.max(0, Math.min(1, (elapsed - 650) / 1200))) * + Math.PI * + 2, + 0.65, + ); + if (completed) + arc( + frame, + 32, + 23, + 0, + (reduced ? 0 : Math.max(0, 1 - elapsed / 1200)) * Math.PI * 2, + 0.6, + ); + if (failed) arc(frame, 32, 23, -0.13, 0.13, 0.65, "red"); + } + if (resting || linking || completed || phase.id === "online") { + const settling = phase.id === "rest"; + const awake = linking + ? reduced + ? 1 + : Math.min(1, elapsed / 650) + : completed + ? reduced + ? 0 + : Math.max(0, 1 - (elapsed - 1200) / 650) + : settling + ? reduced + ? 0 + : Math.max(0, 1 - elapsed / 750) + : resting + ? 0 + : 1; + bee(frame, elapsed, reduced, resting && awake === 0, Math.min(1, awake)); + statusWord = resting + ? awake > 0 + ? "RESTING" + : "SLEEP" + : linking + ? "WAKING" + : phase.word; + } else if (warming) { + const fill = reduced ? 0.8 : Math.min(1, elapsed / 1800); + for (let row = 0; row < 64; row += 1) + for (let column = 0; column < 64; column += 1) { + const front = 64 - fill * 80; + const gradient = Math.max(0, Math.min(1, (row - front) / 24)); + const softness = 0.8 + 0.2 * Math.cos((column - 31.5) / 32); + dot(frame, column, row, gradient * softness * 0.2); + } + statusWord = "WARMING UP"; + } else if (active) { + statusWord = "WORKING"; + const headings = [0, 1, 2, 3, 4, 5, 6, 7]; + let directionSeed = + (Math.floor(model.changed * 1000) + + Math.floor(motion / (2800 * 8)) * 2654435761) >>> + 0; + for (let slot = 7; slot > 1; slot--) { + directionSeed = (Math.imul(directionSeed, 1664525) + 1013904223) >>> 0; + const target = 1 + (directionSeed % slot); + [headings[slot], headings[target]] = [headings[target], headings[slot]]; + } + const visit = reduced ? 0 : Math.floor(motion / 2800) % headings.length; + const age = reduced ? 1900 : motion % 2800; + const centerX = 32; + const entry = reduced ? 1 : Math.min(1, Math.max(0, elapsed / 700)); + const entryEase = entry * entry * (3 - 2 * entry); + const centerY = 32; + const direction = + entry < 1 + ? 0 + : headings[ + (visit + (!reduced && age >= 2150 ? 1 : 0)) % headings.length + ]; + lastFlightHeading = (direction * Math.PI) / 4; + const heading = (direction * Math.PI) / 4; + const turnProgress = reduced + ? 0 + : Math.max(0, Math.min(1, (age - 1750) / 400)); + const turnEase = turnProgress * turnProgress * (3 - 2 * turnProgress); + const nextDirection = headings[(visit + 1) % headings.length]; + const turnDistance = ((nextDirection - headings[visit] + 12) % 8) - 4; + const indicatorHeading = + ((headings[visit] + turnDistance * turnEase) * Math.PI) / 4; + arc(frame, 32, 23, indicatorHeading - 0.09, indicatorHeading + 0.1, 0.8); + const flowerApproachAge = + age >= 2350 ? age - 2350 : age + (motion >= 2800 ? 450 : 0); + const flowerApproachDuration = motion < 2800 && age < 2350 ? 1600 : 2050; + const flowerProgress = Math.min( + 1, + flowerApproachAge / flowerApproachDuration, + ); + const flowerTravel = 1 - (1 - flowerProgress) ** 2; + const flowerDistance = 15 * (1 - flowerTravel); + const flowerX = centerX + Math.sin(heading) * flowerDistance; + const flowerY = centerY - Math.cos(heading) * flowerDistance; + const flowerFadeProgress = Math.min(1, flowerApproachAge / 240); + const flowerFade = reduced + ? 1 + : flowerFadeProgress * flowerFadeProgress * (3 - 2 * flowerFadeProgress); + const covered = Math.max(0, Math.min(1, (flowerTravel - 0.75) / 0.25)); + const flowerInk = + (reduced ? 0.35 : 0.55) * flowerFade * (1 - covered * 0.92) * entryEase; + const flowerCircles = [ + [27, 27], + [27, 75], + [75, 27], + [41, 41], + [27, 7], + [27, 55], + [75, 55], + [7, 27], + [55, 27], + [55, 75], + [7, 55], + [55, 7], + [55, 55], + ]; + const backgroundVisit = Math.floor(motion / 2800) + (age >= 2350 ? 1 : 0); + const backgroundAge = + age >= 2350 ? age - 2350 : age + (motion >= 2800 ? 450 : 0); + const backgroundFade = Math.min(1, backgroundAge / 400); + const landingFade = + age >= 2350 ? 1 : Math.max(0, Math.min(1, (1750 - age) / 350)); + const depthInk = reduced + ? 0 + : 0.25 * + backgroundFade * + backgroundFade * + (3 - 2 * backgroundFade) * + landingFade * + landingFade * + (3 - 2 * landingFade) * + entryEase; + const backgroundDistance = + ((Math.min(backgroundAge, 1600) + + Math.max(0, backgroundAge - 1600) * 0.5) * + 15) / + 3200; + const placementShift = Math.sin(backgroundVisit * 2.4) * 3; + for (const [seedX, seedY] of [ + [-13 + placementShift, -8], + [8, 12 - placementShift], + ]) { + const backgroundX = seedX - Math.sin(heading) * backgroundDistance; + const backgroundY = seedY + Math.cos(heading) * backgroundDistance; + for (let stripeDot = 0; stripeDot < 9; stripeDot++) { + const stripeOffset = (stripeDot - 4) * 0.31; + const offsetX = backgroundX + Math.sin(heading) * stripeOffset, + offsetY = backgroundY - Math.cos(heading) * stripeOffset; + if (Math.hypot(offsetX, offsetY) < 21) + frame.sprite.push([ + centerX + offsetX, + centerY + offsetY, + depthInk, + 1.4, + ]); + } + } + for (const [horizontal, vertical] of flowerCircles) + frame.sprite.push([ + flowerX + (horizontal - 41) / 20, + flowerY + (vertical - 41) / 20, + (flowerInk * 217) / 255, + 2.8, + ]); + const departureAge = + age >= 2350 ? age - 2350 : motion >= 2800 ? age + 450 : -1; + if (!reduced && departureAge >= 0 && departureAge < 1600) { + const departure = departureAge / 1600; + const distance = 15 * departure; + const ink = + 0.55 * + Math.min(1, 0.08 + departureAge / 200) * + Math.max(0, Math.min(1, (750 - departureAge) / 300)); + for (const [horizontal, vertical] of flowerCircles) + frame.sprite.push([ + centerX - Math.sin(heading) * distance + (horizontal - 41) / 20, + centerY + Math.cos(heading) * distance + (vertical - 41) / 20, + (ink * 217) / 255, + 2.8, + ]); + } + const settle = Math.max(0, Math.min(1, (age - 1150) / 450)); + const takeoff = Math.max(0, Math.min(1, (age - 2350) / 450)); + const landing = + settle * + settle * + (3 - 2 * settle) * + (1 - takeoff * takeoff * (3 - 2 * takeoff)); + const flightProgress = Math.min(1, age / 1600); + const flightDip = reduced + ? 0 + : Math.sin(Math.PI * flightProgress) ** 2 * 0.7 * entryEase; + const beeScale = reduced + ? 1.15 + : (2 - 0.85 * entryEase) * (1 - 0.08 * landing); + const flapping = !reduced && entry >= 1 && (age < 1500 || age >= 2350); + const wingFold = flapping ? [0, 1, 2, 1][Math.floor(motion / 110) % 4] : 0; + beePose( + direction, + (horizontal, vertical, ink) => + frame.sprite.push([ + centerX + horizontal * beeScale, + centerY + flightDip + vertical * beeScale, + ink, + 2.8 * beeScale, + ]), + wingFold, + ); + const flightStreakAge = + age >= 2350 ? age - 2350 : age + (motion >= 2800 ? 450 : 0); + const flightStreakDuration = motion < 2800 && age < 2350 ? 1600 : 2050; + const streakTime = + current.streakPreviewTime ?? + (flightStreakAge / flightStreakDuration) * 3960; + for (const [lane, distance, end, delay] of [ + [0, 6, 8.8, 0], + [-1.4, 7.4, 7.84, 220], + [1.4, 6.6, 9.7, 440], + [-1.4, 8.346, 9.6, 660], + [0, 9.4, 10.3, 880], + ]) { + const streakAge = + (current.streakPreviewTime !== undefined + ? streakTime + : Math.min(streakTime, 3959.999) % 1980) - delay; + const phase = + current.streakPreviewTime !== undefined + ? Math.max(0, streakAge) % 1600 + : Math.max(0, streakAge); + const centerFadeIn = Math.min(1, phase / 150); + const centerFadeOut = Math.max(0, Math.min(1, (1500 - phase) / 400)); + const flightInk = reduced + ? 0 + : current.streakPreviewTime !== undefined + ? 1 + : age >= 2350 + ? 1 + : Math.max(0, Math.min(1, (1600 - age) / 350)); + const takeoffInk = streakTime < 150 ? 0.3 * (1 - streakTime / 150) : 0; + const pulse = + delay === 0 + ? Math.max( + takeoffInk, + centerFadeIn * centerFadeIn * (3 - 2 * centerFadeIn), + ) * + centerFadeOut * + centerFadeOut * + (3 - 2 * centerFadeOut) + : streakAge < 0 || phase >= 1100 + ? 0 + : Math.sin((Math.PI * phase) / 1100) ** 2; + const ink = pulse * 0.65 * flightInk; + const point = (vertical) => [ + centerX + lane * Math.cos(heading) - vertical * Math.sin(heading), + centerY + + flightDip + + lane * Math.sin(heading) + + vertical * Math.cos(heading), + ]; + frame.streaks.push([...point(distance), ...point(end), ink]); + } + } else if (failed) { + const fall = reduced ? 1 : Math.max(0, Math.min(1, (elapsed - 450) / 650)); + const horizontal = reduced + ? 51 + : elapsed < 450 + ? 8 + (26 * elapsed) / 450 + : 34 + 17 * (2 * fall - fall * fall); + const vertical = 22 + 29 * fall * fall; + if (!reduced && elapsed < 1350) + for (let step = 1; step <= 5; step += 1) { + const prior = Math.max(0, elapsed - step * 45); + const priorFall = Math.max(0, Math.min(1, (prior - 450) / 650)); + const fade = Math.max(0, 1 - Math.max(0, elapsed - 1100) / 250); + dot( + frame, + prior < 450 + ? 8 + (26 * prior) / 450 + : 34 + 17 * (2 * priorFall - priorFall * priorFall), + 22 + 29 * priorFall * priorFall, + 0.22 * (1 - step / 6) * fade, + ); + } + const heading = + 2 + Math.round(Math.atan2(58 * fall, 34 * (1 - fall)) / (Math.PI / 4)); + beePose(heading, (offsetX, offsetY, ink) => + frame.sprite.push([horizontal + offsetX, vertical + offsetY, ink * 0.7]), + ); + if (fall === 1) + for (let star = 0; star < 3; star += 1) { + const centerX = 44 + star * 7; + const centerY = star === 1 ? 41 : 43; + const ink = reduced + ? 0.5 + : 0.4 + 0.18 * Math.sin((elapsed - 1100) / 450 + star * 2); + dot(frame, centerX, centerY, ink); + dot(frame, centerX - 1, centerY, ink * 0.7); + dot(frame, centerX + 1, centerY, ink * 0.7); + dot(frame, centerX, centerY - 1, ink * 0.7); + dot(frame, centerX, centerY + 1, ink * 0.7); + } + const message = reduced + ? 1 + : Math.max(0, Math.min(1, (elapsed - 1000) / 300)); + dot(frame, 32, 18, 0.65 * message, "red"); + statusWord = "ERROR"; + } else if (offline) { + for (const radius of [7, 12, 17]) arc(frame, 34, radius, -0.8, 0.8, 0.5); + dot(frame, 32, 34, 0.6); + for (let step = 0; step < 19; step += 1) + dot(frame, 23 + step, 35 - step, 0.85); + statusWord = "OFFLINE"; + } else { + statusWord = phase.word; + } + label( + frame, + statusWord, + 4, + 1, + active && !reduced ? 0.7 + 0.12 * Math.sin(elapsed / 450) : 0.7, + "white", + "center", + ); + for (const [horizontal, vertical] of [ + [4, 55], + [6, 55], + [5, 57], + [4, 59], + [6, 59], + ]) + dot(frame, horizontal, vertical, 0.45); + label( + frame, + ` ${model.valid && model.peers !== null ? formatCount(model.peers) : "--"}`, + 55, + 1, + 0.45, + "white", + "left", + ); + if (showTotal) label(frame, data.total, 55, 1, 0.45, "white", "right"); + return frame; +} + +const canvas = document.getElementById("display"); +const reduce = matchMedia("(prefers-reduced-motion: reduce)"); +const invoke = + window.__TAURI__?.core?.invoke || window.__TAURI_INTERNALS__?.invoke; +const model = { + phase: "offline", + changed: performance.now(), + runtime: "off", + total: null, + lastTotal: null, + inflight: 0, + batchBase: null, + batch: null, + rate: null, + peers: null, + rateHistory: [], + lastActive: 0, + valid: false, +}; +const showTotal = true; + +function metrics() { + return { + total: formatCount(model.total), + request: "--", + rate: model.rate === null ? "--" : String(Math.round(model.rate)), + }; +} +function formatCount(value) { + if (value === null) return "--"; + if (value < 10000) return String(Math.floor(value)); + if (value < 1000000) return (value / 1000).toFixed(1) + "K"; + return (value / 1000000).toFixed(1) + "M"; +} +let lastFrame = null, + workEntry = null, + requestWaking = false, + returnScale = null, + finishLandingAt = null, + lastFlightHeading = 0, + returnHeading = null; +function setPhase(phase) { + if (!["link", "online"].includes(phase)) requestWaking = false; + if (model.phase !== phase) { + if (phase === "complete" && model.phase === "active") + returnHeading = lastFlightHeading; + else if (!["complete", "online", "rest"].includes(phase)) + returnHeading = null; + returnScale = + phase === "complete" && model.phase === "active" + ? (lastFrame?.sprite.at(-1)?.[3] ?? 2.8) / 5.6 + : null; + workEntry = + phase === "active" && + ["sleep", "rest", "online", "link", "warm"].includes(model.phase) + ? lastFrame + : null; + model.phase = phase; + model.changed = performance.now(); + } +} +function ingest(usage, status) { + const now = performance.now(); + if ( + !status || + typeof status.state !== "string" || + !usage || + !Number.isFinite(usage.tokensServed) || + !Number.isFinite(usage.inflight) + ) + throw new Error("Invalid mesh status"); + const total = Math.max(0, usage.tokensServed), + inflight = Math.max(0, usage.inflight); + const delta = + model.lastTotal === null ? 0 : Math.max(0, total - model.lastTotal); + if (model.lastTotal !== null && total < model.lastTotal) { + model.batchBase = null; + model.batch = null; + } + if (inflight > 0 && model.inflight === 0) { + model.batchBase = model.lastTotal; + model.batch = + model.batchBase === null ? null : Math.max(0, total - model.batchBase); + } + if (model.batchBase !== null) + model.batch = Math.max(0, total - model.batchBase); + if (delta > 0 && model.inflight === 0 && inflight === 0) { + model.batch = delta; + } + const completed = inflight === 0 && (model.inflight > 0 || delta > 0); + if ( + total < model.total || + (inflight > 0 && model.inflight === 0) || + status.state !== "running" + ) { + model.rateHistory = []; + model.ratePeak = 1; + } + model.total = total; + model.lastTotal = total; + model.inflight = inflight; + model.rate = Number.isFinite(usage.tokensPerSecond) + ? Math.max(0, usage.tokensPerSecond) + : null; + model.peers = Number.isFinite(usage.peers) + ? Math.max(0, Math.floor(usage.peers)) + : null; + model.valid = true; + if (inflight > 0 && status.state === "running") { + model.rateHistory.push(model.rate); + model.rateSampleAt = now; + model.ratePeak = Math.max(model.ratePeak || 1, model.rate || 0); + if (model.rateHistory.length > 25) model.rateHistory.shift(); + } + const priorRuntime = model.runtime; + model.runtime = status.state; + if (inflight > 0 || status.state !== "running") finishLandingAt = null; + if (inflight > 0 || delta > 0) model.lastActive = now; + if (status.state === "failed") setPhase("error"); + else if (status.state === "starting") { + if (priorRuntime !== "starting") setPhase("link"); + } else if (status.state !== "running") setPhase("offline"); + else if (inflight > 0) { + if (["sleep", "rest"].includes(model.phase) && !reduce.matches) { + requestWaking = true; + setPhase("link"); + } else if (!requestWaking) setPhase("active"); + } else if (completed) { + if (model.phase === "active" && !reduce.matches) { + if (finishLandingAt === null) { + const flightAge = + (Math.max(0, now - model.changed - 700) * 0.78) % 2800; + const remaining = + flightAge < 1600 + ? 1600 - flightAge + : flightAge < 2150 + ? 0 + : 2800 - flightAge + 1600; + finishLandingAt = now + remaining / 0.78 + 120; + } + } else setPhase("complete"); + } else if ( + priorRuntime !== "running" || + ["error", "offline", "link", "warm"].includes(model.phase) + ) { + model.lastActive = now; + setPhase("online"); + } +} +async function poll() { + if (!invoke) { + setPhase("offline"); + return; + } + let timeout; + const deadline = new Promise((_, reject) => { + timeout = setTimeout( + () => reject(new Error("Compute status timed out")), + 8000, + ); + }); + try { + const status = await Promise.race([ + invoke("community_compute_status"), + deadline, + ]); + if (status.generation !== model.generation) { + model.generation = status.generation; + model.total = null; + model.lastTotal = null; + model.batchBase = null; + model.batch = null; + model.rateHistory = []; + } + if (status.usage) ingest(status.usage, status); + else { + model.valid = false; + model.rate = null; + model.inflight = 0; + model.runtime = status.state; + setPhase( + status.state === "failed" + ? "error" + : status.state === "starting" + ? "link" + : "offline", + ); + } + } catch (error) { + model.valid = false; + model.rate = null; + setPhase("offline"); + console.warn("Compute status unavailable", error); + } finally { + clearTimeout(timeout); + setTimeout(poll, 1000); + } +} +function advancePresentation(now) { + if (finishLandingAt !== null) { + if ( + model.phase !== "active" || + !model.valid || + model.runtime !== "running" || + model.inflight > 0 + ) + finishLandingAt = null; + else if (now >= finishLandingAt) { + finishLandingAt = null; + setPhase("complete"); + return; + } + } + if (requestWaking && model.runtime === "running" && model.valid) { + if (model.phase === "link" && now - model.changed >= 900) + setPhase("online"); + else if (model.phase === "online" && now - model.changed >= 350) + setPhase(model.inflight > 0 ? "active" : "online"); + } + if ( + model.runtime === "starting" && + model.phase === "link" && + now - model.changed >= 2000 + ) + setPhase("warm"); + if (model.runtime === "running" && model.valid && model.inflight === 0) { + if (model.phase === "complete" && now - model.changed >= 2000) + setPhase("online"); + if (model.phase === "online" && now - model.lastActive >= 10000) + setPhase("rest"); + if (model.phase === "rest" && now - model.changed >= 2000) + setPhase("sleep"); + } +} +const variations = ["bee", "orbit", "signal", "original"]; +let originalStartedAt = performance.now(); +let inspectedState = null; +let variation = "bee"; +try { + const saved = localStorage.getItem("mesh-buddy-variation"); + if (variations.includes(saved)) variation = saved; +} catch {} +function changeVariation(offset) { + originalStartedAt = performance.now(); + variation = + variations[ + (variations.indexOf(variation) + offset + variations.length) % + variations.length + ]; + workEntry = null; + lastFrame = null; + try { + localStorage.setItem("mesh-buddy-variation", variation); + } catch {} +} +window.addEventListener("keydown", (event) => { + if ( + event.altKey || + event.metaKey || + event.ctrlKey || + event.shiftKey || + event.repeat + ) + return; + if (event.key === "Escape") { + inspectedState = null; + render(performance.now(), false); + event.preventDefault(); + return; + } + if (/^[0-9]$/.test(event.key)) { + const index = event.key === "0" ? 9 : Number(event.key) - 1; + inspectedState = { index, started: performance.now() }; + workEntry = null; + render(performance.now(), false); + event.preventDefault(); + return; + } + if (event.key === "ArrowLeft" || event.key === "ArrowRight") { + event.preventDefault(); + changeVariation(event.key === "ArrowLeft" ? -1 : 1); + render(performance.now(), false); + } +}); +function drawInstrument(current, reduced) { + const frame = buffer(), + active = current.phase.id === "active", + warming = current.phase.id === "warm"; + const elapsed = reduced ? 1100 : current.elapsed; + if (warming) + for (let row = 5; row < 59; row += 2) + for (let column = 5; column < 59; column += 2) { + const fill = reduced ? 0.7 : Math.min(1, elapsed / 2500); + dot( + frame, + column, + row, + Math.max(0, Math.min(1, (row - (59 - fill * 54)) / 6)) * 0.28, + ); + } + if (variation === "orbit") { + for (let point = 0; point < 82; point++) { + const angle = (point * Math.PI * 2) / 82; + const offset = + (((angle - elapsed * 0.0014) % (Math.PI * 2)) + Math.PI * 2) % + (Math.PI * 2); + const ink = active && offset < 1.15 ? 0.84 : 0.2; + frame.sprite.push([ + 31.5 + Math.sin(angle) * 19.5, + 31.5 - Math.cos(angle) * 19.5, + ink, + 2.9, + ]); + } + } else { + for (let column = 12; column <= 51; column++) { + const unit = (column - 12) / 39, + envelope = Math.sin(unit * Math.PI) ** 2; + if ( + ["error", "offline"].includes(current.phase.id) && + column > 28 && + column < 35 + ) + continue; + dot( + frame, + column, + 43 + + (active + ? Math.sin(unit * Math.PI * 5 - elapsed / 240) * 4.5 * envelope + : 0), + active ? 0.42 + 0.4 * envelope : 0.2, + ); + } + } + const top = variation === "orbit" ? 29 : 24; + label(frame, current.phase.word, top, variation === "orbit" ? 1 : 2, 0.9); + if (active && !reduced) { + const sweep = ((elapsed % 1800) / 1800) * 70 - 3; + for (let row = top; row < 34; row++) + for (let column = 0; column < 64; column++) + frame.white[row * 64 + column] *= + 0.72 + + 0.08 * Math.sin((elapsed * Math.PI * 2) / 1800) + + 0.2 * Math.exp(-(((column - sweep) / 4) ** 2)); + } + label(frame, formatCount(model.total), 55, 1, 0.6); + return frame; +} +function drawOriginal(current, reduced, liveModel = model) { + const frame = buffer(), + pixels = frame.white, + GRID = 64, + INTRO_MS = 1800, + INTRO_FADE_MS = 350; + const introStartedAt = originalStartedAt, + reduceMotion = reduced; + const model = { + state: { + sleep: "SLEEP", + link: "REQUEST", + warm: "BOOT", + active: "WORKING", + complete: "SERVED", + error: "ERROR", + offline: "OFF", + recovery: "REQUEST", + online: "ONLINE", + rest: "SLEEP", + }[current.phase.id], + tokens: liveModel.total, + nodes: liveModel.peers ?? "--", + inflight: liveModel.inflight, + tokenDelta: liveModel.batch ?? 0, + history: Array(52) + .fill(0) + .concat(liveModel.rateHistory.map((value) => value ?? 0)) + .slice(-52), + }; + const lastFrame = { set() {} }; + const FONT = { + " ": ["000", "000", "000", "000", "000"], + "+": ["000", "010", "111", "010", "000"], + ".": ["000", "000", "000", "000", "100"], + 0: ["111", "101", "101", "101", "111"], + 1: ["010", "110", "010", "010", "111"], + 2: ["111", "001", "111", "100", "111"], + 3: ["111", "001", "111", "001", "111"], + 4: ["101", "101", "111", "001", "001"], + 5: ["111", "100", "111", "001", "111"], + 6: ["111", "100", "111", "101", "111"], + 7: ["111", "001", "010", "010", "010"], + 8: ["111", "101", "111", "101", "111"], + 9: ["111", "101", "111", "001", "111"], + A: ["010", "101", "111", "101", "101"], + B: ["110", "101", "110", "101", "110"], + D: ["110", "101", "101", "101", "110"], + E: ["111", "100", "110", "100", "111"], + F: ["111", "100", "110", "100", "100"], + G: ["111", "100", "101", "101", "111"], + H: ["101", "101", "111", "101", "101"], + I: ["111", "010", "010", "010", "111"], + K: ["101", "101", "110", "101", "101"], + L: ["100", "100", "100", "100", "111"], + N: ["101", "111", "111", "111", "101"], + O: ["111", "101", "101", "101", "111"], + P: ["111", "101", "111", "100", "100"], + Q: ["111", "101", "101", "111", "001"], + R: ["110", "101", "110", "101", "101"], + S: ["111", "100", "111", "001", "111"], + T: ["111", "010", "010", "010", "010"], + U: ["101", "101", "101", "101", "111"], + V: ["101", "101", "101", "101", "010"], + W: ["101", "101", "111", "111", "101"], + Y: ["101", "101", "010", "010", "010"], + Z: ["111", "001", "010", "100", "111"], + }; + + const BEE_BODY_DOTS = [ + [8, 2], + [10, 2], + [9, 3], + [7, 4], + [8, 4], + [9, 4], + [10, 4], + [11, 4], + [7, 5], + [9, 5], + [11, 5], + [7, 6], + [8, 6], + [9, 6], + [10, 6], + [11, 6], + [7, 7], + [11, 7], + [7, 8], + [8, 8], + [9, 8], + [10, 8], + [11, 8], + [7, 9], + [11, 9], + [8, 10], + [9, 10], + [10, 10], + ]; + const BEE_LEFT_WING_DOTS = [ + [4, 5], + [5, 5], + [3, 6], + [4, 6], + [5, 6], + [3, 7], + [4, 7], + [5, 7], + [4, 8], + [5, 8], + ]; + const BEE_RIGHT_WING_DOTS = BEE_LEFT_WING_DOTS.map(([x, y]) => [18 - x, y]); + + function put(x, y, value = 1) { + if (x < 0 || y < 0 || x >= GRID || y >= GRID) return; + pixels[y * GRID + x] = Math.max(pixels[y * GRID + x], value); + } + + function drawText(text, x, y, scale = 1, value = 1) { + let cursor = x; + for (const character of text) { + const glyph = FONT[character] || FONT[" "]; + for (let row = 0; row < glyph.length; row += 1) { + for (let column = 0; column < 3; column += 1) { + if (glyph[row][column] !== "1") continue; + for (let dy = 0; dy < scale; dy += 1) { + for (let dx = 0; dx < scale; dx += 1) { + put(cursor + column * scale + dx, y + row * scale + dy, value); + } + } + } + } + cursor += (character === "." ? 2 : 4) * scale; + } + } + + function textWidth(text, scale = 1) { + const width = Array.from(text).reduce( + (sum, character) => sum + (character === "." ? 2 : 4) * scale, + 0, + ); + return Math.max(0, width - scale); + } + + function drawCentered(text, y, scale = 1, value = 1) { + drawText( + text, + Math.floor((GRID - textWidth(text, scale)) / 2), + y, + scale, + value, + ); + } + + function formatCount(value) { + if (value === null) return "--"; + if (value < 1000) return String(value); + if (value < 1000000) return `${Math.floor(value / 100) / 10}K`; + return `${Math.floor(value / 100000) / 10}M`; + } + + function drawLine(x0, y0, x1, y1, value) { + const dx = Math.abs(x1 - x0); + const sx = x0 < x1 ? 1 : -1; + const dy = -Math.abs(y1 - y0); + const sy = y0 < y1 ? 1 : -1; + let error = dx + dy; + while (true) { + put(x0, y0, value); + if (x0 === x1 && y0 === y1) break; + const twice = error * 2; + if (twice >= dy) { + error += dy; + x0 += sx; + } + if (twice <= dx) { + error += dx; + y0 += sy; + } + } + } + + function drawSparkline(value, compact) { + const values = model.history; + const min = Math.min(...values); + const max = Math.max(...values); + const range = Math.max(1, max - min); + const left = compact ? 6 : 4; + const right = compact ? 57 : 59; + const top = compact ? 40 : 45; + const bottom = compact ? 46 : 51; + for (let index = 1; index < values.length; index += 1) { + const previousX = + left + Math.round(((index - 1) / (values.length - 1)) * (right - left)); + const currentX = + left + Math.round((index / (values.length - 1)) * (right - left)); + const previousY = + bottom - + Math.round(((values[index - 1] - min) / range) * (bottom - top)); + const currentY = + bottom - Math.round(((values[index] - min) / range) * (bottom - top)); + drawLine(previousX, previousY, currentX, currentY, value); + } + } + + function drawSleepingZs(now, value) { + for (let index = 0; index < 3; index += 1) { + const phase = (now / 2800 + index / 3) % 1; + const opacity = Math.sin(phase * Math.PI) * value * (0.45 + index * 0.14); + const x = Math.round( + 44 + phase * 10 + Math.sin(phase * Math.PI * 2) * 1.5, + ); + const y = Math.round(18 - phase * 15 + Math.sin(phase * Math.PI * 4)); + drawText("Z", x, y, 1, opacity); + } + } + + function drawRequestMotion(now) { + const left = 8; + const width = 48; + const head = Math.floor((now / 55) % width); + for (let trail = 0; trail < 7; trail += 1) { + const x = left + ((head - trail + width) % width); + const value = Math.max(0.12, 0.9 - trail * 0.12); + put(x, 12, value); + if (trail < 3) put(x, 13, value * 0.7); + } + } + + function drawWorkingMotion(now) { + for (let index = 0; index < 9; index += 1) { + const x = 11 + index * 5; + const wave = (Math.sin(now / 180 + index * 1.35) + 1) / 2; + const height = 2 + Math.round(wave * 7); + for (let offset = 0; offset < height; offset += 1) { + put(x, 17 - offset, 0.28 + (offset / height) * 0.62); + put(x + 1, 17 - offset, 0.2 + (offset / height) * 0.45); + } + } + + const railPosition = 20 + Math.floor((now / 70) % 23); + for (let trail = 0; trail < 5; trail += 1) { + const y = 20 + ((railPosition - 20 - trail + 23) % 23); + const value = 0.78 - trail * 0.13; + put(2, y, value); + put(61, 62 - y, value); + } + } + + function drawServedMotion(now) { + const phase = (now / 700) % 1; + const radius = 2 + phase * 8; + const opacity = Math.sin(phase * Math.PI) * 0.8; + for (let index = 0; index < 20; index += 1) { + const angle = (index / 20) * Math.PI * 2; + put( + Math.round(32 + Math.cos(angle) * radius), + Math.round(13 + Math.sin(angle) * radius * 0.48), + opacity, + ); + } + } + + function drawBeeDots(dots, originX, originY, xShift, value) { + for (const [x, y] of dots) { + const left = originX + x * 2 + xShift; + const top = originY + y * 2; + put(left, top, value); + put(left + 1, top, value); + put(left, top + 1, value); + put(left + 1, top + 1, value); + } + } + + function drawStartupBee(now) { + const elapsed = now - introStartedAt; + const fadeStart = INTRO_MS - INTRO_FADE_MS; + const fade = + elapsed > fadeStart + ? Math.max(0, 1 - (elapsed - fadeStart) / INTRO_FADE_MS) + : 1; + const bob = reduceMotion ? 0 : Math.round(Math.sin(now / 210)); + const flap = reduceMotion ? 0 : Math.round((Math.sin(now / 70) + 1) * 0.75); + const originX = 13; + const originY = 16 + bob; + const breath = reduceMotion ? 1 : 0.82 + Math.sin(now / 170) * 0.18; + + drawBeeDots(BEE_LEFT_WING_DOTS, originX, originY, -flap, fade * 0.72); + drawBeeDots(BEE_RIGHT_WING_DOTS, originX, originY, flap, fade * 0.72); + drawBeeDots(BEE_BODY_DOTS, originX, originY, 0, fade * breath); + } + + function composeFrame(now) { + pixels.fill(0); + if (now - introStartedAt < INTRO_MS) { + canvas.dataset.phase = "intro"; + drawStartupBee(now); + lastFrame.set(pixels); + return; + } + canvas.dataset.phase = "status"; + const compact = Math.min(canvas.clientWidth, canvas.clientHeight) < 280; + const state = model.manualState || model.state; + const muted = state === "OFF"; + const breath = state === "SLEEP" ? 0.68 + Math.sin(now / 900) * 0.18 : 1; + const activePulse = + state === "REQUEST" || state === "WORKING" + ? 0.82 + Math.sin(now / 170) * 0.18 + : 1; + const value = muted ? 0.24 : breath * activePulse; + + drawCentered(state, compact ? 19 : 22, 2, value); + drawSparkline(muted ? 0.16 : 0.48, compact); + if (state === "SLEEP") drawSleepingZs(now, breath); + if (state === "REQUEST") drawRequestMotion(now); + if (state === "WORKING") drawWorkingMotion(now); + if (state === "SERVED") drawServedMotion(now); + + const tokens = `TOK ${formatCount(model.tokens)}`; + const nodes = `${model.nodes}ND`; + const counterInset = compact ? 6 : 4; + const displaySize = Math.min(canvas.clientWidth, canvas.clientHeight); + const counterY = compact ? 52 : displaySize >= 480 ? 56 : 55; + drawText(tokens, counterInset, counterY, 1, muted ? 0.2 : 0.72); + drawText( + nodes, + GRID - counterInset - textWidth(nodes), + counterY, + 1, + muted ? 0.2 : 0.72, + ); + + if (state === "SERVED" && model.tokenDelta > 0) { + drawCentered(`+${formatCount(model.tokenDelta)}`, 18, 1, 0.65); + } + if (state === "BOOT") { + const phase = Math.floor(now / 180) % 4; + for (let index = 0; index < 3; index += 1) { + put(29 + index * 3, 40, index <= phase % 3 ? 0.85 : 0.16); + } + } + if (model.inflight > 0 && Math.floor(now / 280) % 2 === 0) { + put(59, 4, 0.9); + put(60, 4, 0.9); + put(59, 5, 0.9); + put(60, 5, 0.9); + } + } + + composeFrame( + reduced ? originalStartedAt + INTRO_MS + 1100 : performance.now(), + ); + return frame; +} +function render(now, schedule = true) { + advancePresentation(now); + const index = + inspectedState?.index ?? + phases.findIndex((phase) => phase.id === model.phase); + const elapsed = inspectedState + ? Math.max(0, now - inspectedState.started) + : Math.max(0, now - model.changed) * + (requestWaking && model.phase === "link" ? 1850 / 900 : 1); + const current = { phase: phases[index], index, elapsed }; + const frame = + variation === "bee" + ? draw("orbit", current, reduce.matches) + : variation === "original" + ? drawOriginal(current, reduce.matches) + : drawInstrument(current, reduce.matches); + if ( + workEntry && + !inspectedState && + !reduce.matches && + now - model.changed < 850 + ) { + const progress = Math.max(0, (now - model.changed) / 850); + const blend = progress * progress * (3 - 2 * progress); + for (let address = 0; address < 4096; address++) { + frame.white[address] = + frame.white[address] * blend + workEntry.white[address] * (1 - blend); + frame.red[address] = + frame.red[address] * blend + workEntry.red[address] * (1 - blend); + } + } else workEntry = null; + lastFrame = frame; + const width = Math.max( + 1, + Math.round(canvas.clientWidth * (devicePixelRatio || 1)), + ); + const height = Math.max( + 1, + Math.round(canvas.clientHeight * (devicePixelRatio || 1)), + ); + if (canvas.width !== width || canvas.height !== height) { + canvas.width = width; + canvas.height = height; + } + const context = canvas.getContext("2d"); + context.setTransform(width / 512, 0, 0, height / 512, 0, 0); + context.fillStyle = "#000"; + context.fillRect(0, 0, 512, 512); + for (let address = 0; address < 4096; address++) { + const white = frame.white[address], + red = frame.red[address], + intensity = Math.max(white, red); + if (intensity < 0.01) continue; + const redWeight = red / (white + red || 1); + context.fillStyle = `rgba(${255 - 40 * redWeight},${255 - 140 * redWeight},${255 - 149 * redWeight},${intensity})`; + context.beginPath(); + context.arc( + (address % 64) * 8 + 4, + Math.floor(address / 64) * 8 + 4, + 2.9, + 0, + Math.PI * 2, + ); + context.fill(); + } + for (const [horizontal, vertical, ink, radius = 2.8] of frame.sprite) { + context.fillStyle = `rgba(255,255,255,${ink})`; + context.beginPath(); + context.arc(horizontal * 8 + 4, vertical * 8 + 4, radius, 0, Math.PI * 2); + context.fill(); + } + for (const [startX, startY, endX, endY, ink] of frame.streaks) { + context.strokeStyle = `rgba(255,255,255,${ink})`; + context.lineWidth = 2.8; + context.lineCap = "round"; + context.beginPath(); + context.moveTo(startX * 8 + 4, startY * 8 + 4); + context.lineTo(endX * 8 + 4, endY * 8 + 4); + context.stroke(); + } + canvas.dataset.phase = model.phase; + canvas.dataset.total = String(model.total ?? ""); + canvas.dataset.batch = String(model.batch ?? ""); + canvas.setAttribute( + "aria-label", + `${inspectedState ? "Visual state preview, Escape returns to live. " : ""}Compute: ${phases[index].label}. ${model.valid ? "" : "Status unavailable. "}${showTotal ? "Runtime session total " + formatCount(model.total) + " tokens. " : ""}${model.phase === "active" ? "Per-request token count unavailable. " : ""}`, + ); + if (schedule) requestAnimationFrame(render); +} +poll(); +requestAnimationFrame(render); + +document.getElementById("display").addEventListener("mousedown", (event) => { + if (event.button === 0 && invoke) + invoke("plugin:window|start_dragging").catch(() => {}); +}); diff --git a/scripts/build-compute-pair.mjs b/scripts/build-compute-pair.mjs new file mode 100644 index 000000000..3299e60c0 --- /dev/null +++ b/scripts/build-compute-pair.mjs @@ -0,0 +1,115 @@ +/** Build isolated macOS development apps; invoke from an activated Hermit shell. */ +import { spawnSync } from "node:child_process"; +import { mkdirSync, copyFileSync, renameSync, writeFileSync } from "node:fs"; +import { resolve, join } from "node:path"; +const root = process.cwd(); +if (process.platform !== "darwin") + throw new Error("This local test pair builder targets macOS"); +const output = resolve( + process.argv[2] ?? resolve(root, ".scratch/compute-pair"), +); +mkdirSync(output, { recursive: true }); +const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`; +function replaceExecutable(source, destination) { + const temporary = `${destination}.new`; + copyFileSync(source, temporary); + renameSync(temporary, destination); +} +for (const [role, title, web, api, consolePort] of [ + ["serve", "Buzz Compute Provider", 1451, 19337, 13131], + ["client", "Buzz Compute Consumer", 1452, 19338, 13132], +]) { + if ( + process.env.BUZZ_COMPUTE_BUILD_ROLE && + process.env.BUZZ_COMPUTE_BUILD_ROLE !== role + ) + continue; + const identifier = `dev.local.buzz.compute-test.${role}`; + const config = { + identifier, + productName: title, + build: { devUrl: `http://localhost:${web}` }, + app: { + windows: [ + { + title, + width: 1200, + height: 800, + titleBarStyle: "Overlay", + hiddenTitle: true, + trafficLightPosition: { x: 20, y: 26 }, + minWidth: 480, + minHeight: 400, + }, + ], + }, + }; + const result = spawnSync("cargo", ["build", "-p", "buzz-foundation"], { + cwd: root, + env: { ...process.env, TAURI_CONFIG: JSON.stringify(config) }, + stdio: "inherit", + }); + if (result.status !== 0) process.exit(result.status ?? 1); + const app = join(output, `${title}.app`), + contents = join(app, "Contents"), + mac = join(contents, "MacOS"), + resources = join(contents, "Resources"); + mkdirSync(mac, { recursive: true }); + mkdirSync(resources, { recursive: true }); + replaceExecutable( + join(root, "target/debug/buzz-foundation"), + join(mac, "buzz-foundation"), + ); + if (role === "client" && process.env.BUZZ_AGENT_RUNTIME_SOURCE) { + const runtime = join(mac, "agent-runtime"); + mkdirSync(runtime, { recursive: true }); + for (const name of ["buzz-acp", "buzz-agent", "buzz-dev-mcp"]) { + replaceExecutable( + join(process.env.BUZZ_AGENT_RUNTIME_SOURCE, name), + join(runtime, name), + ); + } + } + copyFileSync( + join(root, "src-tauri/icons/icon.icns"), + join(resources, "icon.icns"), + ); + writeFileSync( + join(contents, "Info.plist"), + `CFBundleName${title}CFBundleDisplayName${title}CFBundleIdentifier${identifier}CFBundleExecutablelaunchCFBundlePackageTypeAPPLCFBundleVersion1CFBundleIconFileicon.icnsNSHighResolutionCapable`, + ); + // The app bundle has no secret material. Its broker uses the existing authorized Keychain account. + // Refuse an occupied web port rather than attaching to somebody else's server. + writeFileSync( + join(mac, "launch"), + `#!/bin/zsh +set -eu +cd ${quote(root)} +source bin/activate-hermit +export BUZZODZ_HOME=${quote(join(output, `${role}-plugins`))} +export BUZZ_COMPUTE_ROLE=${quote(role)} +export BUZZ_COMPUTE_API_PORT=${api} +export BUZZ_COMPUTE_CONSOLE_PORT=${consolePort} +if [[ -f ${quote(join(output, "account-env.sh"))} ]]; then + source ${quote(join(output, "account-env.sh"))} +fi +if /usr/sbin/lsof -nP -iTCP:${web} -sTCP:LISTEN >/dev/null 2>&1; then + print "Port ${web} is already in use. Close this test app before opening another copy." >&2 + exit 1 +fi +node node_modules/vite/bin/vite.js --port ${web} --strictPort >${quote(join(output, `${role}-broker.log`))} 2>&1 & +compute_broker_pid=$! +trap 'kill "$compute_broker_pid" 2>/dev/null || true' EXIT +compute_ready=0 +for attempt in {1..100}; do + if /usr/bin/curl --silent --fail http://localhost:${web}/ >/dev/null; then compute_ready=1; break; fi + if ! kill -0 "$compute_broker_pid" 2>/dev/null; then exit 1; fi + sleep 0.1 +done +if [[ "$compute_ready" != 1 ]]; then exit 1; fi +${quote(join(mac, "buzz-foundation"))} >${quote(join(output, `${role}-app.log`))} 2>&1 +`, + { mode: 0o755 }, + ); + console.log(`Created ${app}`); +} diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 8b52c5dc5..e877ce463 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -13,7 +13,10 @@ crate-type = ["staticlib", "cdylib", "rlib"] tauri-build = { version = "2", features = [] } [dependencies] -tauri = { version = "2", features = [] } +tauri = { version = "2", features = ["macos-private-api"] } +buzz-community-compute = { path = "../crates/community-compute" } +nostr = "0.44" +serde_json = "1" buzzodz-plugins = { path = "../crates/plugin-manager" } tauri-plugin-dialog = "2" tauri-plugin-opener = "2" diff --git a/src-tauri/capabilities/compute-widget.json b/src-tauri/capabilities/compute-widget.json new file mode 100644 index 000000000..c7aaa5a73 --- /dev/null +++ b/src-tauri/capabilities/compute-widget.json @@ -0,0 +1,7 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "compute-widget", + "description": "Allow dragging and closing the floating Compute widget.", + "windows": ["compute-widget"], + "permissions": ["core:window:allow-start-dragging", "core:window:allow-close"] +} diff --git a/src-tauri/src/agent_runner/mod.rs b/src-tauri/src/agent_runner/mod.rs new file mode 100644 index 000000000..55a718145 --- /dev/null +++ b/src-tauri/src/agent_runner/mod.rs @@ -0,0 +1,352 @@ +//! App-owned runner for an explicitly configured existing shared-compute agent. +//! No renderer-provided paths, commands, credentials or environment variables. +mod profile; +pub mod supervisor; +use crate::compute_host::{ComputeHost, TestLease}; +use serde::Serialize; +use std::{ + fs::{File, OpenOptions}, + path::PathBuf, + process::{Child, Command, Stdio}, + sync::{Arc, Mutex}, + time::{Duration, Instant}, +}; + +#[derive(Clone)] +struct Config { + library: PathBuf, + binaries: PathBuf, + workdir: PathBuf, + service: String, + agent: String, +} +#[derive(Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Status { + available: bool, + state: String, + name: Option, + pubkey: Option, + detail: Option, +} +struct Running { + child: Child, + generation: u64, + _lock: File, + _lease: TestLease, +} +struct Core { + running: Option, + status: Status, +} +#[derive(Clone)] +pub struct AgentRunner { + core: Arc>, + config: Option, + logs: PathBuf, + compute: ComputeHost, +} + +impl AgentRunner { + pub fn new(data: PathBuf, binaries: PathBuf, compute: ComputeHost) -> Self { + let config = (|| { + if !cfg!(all(debug_assertions, target_os = "macos")) { + return None; + } + let agent = std::env::var("BUZZ_RUNNER_AGENT").ok()?; + if agent.len() != 64 || !agent.bytes().all(|b| b.is_ascii_hexdigit()) { + return None; + } + Some(Config { + library: std::env::var_os("BUZZ_RUNNER_LIBRARY")?.into(), + service: std::env::var("BUZZ_DEV_CREDENTIAL_SERVICE").ok()?, + workdir: std::env::var_os("BUZZ_RUNNER_WORKDIR")?.into(), + binaries, + agent, + }) + })(); + let result = Self { + core: Arc::new(Mutex::new(Core { + running: None, + status: Status { + available: config.is_some(), + state: "stopped".into(), + name: None, + pubkey: config.as_ref().map(|c| c.agent.clone()), + detail: None, + }, + })), + config, + logs: data.join("agent-runner"), + compute, + }; + let monitor = result.clone(); + std::thread::spawn(move || loop { + std::thread::sleep(Duration::from_secs(1)); + let _ = monitor.status(); + }); + result + } + pub fn status(&self) -> Result { + let mut core = self.core.lock().map_err(|_| "Agent state unavailable")?; + let generation = self.compute.status()?; + if let Some(running) = core.running.as_mut() { + if running + .child + .try_wait() + .map_err(|_| "Cannot inspect agent process")? + .is_some() + { + // Retain the child PID until group cleanup, even if the leader exited. + terminate(running); + core.running = None; + core.status.state = "failed".into(); + core.status.detail = Some("Agent exited. Check its local log and restart.".into()); + } else if generation.state != "running" + || generation.generation != running.generation + || !generation.available + { + terminate(running); + core.running = None; + core.status.state = "stopped".into(); + core.status.detail = + Some("Compute connection changed; reconnect and start the agent again.".into()); + } + } + Ok(core.status.clone()) + } + pub fn start(&self, viewer: &str, community: &str) -> Result { + let config = self + .config + .as_ref() + .ok_or("This build has no configured agent runtime")?; + let mut core = self.core.lock().map_err(|_| "Agent state unavailable")?; + if core.running.is_some() { + return Ok(core.status.clone()); + } + let compute = self.compute.status()?; + if !compute.available + || compute.state != "running" + || compute.mode.as_deref() != Some("client") + || compute.viewer.as_deref() != Some(viewer) + || compute.community.as_deref() != Some(community) + { + return Err("Connect shared compute for this account and community first".into()); + } + let api = compute + .api_base_url + .as_deref() + .ok_or("Shared compute endpoint unavailable")?; + // The consumer lease blocks port reuse until the entire runner group + // is gone, including while Keychain access is waiting for the user. + let lease = self.compute.begin_test(compute.generation)?; + let mut relay = url::Url::parse(community).map_err(|_| "Invalid community")?; + if relay.scheme() == "https" { + relay.set_scheme("wss").map_err(|_| "Invalid community")?; + } + if relay.scheme() != "wss" { + return Err("A secure community is required".into()); + } + let raw = profile::read_json(&config.library)?; + let profile = profile::project(&raw, &config.agent, viewer)?; + // Never adopt or kill legacy processes. A live legacy receipt blocks a + // second listener for the same identity, even across communities. + let receipts = config + .library + .parent() + .ok_or("Invalid agent library path")? + .join("agent-pids"); + if let Ok(entries) = std::fs::read_dir(receipts) { + for entry in entries + .flatten() + .filter(|e| e.file_name().to_string_lossy().starts_with(&config.agent)) + { + let receipt = profile::read_json(&entry.path())?; + if let Some(pid) = receipt + .get("pid") + .unwrap_or(&receipt) + .as_u64() + .filter(|p| *p > 1 && *p <= i32::MAX as u64) + { + #[cfg(unix)] + if unsafe { libc::kill(pid as i32, 0) } == 0 { + return Err("Stop this agent in old Buzz before starting it here".into()); + } + } + } + } + std::fs::create_dir_all(&self.logs).map_err(|_| "Cannot create agent log directory")?; + let mut lock_options = OpenOptions::new(); + lock_options.read(true).write(true).create(true); + #[cfg(unix)] + { + use std::os::unix::fs::OpenOptionsExt; + lock_options.mode(0o600).custom_flags(libc::O_NOFOLLOW); + } + let lock = lock_options + .open( + config + .library + .parent() + .unwrap() + .join(format!("{}.new-app.lock", config.agent)), + ) + .map_err(|_| "Cannot lock agent runtime")?; + #[cfg(unix)] + { + use std::os::fd::AsRawFd; + if unsafe { libc::flock(lock.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) } != 0 { + return Err("This agent is already running in another new Buzz app".into()); + } + } + for name in ["buzz-acp", "buzz-agent", "buzz-dev-mcp"] { + if !config.binaries.join(name).is_file() { + return Err("This app is missing its bundled agent runtime".into()); + } + } + let secret = profile::agent_secret(&config.service, &config.agent)?; + let mut log_options = OpenOptions::new(); + log_options.create(true).append(true); + #[cfg(unix)] + { + use std::os::unix::fs::OpenOptionsExt; + log_options.mode(0o600).custom_flags(libc::O_NOFOLLOW); + } + let log = log_options + .open(self.logs.join("runner.log")) + .map_err(|_| "Cannot open agent log")?; + let mut command = + Command::new(std::env::current_exe().map_err(|_| "Cannot locate runner supervisor")?); + command.arg(supervisor::ARG); + command.env_clear(); + command.env("BUZZ_RUNNER_CHILD", config.binaries.join("buzz-acp")); + #[cfg(unix)] + { + use std::os::fd::AsRawFd; + let fd = lock.as_raw_fd(); + use std::os::unix::process::CommandExt; + // The supervisor inherits the same flock open-file description; + // it remains held after a desktop crash until group shutdown. + unsafe { + command.pre_exec(move || { + if libc::fcntl(fd, libc::F_SETFD, 0) < 0 { + return Err(std::io::Error::last_os_error()); + } + Ok(()) + }); + } + command.env("BUZZ_RUNNER_LOCK_FD", fd.to_string()); + } + for key in ["HOME", "TMPDIR", "LANG", "SSL_CERT_FILE", "SSL_CERT_DIR"] { + if let Some(value) = std::env::var_os(key) { + command.env(key, value); + } + } + let path = std::env::var("PATH").unwrap_or_else(|_| "/usr/bin:/bin:/usr/sbin:/sbin".into()); + command + .current_dir(&config.workdir) + .env("PATH", format!("{}:{path}", config.binaries.display())) + .env("BUZZ_PRIVATE_KEY", secret) + .env("BUZZ_AUTH_TAG", profile.auth) + .env("BUZZ_RELAY_URL", relay.as_str()) + .env("BUZZ_ACP_AGENT_OWNER", viewer) + .env("BUZZ_ACP_RESPOND_TO", "owner-only") + .env("BUZZ_ACP_AGENT_COMMAND", config.binaries.join("buzz-agent")) + .env("BUZZ_ACP_MCP_COMMAND", config.binaries.join("buzz-dev-mcp")) + .env("BUZZ_ACP_SYSTEM_PROMPT", profile.prompt) + .env("BUZZ_ACP_SESSION_TITLE", &profile.name) + .env("BUZZ_ACP_DISPLAY_NAME", &profile.name) + .env("BUZZ_ACP_AGENTS", "1") + .env("BUZZ_ACP_RELAY_OBSERVER", "true") + .env("BUZZ_ACP_AUTO_PUBLISH_REPLY", "true") + .env("BUZZ_AGENT_REQUIRE_REPLY", "0") + .env("BUZZ_ACP_MODEL", &profile.model) + .env("BUZZ_AGENT_MODEL", &profile.model) + .env("BUZZ_AGENT_PROVIDER", "openai") + .env("OPENAI_COMPAT_BASE_URL", api) + .env("OPENAI_COMPAT_MODEL", &profile.model) + .env("OPENAI_COMPAT_API_KEY", "buzz-mesh-local") + .env("OPENAI_COMPAT_API", "chat") + .env("BUZZ_AGENT_MAX_OUTPUT_TOKENS", "4096") + .env("RUST_LOG", "info") + .stdin(Stdio::piped()) + .stdout(log.try_clone().map_err(|_| "Cannot open agent log")?) + .stderr(log); + #[cfg(unix)] + { + use std::os::unix::process::CommandExt; + command.process_group(0); + } + if !lease.is_current() { + return Err("Compute connection changed while starting the agent".into()); + } + let child = command + .spawn() + .map_err(|_| "Could not start bundled agent runtime")?; + core.running = Some(Running { + child, + generation: compute.generation, + _lock: lock, + _lease: lease, + }); + core.status.state = "running".into(); + core.status.name = Some(profile.name); + core.status.detail = None; + Ok(core.status.clone()) + } + pub fn stop(&self) -> Result { + let mut core = self.core.lock().map_err(|_| "Agent state unavailable")?; + if let Some(running) = core.running.as_mut() { + terminate(running); + } + core.running = None; + core.status.state = "stopped".into(); + core.status.detail = None; + Ok(core.status.clone()) + } +} +fn terminate(running: &mut Running) { + running.child.stdin.take(); + #[cfg(unix)] + unsafe { + libc::kill(-(running.child.id() as i32), libc::SIGTERM); + } + let until = Instant::now() + Duration::from_secs(3); + while Instant::now() < until { + if running.child.try_wait().ok().flatten().is_some() { + break; + } + std::thread::sleep(Duration::from_millis(40)); + } + #[cfg(unix)] + unsafe { + libc::kill(-(running.child.id() as i32), libc::SIGKILL); + } + let _ = running.child.kill(); + let _ = running.child.wait(); +} +#[tauri::command] +pub async fn agent_runner_status(host: tauri::State<'_, AgentRunner>) -> Result { + let host = host.inner().clone(); + tauri::async_runtime::spawn_blocking(move || host.status()) + .await + .map_err(|_| "Agent status failed")? +} +#[tauri::command] +pub async fn agent_runner_start( + host: tauri::State<'_, AgentRunner>, + viewer: String, + community: String, +) -> Result { + let host = host.inner().clone(); + tauri::async_runtime::spawn_blocking(move || host.start(&viewer, &community)) + .await + .map_err(|_| "Agent start failed")? +} +#[tauri::command] +pub async fn agent_runner_stop(host: tauri::State<'_, AgentRunner>) -> Result { + let host = host.inner().clone(); + tauri::async_runtime::spawn_blocking(move || host.stop()) + .await + .map_err(|_| "Agent stop failed")? +} diff --git a/src-tauri/src/agent_runner/profile.rs b/src-tauri/src/agent_runner/profile.rs new file mode 100644 index 000000000..12a69ba3e --- /dev/null +++ b/src-tauri/src/agent_runner/profile.rs @@ -0,0 +1,197 @@ +//! Read-only import of one explicitly configured legacy agent. Secrets stay native. +use nostr::{ + hashes::{sha256, Hash}, + secp256k1::{schnorr::Signature, Message}, + Keys, PublicKey, SECP256K1, +}; +use serde_json::Value; +use std::{ + collections::HashMap, fs::OpenOptions, io::Read, path::Path, process::Command, str::FromStr, +}; + +pub struct Profile { + pub name: String, + pub prompt: String, + pub model: String, + pub auth: String, +} + +pub fn read_json(path: &Path) -> Result { + let mut options = OpenOptions::new(); + options.read(true); + #[cfg(unix)] + { + use std::os::unix::fs::OpenOptionsExt; + options.custom_flags(libc::O_NOFOLLOW | libc::O_NONBLOCK); + } + let file = options + .open(path) + .map_err(|_| "Cannot read the configured agent library")?; + if !file + .metadata() + .map_err(|_| "Cannot inspect agent library")? + .is_file() + { + return Err("Agent library must be a regular file".into()); + } + let mut bytes = Vec::new(); + file.take(8 * 1024 * 1024 + 1) + .read_to_end(&mut bytes) + .map_err(|_| "Cannot read agent library")?; + let result = if bytes.len() > 8 * 1024 * 1024 { + Err("Agent library is too large".into()) + } else { + serde_json::from_slice(&bytes).map_err(|_| "Invalid agent library".into()) + }; + bytes.fill(0); + result +} + +pub fn project(raw: &Value, agent: &str, owner: &str) -> Result { + let rows = raw.as_array().ok_or("Invalid agent library")?; + let mut matches = rows.iter().filter(|r| r["pubkey"].as_str() == Some(agent)); + let row = matches.next().ok_or("Configured agent was not found")?; + if matches.next().is_some() { + return Err("Duplicate agent identity".into()); + } + if row["agent_command"].as_str() != Some("buzz-agent") + || row["respond_to"].as_str() != Some("owner-only") + { + return Err("This runner supports owner-only Buzz agents".into()); + } + let definition = match row["persona_id"].as_str() { + Some(id) => rows + .iter() + .find(|r| r["pubkey"].as_str() == Some("") && r["slug"].as_str() == Some(id)) + .ok_or("Agent definition is missing")?, + None => row, + }; + if definition["provider"].as_str() != Some("relay-mesh") { + return Err("Choose shared compute for this agent before starting it".into()); + } + let auth = row["auth_tag"] + .as_str() + .ok_or("Agent owner attestation is missing")?; + verify_owner(auth, agent, owner)?; + let stored = definition["model"].as_str().unwrap_or("auto").trim(); + Ok(Profile { + name: row["name"].as_str().unwrap_or("Agent").to_string(), + prompt: definition["system_prompt"] + .as_str() + .unwrap_or("") + .to_string(), + model: if stored.is_empty() || stored == "auto" { + "mesh" + } else { + stored + } + .to_string(), + auth: auth.to_string(), + }) +} + +fn verify_owner(auth: &str, agent: &str, owner: &str) -> Result<(), String> { + let fail = || "Agent owner attestation does not match this account".to_string(); + let parts: Vec = serde_json::from_str(auth).map_err(|_| fail())?; + // Initial compatibility scope is unconditional credentials. Never silently + // discard delegated restrictions we do not implement. + if parts.len() != 4 + || parts[0] != "auth" + || parts[1] != owner + || !parts[2].is_empty() + || agent == owner + { + return Err(fail()); + } + let key = PublicKey::from_hex(owner).map_err(|_| fail())?; + let sig = Signature::from_str(&parts[3]).map_err(|_| fail())?; + let digest = sha256::Hash::hash(format!("nostr:agent-auth:{agent}:").as_bytes()); + let msg = Message::from_digest(digest.to_byte_array()); + SECP256K1 + .verify_schnorr(&sig, &msg, &key.xonly().map_err(|_| fail())?) + .map_err(|_| fail()) +} + +pub fn agent_secret(service: &str, agent: &str) -> Result { + if !service.starts_with("buzz-desktop") || service.len() > 128 { + return Err("Invalid agent Keychain service".into()); + } + let mut output = Command::new("/usr/bin/security") + .args([ + "find-generic-password", + "-s", + service, + "-a", + "secrets", + "-w", + ]) + .output() + .map_err(|_| "Cannot access the agent Keychain")?; + if !output.status.success() { + return Err("Agent Keychain access failed".into()); + } + let parsed: Result, _> = serde_json::from_slice(&output.stdout); + output.stdout.fill(0); + let mut secrets = parsed.map_err(|_| "Invalid agent Keychain data")?; + let result = secrets + .remove(&format!("agent:{agent}")) + .ok_or("Agent credential is unavailable")?; + if Keys::parse(&result) + .map_err(|_| "Invalid agent credential")? + .public_key() + .to_hex() + != agent + { + return Err("Agent credential identity mismatch".into()); + } + Ok(result) +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + fn fixture() -> (Value, String, String) { + let owner = Keys::generate(); + let agent = Keys::generate().public_key().to_hex(); + let message = Message::from_digest( + sha256::Hash::hash(format!("nostr:agent-auth:{agent}:").as_bytes()).to_byte_array(), + ); + let auth = json!([ + "auth", + owner.public_key().to_hex(), + "", + owner.sign_schnorr(&message).to_string() + ]) + .to_string(); + let raw = json!([ + {"pubkey":"", "slug":"definition", "provider":"relay-mesh", "model":"auto", "system_prompt":"current prompt"}, + {"pubkey":agent, "name":"Moonpal", "agent_command":"buzz-agent", "respond_to":"owner-only", "persona_id":"definition", "system_prompt":"stale prompt", "auth_tag":auth} + ]); + (raw, agent, owner.public_key().to_hex()) + } + #[test] + fn uses_linked_definition_and_verifies_owner() { + let (raw, agent, owner) = fixture(); + let p = project(&raw, &agent, &owner).unwrap(); + assert_eq!(p.model, "mesh"); + assert_eq!(p.prompt, "current prompt"); + assert!(project(&raw, &agent, &Keys::generate().public_key().to_hex()).is_err()); + } + #[test] + fn refuses_invalid_credentials_missing_definition_and_other_providers() { + let (raw, agent, owner) = fixture(); + let mut forged = raw.clone(); + forged[1]["auth_tag"] = json!(json!(["auth", owner, "", "0".repeat(128)]).to_string()); + assert!(project(&forged, &agent, &owner).is_err()); + let mut missing = raw.clone(); + missing[1]["persona_id"] = json!("missing"); + assert!(project(&missing, &agent, &owner).is_err()); + let mut other = raw.clone(); + other[0]["provider"] = json!("openai"); + assert!(project(&other, &agent, &owner).is_err()); + let mut access = raw.clone(); + access[1]["respond_to"] = json!("anyone"); + assert!(project(&access, &agent, &owner).is_err()); + } +} diff --git a/src-tauri/src/agent_runner/supervisor.rs b/src-tauri/src/agent_runner/supervisor.rs new file mode 100644 index 000000000..9a37eab31 --- /dev/null +++ b/src-tauri/src/agent_runner/supervisor.rs @@ -0,0 +1,75 @@ +//! A separate group leader survives desktop exit long enough to stop its children. +pub const ARG: &str = "--buzz-agent-runner-supervisor"; +#[cfg(unix)] +pub fn entry() -> ! { + use std::{ + io::Read, + os::unix::process::CommandExt, + process::{Command, Stdio}, + sync::mpsc, + time::Duration, + }; + // Only execute in a dedicated process group created by the desktop. + let pid = unsafe { libc::getpid() }; + if unsafe { libc::getpgrp() } != pid { + std::process::exit(2); + } + let fd: i32 = std::env::var("BUZZ_RUNNER_LOCK_FD") + .ok() + .and_then(|v| v.parse().ok()) + .filter(|fd| *fd > 2) + .unwrap_or_else(|| std::process::exit(2)); + if unsafe { libc::fcntl(fd, libc::F_GETFD) } < 0 { + std::process::exit(2); + } + unsafe { + libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC); + libc::signal(libc::SIGTERM, libc::SIG_IGN); + } + let Some(binary) = std::env::var_os("BUZZ_RUNNER_CHILD") else { + std::process::exit(2); + }; + let mut command = Command::new(binary); + command + .env_remove("BUZZ_RUNNER_CHILD") + .env_remove("BUZZ_RUNNER_LOCK_FD") + .stdin(Stdio::null()); + unsafe { + command.pre_exec(|| { + libc::signal(libc::SIGTERM, libc::SIG_DFL); + Ok(()) + }); + } + let Ok(mut child) = command.spawn() else { + std::process::exit(2); + }; + let (tx, rx) = mpsc::channel(); + std::thread::spawn(move || { + let mut buffer = [0; 16]; + let mut input = std::io::stdin(); + while matches!(input.read(&mut buffer), Ok(n) if n > 0) {} + let _ = tx.send(()); + }); + loop { + if !matches!(child.try_wait(), Ok(None)) + || rx.recv_timeout(Duration::from_millis(100)) != Err(mpsc::RecvTimeoutError::Timeout) + { + break; + } + } + // We ignore TERM; descendants use its default. Keep the lock until the + // final group kill, including descendants that ignore graceful shutdown. + unsafe { + libc::kill(-pid, libc::SIGTERM); + } + std::thread::sleep(Duration::from_millis(1500)); + let _ = child.try_wait(); + unsafe { + libc::kill(-pid, libc::SIGKILL); + } + std::process::exit(1) +} +#[cfg(not(unix))] +pub fn entry() -> ! { + std::process::exit(2) +} diff --git a/src-tauri/src/community_compute.rs b/src-tauri/src/community_compute.rs new file mode 100644 index 000000000..d583e1583 --- /dev/null +++ b/src-tauri/src/community_compute.rs @@ -0,0 +1,71 @@ +/// Display-only projection. No signing keys or node lifecycle are exposed here. +#[tauri::command] +pub async fn community_compute_snapshot( + events: Vec, + authority: String, + viewer: String, +) -> Result { + tauri::async_runtime::spawn_blocking(move || { + buzz_community_compute::project(events, &authority, &viewer) + }) + .await + .map_err(|error| error.to_string())? +} + +use crate::compute_host::{ComputeHost, Status}; +use buzz_community_compute::worker::StartRequest; +#[tauri::command] +pub fn community_compute_status(host: tauri::State<'_, ComputeHost>) -> Result { + host.status() +} +#[tauri::command] +pub async fn community_compute_models() -> Result +{ + tauri::async_runtime::spawn_blocking(buzz_community_compute::model_catalog) + .await + .map_err(|e| e.to_string()) +} +#[tauri::command] +pub async fn community_compute_start( + host: tauri::State<'_, ComputeHost>, + request: StartRequest, +) -> Result { + let host = host.inner().clone(); + tauri::async_runtime::spawn_blocking(move || host.start(request)) + .await + .map_err(|e| e.to_string())? +} +#[tauri::command] +pub async fn community_compute_stop( + host: tauri::State<'_, ComputeHost>, + generation: u64, +) -> Result { + let host = host.inner().clone(); + tauri::async_runtime::spawn_blocking(move || host.stop(Some(generation), true)) + .await + .map_err(|e| e.to_string())? +} + +#[tauri::command] +pub async fn community_compute_test( + host: tauri::State<'_, ComputeHost>, + generation: u64, + prompt: String, +) -> Result { + let lease = host.begin_test(generation)?; + let result = buzz_community_compute::consumer::test_request( + lease + .status + .api_base_url + .as_deref() + .ok_or("Compute API unavailable")?, + &prompt, + || lease.is_current(), + ) + .await + .map_err(|e| e.to_string()); + if !lease.is_current() { + return Err("Compute session changed during the request".into()); + } + result +} diff --git a/src-tauri/src/compute_host.rs b/src-tauri/src/compute_host.rs new file mode 100644 index 000000000..7a1c3826b --- /dev/null +++ b/src-tauri/src/compute_host.rs @@ -0,0 +1,617 @@ +//! App-owned process lifecycle. Page disposal does not own compute; plugin disable +//! and app exit do. Only our child handle is ever stopped, never a discovered PID. +use buzz_community_compute::worker::{ + Download, StartRequest, Update, WorkerInput, OUTPUT_PREFIX, WORKER_ARG, +}; +use serde::Serialize; +use std::{ + io::{BufRead, BufReader, Read, Write}, + path::PathBuf, + process::{Child, Command, Stdio}, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, + }, + time::{Duration, Instant}, +}; + +#[derive(Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Status { + pub preferred_mode: String, + pub api_base_url: Option, + pub usage: Option, + pub available: bool, + pub generation: u64, + pub state: String, + pub mode: Option, + pub model_id: Option, + pub community: Option, + pub viewer: Option, + pub detail: Option, + pub download: Option, +} +struct Core { + testing: bool, + usage_at: Option, + stopping: bool, + status: Status, + child: Option>>, +} +#[derive(Clone)] +pub struct ComputeHost { + core: Arc>, + operation: Arc>, + allowed: Arc, + path: Option, + broker: Option, + grace: Duration, + #[cfg(test)] + fixture: Option, +} +pub struct TestLease { + host: ComputeHost, + pub status: Status, +} +impl TestLease { + pub fn is_current(&self) -> bool { + self.host.status().is_ok_and(|s| { + s.generation == self.status.generation + && s.state == "running" + && s.mode.as_deref() == Some("client") + }) + } +} +impl Drop for TestLease { + fn drop(&mut self) { + if let Ok(mut core) = self.host.core.lock() { + core.testing = false; + } + } +} +impl ComputeHost { + pub fn new(path: Option, broker: Option, enabled: bool) -> Self { + let available = cfg!(debug_assertions) && path.is_some() && broker.is_some(); + Self { + core: Arc::new(Mutex::new(Core { + testing: false, + usage_at: None, + stopping: false, + status: Status { + usage: None, + preferred_mode: std::env::var("BUZZ_COMPUTE_ROLE") + .unwrap_or_else(|_| "serve".into()), + api_base_url: None, + available, + generation: 0, + state: "off".into(), + mode: None, + model_id: None, + community: None, + viewer: None, + detail: None, + download: None, + }, + child: None, + })), + operation: Arc::new(Mutex::new(())), + allowed: Arc::new(AtomicBool::new(enabled)), + path: path.map(|p| p.join("community-compute.json")), + broker, + grace: Duration::from_secs(6), + #[cfg(test)] + fixture: None, + } + } + pub fn status(&self) -> Result { + let core = self.core.lock().map_err(|_| "Compute state unavailable")?; + let mut status = core.status.clone(); + if status.state != "running" + || !core + .usage_at + .is_some_and(|at| at.elapsed() < Duration::from_secs(5)) + { + status.usage = None; + } + status.available &= self.allowed.load(Ordering::SeqCst); + Ok(status) + } + pub fn begin_test(&self, generation: u64) -> Result { + let _operation = self + .operation + .lock() + .map_err(|_| "Compute operation unavailable")?; + let mut core = self.core.lock().map_err(|_| "Compute state unavailable")?; + if core.testing + || core.status.generation != generation + || core.status.state != "running" + || core.status.mode.as_deref() != Some("client") + { + return Err("Connect the consumer and wait for the previous request to finish".into()); + } + core.testing = true; + Ok(TestLease { + host: self.clone(), + status: core.status.clone(), + }) + } + pub fn set_allowed(&self, value: bool) { + self.allowed.store(value, Ordering::SeqCst); + } + pub fn restore(&self) { + let host = self.clone(); + std::thread::spawn(move || { + let Some(path) = &host.path else { return }; + if !host.allowed.load(Ordering::SeqCst) { + return; + } + let result = (|| { + let _operation = host + .operation + .lock() + .map_err(|_| "Compute operation unavailable")?; + let file = match std::fs::File::open(path) { + Ok(f) => f, + Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(()), + Err(e) => return Err(e.to_string()), + }; + if file.metadata().map_err(|e| e.to_string())?.len() > 8192 { + return Err("Saved compute preference exceeds limit".into()); + } + let request: StartRequest = serde_json::from_reader(file) + .map_err(|_| "Saved compute preference is invalid")?; + host.start_locked(request).map(|_| ()) + })(); + if let Err(error) = result { + if let Ok(mut core) = host.core.lock() { + if core.child.is_none() { + core.status.state = "failed".into(); + core.status.detail = Some(error); + } + } + } + }); + } + pub fn start(&self, request: StartRequest) -> Result { + let _operation = self + .operation + .lock() + .map_err(|_| "Compute operation unavailable")?; + self.start_locked(request) + } + fn start_locked(&self, request: StartRequest) -> Result { + if self + .core + .lock() + .map_err(|_| "Compute state unavailable")? + .testing + { + return Err("Previous test request is cancelling; retry connection shortly".into()); + } + request.validate().map_err(|e| e.to_string())?; + if !self.allowed.load(Ordering::SeqCst) || !self.status()?.available { + return Err("Sharing requires the enabled desktop development plugin".into()); + } + if self + .core + .lock() + .map_err(|_| "Compute state unavailable")? + .child + .is_some() + { + return Err("Stop the current sharing session before starting another".into()); + } + let path = self + .path + .as_ref() + .ok_or("Compute preferences unavailable")?; + std::fs::create_dir_all(path.parent().ok_or("Invalid preferences path")?) + .map_err(|e| e.to_string())?; + let temporary = path.with_extension("json.tmp"); + std::fs::write( + &temporary, + serde_json::to_vec(&request).map_err(|e| e.to_string())?, + ) + .map_err(|e| e.to_string())?; + std::fs::rename(&temporary, path).map_err(|e| e.to_string())?; + let port = |name: &str, default: u16| -> Result { + match std::env::var(name) { + Ok(value) => value + .parse::() + .ok() + .filter(|p| *p > 0) + .ok_or_else(|| format!("Invalid {name}")), + Err(_) => Ok(default), + } + }; + let api_port = port("BUZZ_COMPUTE_API_PORT", 9337)?; + let console_port = port("BUZZ_COMPUTE_CONSOLE_PORT", 3131)?; + if api_port == console_port { + return Err("Compute ports must be distinct".into()); + } + let input = WorkerInput { + api_port, + console_port, + owner_key: path.with_file_name("community-compute-owner.json"), + broker_origin: self + .broker + .clone() + .ok_or("Development broker unavailable")?, + request: request.clone(), + }; + let mut command = self.command()?; + let mut child = command + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .spawn() + .map_err(|e| format!("Cannot start compute worker: {e}"))?; + let input = serde_json::to_string(&input).map_err(|e| e.to_string())?; + if let Err(error) = writeln!( + child.stdin.as_mut().ok_or("Compute input unavailable")?, + "{input}" + ) { + let _ = child.kill(); + let _ = child.wait(); + return Err(error.to_string()); + } + let stdout = child.stdout.take().ok_or("Compute output unavailable")?; + let child = Arc::new(Mutex::new(child)); + let generation; + { + let mut core = self.core.lock().map_err(|_| "Compute state unavailable")?; + generation = core.status.generation + 1; + core.status = Status { + preferred_mode: core.status.preferred_mode.clone(), + api_base_url: Some(format!("http://127.0.0.1:{api_port}/v1")), + usage: None, + available: true, + generation, + state: "starting".into(), + mode: Some(request.mode.as_str().into()), + model_id: Some(request.model_id), + community: Some(request.community), + viewer: Some(request.viewer), + detail: Some("Preparing shared compute…".into()), + download: None, + }; + core.stopping = false; + core.child = Some(child.clone()); + } + let host = self.clone(); + let output = std::thread::spawn(move || { + let mut reader = BufReader::new(stdout); + loop { + let mut line = String::new(); + match reader.by_ref().take(65537).read_line(&mut line) { + Ok(0) | Err(_) => break, + _ => {} + } + if line.len() > 65536 { + break; + } + let Some(json) = line.strip_prefix(OUTPUT_PREFIX) else { + continue; + }; + let Ok(update) = serde_json::from_str::(json) else { + continue; + }; + if !["starting", "running", "stopping", "failed"].contains(&update.state.as_str()) { + continue; + } + if let Ok(mut core) = host.core.lock() { + if core.status.generation != generation || core.child.is_none() { + break; + } + if core.stopping { + continue; + } + if update.usage_sample { + core.usage_at = update.usage.as_ref().map(|_| Instant::now()); + core.status.usage = update.usage; + } + core.status.state = update.state; + core.status.detail = update.detail; + core.status.download = update.download; + } + } + }); + let host = self.clone(); + std::thread::spawn(move || { + let exit = loop { + match child + .lock() + .map_err(|_| ()) + .and_then(|mut c| c.try_wait().map_err(|_| ())) + { + Ok(Some(exit)) => break Some(exit), + Err(_) => break None, + Ok(None) => std::thread::sleep(Duration::from_millis(50)), + } + }; + let _ = output.join(); + if let Ok(mut core) = host.core.lock() { + if core.status.generation == generation && core.child.is_some() { + core.child = None; + core.status.download = None; + if core.status.state == "stopping" && exit.is_some_and(|e| e.success()) { + core.status.state = "off".into(); + core.status.mode = None; + } else { + let reported_failure = core.status.state == "failed"; + core.status.state = "failed".into(); + if !reported_failure { + core.status.detail=Some("Compute worker stopped unexpectedly; turn sharing off before retrying".into()); + } + } + } + } + }); + self.status() + } + fn command(&self) -> Result { + #[cfg(test)] + if let Some(script) = &self.fixture { + let mut command = Command::new("/bin/sh"); + command.arg(script); + return Ok(command); + } + let mut command = Command::new(std::env::current_exe().map_err(|e| e.to_string())?); + command.arg(WORKER_ARG); + #[cfg(windows)] + { + use std::os::windows::process::CommandExt; + command.creation_flags(0x08000000); + } + Ok(command) + } + /// Clearing saved opt-in is independent from cleanup: storage failure must + /// never prevent a running child from being stopped. + pub fn stop(&self, generation: Option, forget: bool) -> Result { + let _operation = self + .operation + .lock() + .map_err(|_| "Compute operation unavailable")?; + let (child, current) = { + let mut core = self.core.lock().map_err(|_| "Compute state unavailable")?; + if generation.is_some_and(|id| id != core.status.generation) { + return Err("Compute session changed; refresh before stopping".into()); + } + core.stopping = true; + core.status.state = "stopping".into(); + (core.child.clone(), core.status.generation) + }; + let persisted = if forget { + self.path + .as_ref() + .map(|path| std::fs::remove_file(path)) + .transpose() + .or_else(|e| { + if e.kind() == std::io::ErrorKind::NotFound { + Ok(None) + } else { + Err(e) + } + }) + .map(|_| ()) + .map_err(|e| e.to_string()) + } else { + Ok(()) + }; + if let Some(child) = child { + { + let mut guard = child.lock().map_err(|_| "Compute process unavailable")?; + if let Some(input) = guard.stdin.as_mut() { + let _ = writeln!(input, "stop"); + } + guard.stdin.take(); + } + let deadline = Instant::now() + self.grace; + loop { + let mut guard = child.lock().map_err(|_| "Compute process unavailable")?; + if guard.try_wait().map_err(|e| e.to_string())?.is_some() { + break; + } + if Instant::now() >= deadline { + guard.kill().map_err(|e| e.to_string())?; + guard.wait().map_err(|e| e.to_string())?; + break; + } + drop(guard); + std::thread::sleep(Duration::from_millis(25)); + } + } + { + let mut core = self.core.lock().map_err(|_| "Compute state unavailable")?; + if core.status.generation == current { + core.child = None; + core.status.state = "off".into(); + core.status.mode = None; + core.status.download = None; + core.status.detail = None; + } + } + persisted?; + self.status() + } +} + +#[cfg(all(test, unix))] +mod tests { + use super::*; + fn request() -> StartRequest { + StartRequest { + community: "https://compute.example".into(), + viewer: "0123456789abcdef".repeat(4), + mode: buzz_community_compute::worker::Mode::Serve, + model_id: "test-model".into(), + max_vram_gb: Some(8.0), + } + } + fn fixture() -> (ComputeHost, PathBuf) { + let dir = std::env::temp_dir().join(format!("buzz-compute-test-{}", uuid::Uuid::new_v4())); + std::fs::create_dir_all(&dir).unwrap(); + let script = dir.join("worker.sh"); + std::fs::write(&script,"IFS= read -r config\nprintf '%s\\n' 'BUZZ_COMPUTE {\"state\":\"running\",\"detail\":null,\"download\":null}'\nIFS= read -r stop\nexit 0\n").unwrap(); + let mut host = ComputeHost::new( + Some(dir.clone()), + Some("http://localhost:1430".into()), + true, + ); + host.fixture = Some(script); + (host, dir) + } + fn running(host: &ComputeHost) { + let deadline = Instant::now() + Duration::from_secs(3); + while host.status().unwrap().state != "running" { + assert!(Instant::now() < deadline, "worker did not become ready"); + std::thread::yield_now(); + } + } + #[test] + fn test_lease_blocks_replacement_but_not_stop() { + let host = ComputeHost::new(None, None, true); + { + let mut core = host.core.lock().unwrap(); + core.status.state = "running".into(); + core.status.mode = Some("client".into()); + } + let lease = host.begin_test(0).unwrap(); + assert!(host.begin_test(0).is_err()); + assert!(host.start(request()).err().unwrap().contains("cancelling")); + host.stop(Some(0), false).unwrap(); + assert!(!lease.is_current()); + assert!(host.core.lock().unwrap().testing); + drop(lease); + assert!(!host.core.lock().unwrap().testing); + } + + #[test] + fn heartbeat_does_not_replace_or_clear_sampler_counters() { + let (host, dir) = fixture(); + std::fs::write(host.fixture.as_ref().unwrap(), r#"IFS= read -r config +printf '%s\n' 'BUZZ_COMPUTE {"state":"running","usageSample":true,"usage":{"tokensServed":42,"inflight":1,"tokensPerSecond":10,"peers":2}}' +printf '%s\n' 'BUZZ_COMPUTE {"state":"running","detail":"heartbeat","usage":{"tokensServed":1,"inflight":0,"tokensPerSecond":0,"peers":2}}' +IFS= read -r stop +"#).unwrap(); + host.start(request()).unwrap(); + let deadline = Instant::now() + Duration::from_secs(3); + while host.status().unwrap().detail.as_deref() != Some("heartbeat") { + assert!(Instant::now() < deadline); + std::thread::yield_now(); + } + assert_eq!(host.status().unwrap().usage.unwrap().tokens_served, 42); + host.stop(None, true).unwrap(); + std::fs::remove_dir_all(dir).unwrap(); + } + + #[test] + fn widget_usage_expires_and_is_not_shown_outside_running_state() { + let host = ComputeHost::new(None, None, true); + { + let mut core = host.core.lock().unwrap(); + core.status.state = "running".into(); + core.status.usage = Some(buzz_community_compute::usage::Usage { + tokens_served: 42, + inflight: 1, + tokens_per_second: Some(10.0), + peers: Some(2), + }); + core.usage_at = Some(Instant::now()); + } + assert_eq!(host.status().unwrap().usage.unwrap().tokens_served, 42); + host.core.lock().unwrap().usage_at = Some(Instant::now() - Duration::from_secs(6)); + assert!(host.status().unwrap().usage.is_none()); + { + let mut core = host.core.lock().unwrap(); + core.usage_at = Some(Instant::now()); + core.status.state = "stopping".into(); + } + assert!(host.status().unwrap().usage.is_none()); + } + + #[test] + fn owns_one_child_persists_opt_in_and_reaps_before_reporting_off() { + let (host, dir) = fixture(); + let first = host.start(request()).unwrap(); + running(&host); + assert!(host.path.as_ref().unwrap().exists()); + assert!(host.start(request()).is_err()); + let child = host.core.lock().unwrap().child.clone().unwrap(); + assert_eq!( + host.stop(Some(first.generation), true).unwrap().state, + "off" + ); + assert!(child.lock().unwrap().try_wait().unwrap().is_some()); + assert!(!host.path.as_ref().unwrap().exists()); + std::fs::remove_dir_all(dir).unwrap(); + } + #[test] + fn stale_stop_cannot_kill_replacement_and_disable_blocks_new_start() { + let (host, dir) = fixture(); + let first = host.start(request()).unwrap(); + running(&host); + host.stop(None, true).unwrap(); + let second = host.start(request()).unwrap(); + running(&host); + assert!(second.generation > first.generation); + assert!(host.stop(Some(first.generation), true).is_err()); + assert_eq!(host.status().unwrap().state, "running"); + host.set_allowed(false); + host.stop(None, true).unwrap(); + assert!(host.start(request()).is_err()); + std::fs::remove_dir_all(dir).unwrap(); + } + #[test] + fn app_exit_keeps_opt_in_and_next_host_restores_only_when_enabled() { + let (host, dir) = fixture(); + host.start(request()).unwrap(); + running(&host); + host.stop(None, false).unwrap(); + host.restore(); + running(&host); + host.stop(None, true).unwrap(); + host.set_allowed(false); + host.restore(); + assert_eq!(host.status().unwrap().state, "off"); + std::fs::remove_dir_all(dir).unwrap(); + } + #[test] + fn unexpected_exit_replaces_healthy_detail_with_failure() { + let (host, dir) = fixture(); + host.start(request()).unwrap(); + running(&host); + { + let mut core = host.core.lock().unwrap(); + core.status.detail = Some("Sharing with verified community members".into()); + } + let child = host.core.lock().unwrap().child.clone().unwrap(); + writeln!(child.lock().unwrap().stdin.as_mut().unwrap(), "exit").unwrap(); + let deadline = Instant::now() + Duration::from_secs(3); + while host.status().unwrap().state != "failed" { + assert!(Instant::now() < deadline); + std::thread::yield_now(); + } + assert!(host + .status() + .unwrap() + .detail + .unwrap() + .contains("unexpectedly")); + host.stop(None, true).unwrap(); + std::fs::remove_dir_all(dir).unwrap(); + } + #[test] + fn stop_kills_a_worker_that_ignores_graceful_cancellation() { + let (mut host, dir) = fixture(); + std::fs::write(host.fixture.as_ref().unwrap(),"IFS= read -r config\nprintf '%s\\n' 'BUZZ_COMPUTE {\"state\":\"running\",\"detail\":null,\"download\":null}'\nexec sleep 120\n").unwrap(); + host.grace = Duration::from_millis(50); + host.start(request()).unwrap(); + running(&host); + let child = host.core.lock().unwrap().child.clone().unwrap(); + host.stop(None, true).unwrap(); + assert!(child.lock().unwrap().try_wait().unwrap().is_some()); + assert_eq!(host.status().unwrap().state, "off"); + std::fs::remove_dir_all(dir).unwrap(); + } +} diff --git a/src-tauri/src/compute_widget.rs b/src-tauri/src/compute_widget.rs new file mode 100644 index 000000000..6ff7e26cd --- /dev/null +++ b/src-tauri/src/compute_widget.rs @@ -0,0 +1,30 @@ +//! A read-only floating view of the app-owned compute worker. +use crate::compute_host::ComputeHost; +use tauri::{Manager, WebviewUrl, WebviewWindowBuilder}; + +pub const LABEL: &str = "compute-widget"; +#[tauri::command] +pub async fn compute_widget_open( + app: tauri::AppHandle, + host: tauri::State<'_, ComputeHost>, +) -> Result<(), String> { + if !host.status()?.available { + return Err("Enable Compute in the development desktop app to open its widget".into()); + } + if let Some(window) = app.get_webview_window(LABEL) { + window.show().map_err(|e| e.to_string())?; + return window.set_focus().map_err(|e| e.to_string()); + } + WebviewWindowBuilder::new(&app, LABEL, WebviewUrl::App("compute-widget.html".into())) + .title("Compute") + .inner_size(200.0, 200.0) + .min_inner_size(128.0, 128.0) + .resizable(true) + .decorations(false) + .transparent(true) + .background_color(tauri::window::Color(0, 0, 0, 0)) + .always_on_top(true) + .build() + .map_err(|e| e.to_string())?; + Ok(()) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index a2a84f1ca..40c137d37 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,3 +1,8 @@ +pub mod agent_runner; +mod community_compute; +mod compute_host; +mod compute_widget; +use compute_host::ComputeHost; mod notifications; mod terminal; use notifications::{notification_show, Notifications}; @@ -130,12 +135,26 @@ async fn plugin_catalog( } #[tauri::command] async fn plugin_change( + app: tauri::AppHandle, manager: tauri::State<'_, PluginManager>, action: String, id: String, + compute: tauri::State<'_, ComputeHost>, ) -> Result { + let compute = compute.inner().clone(); with_manager(manager, move |m| { - m.change(&action, &id).map(|catalog| ready(&m, catalog)) + if id == "buzz.community-compute" && action == "disable" { + compute.set_allowed(false); + compute.stop(None, true)?; + if let Some(window) = app.get_webview_window(compute_widget::LABEL) { + window.close().map_err(|e| e.to_string())?; + } + } + let result = m.change(&action, &id)?; + if id == "buzz.community-compute" && action == "enable" { + compute.set_allowed(true); + } + Ok(ready(&m, result)) }) .await } @@ -162,7 +181,47 @@ pub fn run() { .manage(Terminals::default()) .manage(Notifications::default()) .manage(PluginManager(Manager::from_env())) + .setup(|app| { + let enabled = app + .state::() + .0 + .as_ref() + .ok() + .and_then(|m| m.catalog().ok()) + .is_some_and(|catalog| { + catalog + .plugins + .iter() + .any(|p| p.manifest.id == "buzz.community-compute" && p.enabled) + }); + let host = ComputeHost::new( + app.path().app_data_dir().ok(), + app.config().build.dev_url.as_ref().map(ToString::to_string), + enabled, + ); + app.manage(host.clone()); + app.manage(agent_runner::AgentRunner::new( + app.path().app_data_dir()?, + std::env::current_exe()? + .parent() + .ok_or("Missing app executable directory")? + .join("agent-runtime"), + host.clone(), + )); + host.restore(); + Ok(()) + }) .invoke_handler(tauri::generate_handler![ + agent_runner::agent_runner_status, + agent_runner::agent_runner_start, + agent_runner::agent_runner_stop, + compute_widget::compute_widget_open, + community_compute::community_compute_test, + community_compute::community_compute_status, + community_compute::community_compute_models, + community_compute::community_compute_start, + community_compute::community_compute_stop, + community_compute::community_compute_snapshot, notification_show, terminal_create_owner, terminal_spawn, @@ -184,6 +243,8 @@ pub fn run() { .expect("failed to build Buzz Foundation") .run(|app, event| { if matches!(event, tauri::RunEvent::Exit) { + let _ = app.state::().stop(); + let _ = app.state::().stop(None, false); if let Err(error) = app.state::().shutdown() { eprintln!("Terminal shutdown failed: {error}"); } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index caba16690..99650f21c 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,5 +1,13 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] fn main() { + if std::env::args().nth(1).as_deref() + == Some(buzz_foundation_lib::agent_runner::supervisor::ARG) + { + buzz_foundation_lib::agent_runner::supervisor::entry(); + } + if std::env::args().nth(1).as_deref() == Some(buzz_community_compute::worker::WORKER_ARG) { + buzz_community_compute::worker::entry(); + } buzz_foundation_lib::run(); } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 0d66c59c6..815712f4c 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -10,6 +10,7 @@ "frontendDist": "../dist" }, "app": { + "macOSPrivateApi": true, "windows": [ { "title": "Buzz Foundation", diff --git a/src/app/Settings.tsx b/src/app/Settings.tsx index 5e2ac1ae1..6595cdf94 100644 --- a/src/app/Settings.tsx +++ b/src/app/Settings.tsx @@ -7,6 +7,7 @@ import { PaletteIcon, BellIcon, WrenchIcon, + CpuIcon, } from "../shared/design-system/icons/index"; import type { PluginManager } from "../plugins/manager"; import type { Communities } from "../features/communities/service"; @@ -19,6 +20,8 @@ import { NotificationSettings } from "./NotificationSettings"; import type { NotificationsService } from "../features/notifications/service"; import { DeveloperSettings } from "./DeveloperSettings"; +import { CommunityComputePage } from "../bundled/community-compute/CommunityComputePage"; + type Section = { id: string; label: string; icon: typeof UserIcon }; const baseSections: Section[] = [ @@ -55,6 +58,13 @@ export function Settings({ | undefined; onSection?: (section: string) => void; }) { + const { configuration, activation, busy, error, refreshError } = + useSyncExternalStore(plugins.subscribe, plugins.snapshot); + const computeEnabled = + activation["buzz.community-compute"]?.status === "active"; + const availableSections = computeEnabled + ? [...sections, { id: "compute", label: "Compute", icon: CpuIcon }] + : sections; const [selected, setSelected] = useState<(typeof sections)[number]["id"]>("profile"); const requestedSection = @@ -64,7 +74,9 @@ export function Settings({ useEffect(() => { if ( requestedSection && - sections.some((section) => section.id === requestedSection) + [...sections, { id: "compute" }].some( + (section) => section.id === requestedSection, + ) ) setSelected(requestedSection as typeof selected); }, [requestedSection]); @@ -72,8 +84,6 @@ export function Settings({ if (requestedSection === selected) navigation?.complete({ status: "opened" }); }, [navigation, requestedSection, selected]); - const { configuration, activation, busy, error, refreshError } = - useSyncExternalStore(plugins.subscribe, plugins.snapshot); const ready = configuration.status === "ready" ? configuration : undefined; const catalog = ready?.catalog; const externalPluginsPaused = ready?.externalPluginsPaused; @@ -85,7 +95,7 @@ export function Settings({ Settings