Refactor syscall modules to use async/await patterns
- Updated `DisplaySyscall` to use `tokio::io::AsyncWriteExt` for asynchronous text output. - Refactored `FsSyscall` to read files asynchronously with offset and length handling. - Modified `MemorySyscall` to use `tokio::task::spawn_blocking` for database operations, allowing async access to SQLite. - Enhanced `NetworkSyscall` to utilize `reqwest` for HTTP requests, replacing the previous `curl` command execution. - Improved `ProcessSyscall` to manage subprocesses with async tasks for stdin, stdout, and stderr handling. - Updated `TimerSyscall` to simplify timer management. - Adjusted plugin implementations for better async support and error handling. - Added `tokio-stream` and `tracing-subscriber` dependencies to `Cargo.toml` for enhanced async stream handling and logging.
This commit is contained in:
726
Cargo.lock
generated
726
Cargo.lock
generated
@@ -8,8 +8,10 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"agentsd-proto",
|
"agentsd-proto",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"libc",
|
||||||
"notify",
|
"notify",
|
||||||
"prost",
|
"prost",
|
||||||
|
"reqwest",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -21,6 +23,76 @@ dependencies = [
|
|||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "agentsd-plugin-ai-test"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"agentsd-plugin-sdk",
|
||||||
|
"agentsd-proto",
|
||||||
|
"anyhow",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
|
"tonic",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "agentsd-plugin-claudecode"
|
||||||
|
version = "2.1.169"
|
||||||
|
dependencies = [
|
||||||
|
"agentsd-plugin-sdk",
|
||||||
|
"agentsd-proto",
|
||||||
|
"anyhow",
|
||||||
|
"futures-core",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
|
"tonic",
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "agentsd-plugin-echo"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"agentsd-plugin-sdk",
|
||||||
|
"agentsd-proto",
|
||||||
|
"anyhow",
|
||||||
|
"tokio",
|
||||||
|
"tonic",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "agentsd-plugin-hermes"
|
||||||
|
version = "0.16.0"
|
||||||
|
dependencies = [
|
||||||
|
"agentsd-plugin-sdk",
|
||||||
|
"agentsd-proto",
|
||||||
|
"anyhow",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
|
"tonic",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "agentsd-plugin-sdk"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"agentsd-proto",
|
||||||
|
"anyhow",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
|
"tonic",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "agentsd-proto"
|
name = "agentsd-proto"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -195,6 +267,23 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg_aliases"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "displaydoc"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.16.0"
|
version = "1.16.0"
|
||||||
@@ -269,6 +358,15 @@ version = "0.1.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "form_urlencoded"
|
||||||
|
version = "1.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
|
||||||
|
dependencies = [
|
||||||
|
"percent-encoding",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fsevent-sys"
|
name = "fsevent-sys"
|
||||||
version = "4.1.0"
|
version = "4.1.0"
|
||||||
@@ -324,8 +422,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
|
"libc",
|
||||||
|
"r-efi 5.3.0",
|
||||||
|
"wasip2",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -336,7 +450,7 @@ checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"r-efi",
|
"r-efi 6.0.0",
|
||||||
"wasip2",
|
"wasip2",
|
||||||
"wasip3",
|
"wasip3",
|
||||||
]
|
]
|
||||||
@@ -472,6 +586,22 @@ dependencies = [
|
|||||||
"want",
|
"want",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper-rustls"
|
||||||
|
version = "0.27.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
|
||||||
|
dependencies = [
|
||||||
|
"http",
|
||||||
|
"hyper",
|
||||||
|
"hyper-util",
|
||||||
|
"rustls",
|
||||||
|
"tokio",
|
||||||
|
"tokio-rustls",
|
||||||
|
"tower-service",
|
||||||
|
"webpki-roots",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-timeout"
|
name = "hyper-timeout"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
@@ -491,13 +621,16 @@ version = "0.1.20"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
|
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base64",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
"hyper",
|
"hyper",
|
||||||
|
"ipnet",
|
||||||
"libc",
|
"libc",
|
||||||
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2 0.6.4",
|
"socket2 0.6.4",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -505,12 +638,115 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "icu_collections"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
|
||||||
|
dependencies = [
|
||||||
|
"displaydoc",
|
||||||
|
"potential_utf",
|
||||||
|
"utf8_iter",
|
||||||
|
"yoke",
|
||||||
|
"zerofrom",
|
||||||
|
"zerovec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "icu_locale_core"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
|
||||||
|
dependencies = [
|
||||||
|
"displaydoc",
|
||||||
|
"litemap",
|
||||||
|
"tinystr",
|
||||||
|
"writeable",
|
||||||
|
"zerovec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "icu_normalizer"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
|
||||||
|
dependencies = [
|
||||||
|
"icu_collections",
|
||||||
|
"icu_normalizer_data",
|
||||||
|
"icu_properties",
|
||||||
|
"icu_provider",
|
||||||
|
"smallvec",
|
||||||
|
"zerovec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "icu_normalizer_data"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "icu_properties"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
|
||||||
|
dependencies = [
|
||||||
|
"icu_collections",
|
||||||
|
"icu_locale_core",
|
||||||
|
"icu_properties_data",
|
||||||
|
"icu_provider",
|
||||||
|
"zerotrie",
|
||||||
|
"zerovec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "icu_properties_data"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "icu_provider"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
|
||||||
|
dependencies = [
|
||||||
|
"displaydoc",
|
||||||
|
"icu_locale_core",
|
||||||
|
"writeable",
|
||||||
|
"yoke",
|
||||||
|
"zerofrom",
|
||||||
|
"zerotrie",
|
||||||
|
"zerovec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "id-arena"
|
name = "id-arena"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.9.3"
|
version = "1.9.3"
|
||||||
@@ -562,6 +798,12 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ipnet"
|
||||||
|
version = "2.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
@@ -643,6 +885,12 @@ version = "0.12.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "litemap"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.14"
|
version = "0.4.14"
|
||||||
@@ -658,6 +906,12 @@ version = "0.4.32"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
|
checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lru-slab"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matchers"
|
name = "matchers"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -817,6 +1071,15 @@ version = "0.3.33"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
|
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "potential_utf"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
|
||||||
|
dependencies = [
|
||||||
|
"zerovec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.21"
|
version = "0.2.21"
|
||||||
@@ -897,6 +1160,61 @@ dependencies = [
|
|||||||
"prost",
|
"prost",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quinn"
|
||||||
|
version = "0.11.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"cfg_aliases",
|
||||||
|
"pin-project-lite",
|
||||||
|
"quinn-proto",
|
||||||
|
"quinn-udp",
|
||||||
|
"rustc-hash",
|
||||||
|
"rustls",
|
||||||
|
"socket2 0.6.4",
|
||||||
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
"web-time",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quinn-proto"
|
||||||
|
version = "0.11.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"getrandom 0.3.4",
|
||||||
|
"lru-slab",
|
||||||
|
"rand 0.9.4",
|
||||||
|
"ring",
|
||||||
|
"rustc-hash",
|
||||||
|
"rustls",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"slab",
|
||||||
|
"thiserror",
|
||||||
|
"tinyvec",
|
||||||
|
"tracing",
|
||||||
|
"web-time",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quinn-udp"
|
||||||
|
version = "0.5.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
|
||||||
|
dependencies = [
|
||||||
|
"cfg_aliases",
|
||||||
|
"libc",
|
||||||
|
"once_cell",
|
||||||
|
"socket2 0.6.4",
|
||||||
|
"tracing",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.45"
|
version = "1.0.45"
|
||||||
@@ -906,6 +1224,12 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "r-efi"
|
||||||
|
version = "5.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "r-efi"
|
name = "r-efi"
|
||||||
version = "6.0.0"
|
version = "6.0.0"
|
||||||
@@ -919,8 +1243,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
|
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"rand_chacha",
|
"rand_chacha 0.3.1",
|
||||||
"rand_core",
|
"rand_core 0.6.4",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
|
||||||
|
dependencies = [
|
||||||
|
"rand_chacha 0.9.0",
|
||||||
|
"rand_core 0.9.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -930,7 +1264,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ppv-lite86",
|
"ppv-lite86",
|
||||||
"rand_core",
|
"rand_core 0.6.4",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core 0.9.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -942,6 +1286,15 @@ dependencies = [
|
|||||||
"getrandom 0.2.17",
|
"getrandom 0.2.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.3.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.18"
|
version = "0.5.18"
|
||||||
@@ -980,6 +1333,58 @@ version = "0.8.10"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "reqwest"
|
||||||
|
version = "0.12.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"bytes",
|
||||||
|
"futures-core",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"http-body-util",
|
||||||
|
"hyper",
|
||||||
|
"hyper-rustls",
|
||||||
|
"hyper-util",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
"quinn",
|
||||||
|
"rustls",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_urlencoded",
|
||||||
|
"sync_wrapper",
|
||||||
|
"tokio",
|
||||||
|
"tokio-rustls",
|
||||||
|
"tower 0.5.3",
|
||||||
|
"tower-http",
|
||||||
|
"tower-service",
|
||||||
|
"url",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
"webpki-roots",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "rusqlite"
|
name = "rusqlite"
|
||||||
version = "0.32.1"
|
version = "0.32.1"
|
||||||
@@ -994,6 +1399,12 @@ dependencies = [
|
|||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-hash"
|
||||||
|
version = "2.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "1.1.4"
|
version = "1.1.4"
|
||||||
@@ -1007,12 +1418,53 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls"
|
||||||
|
version = "0.23.40"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"ring",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"rustls-webpki",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-pki-types"
|
||||||
|
version = "1.14.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
|
||||||
|
dependencies = [
|
||||||
|
"web-time",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-webpki"
|
||||||
|
version = "0.103.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
|
||||||
|
dependencies = [
|
||||||
|
"ring",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"untrusted",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.22"
|
version = "1.0.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
@@ -1077,6 +1529,18 @@ dependencies = [
|
|||||||
"zmij",
|
"zmij",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "sharded-slab"
|
name = "sharded-slab"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
@@ -1134,6 +1598,18 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stable_deref_trait"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "subtle"
|
||||||
|
version = "2.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.117"
|
version = "2.0.117"
|
||||||
@@ -1150,6 +1626,20 @@ name = "sync_wrapper"
|
|||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "synstructure"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
@@ -1164,6 +1654,26 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "2.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "2.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thread_local"
|
name = "thread_local"
|
||||||
version = "1.1.9"
|
version = "1.1.9"
|
||||||
@@ -1173,6 +1683,31 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinystr"
|
||||||
|
version = "0.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
|
||||||
|
dependencies = [
|
||||||
|
"displaydoc",
|
||||||
|
"zerovec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec"
|
||||||
|
version = "1.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec_macros"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.52.3"
|
version = "1.52.3"
|
||||||
@@ -1201,6 +1736,16 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-rustls"
|
||||||
|
version = "0.26.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
|
||||||
|
dependencies = [
|
||||||
|
"rustls",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-stream"
|
name = "tokio-stream"
|
||||||
version = "0.1.18"
|
version = "0.1.18"
|
||||||
@@ -1280,7 +1825,7 @@ dependencies = [
|
|||||||
"indexmap 1.9.3",
|
"indexmap 1.9.3",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"rand",
|
"rand 0.8.6",
|
||||||
"slab",
|
"slab",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
@@ -1299,10 +1844,29 @@ dependencies = [
|
|||||||
"futures-util",
|
"futures-util",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"sync_wrapper",
|
"sync_wrapper",
|
||||||
|
"tokio",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-http"
|
||||||
|
version = "0.6.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.0",
|
||||||
|
"bytes",
|
||||||
|
"futures-util",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tower 0.5.3",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-layer"
|
name = "tower-layer"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
@@ -1394,6 +1958,30 @@ version = "0.2.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||||
|
|
||||||
|
[[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"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
|
||||||
|
dependencies = [
|
||||||
|
"form_urlencoded",
|
||||||
|
"idna",
|
||||||
|
"percent-encoding",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8_iter"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.23.2"
|
version = "1.23.2"
|
||||||
@@ -1479,6 +2067,16 @@ dependencies = [
|
|||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-futures"
|
||||||
|
version = "0.4.73"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54568702fabf5d4849ce2b90fadfa64168a097eaf4b351ce9df8b687a0086aaf"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.123"
|
version = "0.2.123"
|
||||||
@@ -1545,6 +2143,35 @@ dependencies = [
|
|||||||
"semver",
|
"semver",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-sys"
|
||||||
|
version = "0.3.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e0871acf327f283dc6da28a1696cdc64fb355ba9f935d052021fa77f35cce69"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"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 = "webpki-roots"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d"
|
||||||
|
dependencies = [
|
||||||
|
"rustls-pki-types",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-util"
|
name = "winapi-util"
|
||||||
version = "0.1.11"
|
version = "0.1.11"
|
||||||
@@ -1736,6 +2363,35 @@ dependencies = [
|
|||||||
"wasmparser",
|
"wasmparser",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "writeable"
|
||||||
|
version = "0.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yoke"
|
||||||
|
version = "0.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
|
||||||
|
dependencies = [
|
||||||
|
"stable_deref_trait",
|
||||||
|
"yoke-derive",
|
||||||
|
"zerofrom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yoke-derive"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"synstructure",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.50"
|
version = "0.8.50"
|
||||||
@@ -1756,6 +2412,66 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerofrom"
|
||||||
|
version = "0.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
|
||||||
|
dependencies = [
|
||||||
|
"zerofrom-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerofrom-derive"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"synstructure",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zeroize"
|
||||||
|
version = "1.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerotrie"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
|
||||||
|
dependencies = [
|
||||||
|
"displaydoc",
|
||||||
|
"yoke",
|
||||||
|
"zerofrom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerovec"
|
||||||
|
version = "0.11.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
|
||||||
|
dependencies = [
|
||||||
|
"yoke",
|
||||||
|
"zerofrom",
|
||||||
|
"zerovec-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerovec-derive"
|
||||||
|
version = "0.11.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zmij"
|
name = "zmij"
|
||||||
version = "1.0.21"
|
version = "1.0.21"
|
||||||
|
|||||||
@@ -25,3 +25,5 @@ anyhow = "1"
|
|||||||
uuid = { version = "1", features = ["v4"] }
|
uuid = { version = "1", features = ["v4"] }
|
||||||
notify = "7"
|
notify = "7"
|
||||||
futures-core = "0.3"
|
futures-core = "0.3"
|
||||||
|
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }
|
||||||
|
libc = "0.2"
|
||||||
|
|||||||
@@ -21,3 +21,5 @@ tracing-subscriber = { workspace = true }
|
|||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
uuid = { workspace = true }
|
uuid = { workspace = true }
|
||||||
notify = { workspace = true }
|
notify = { workspace = true }
|
||||||
|
reqwest = { workspace = true }
|
||||||
|
libc = { workspace = true }
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ pub struct CallbackBus {
|
|||||||
senders: Mutex<HashMap<String, mpsc::Sender<TimerEvent>>>,
|
senders: Mutex<HashMap<String, mpsc::Sender<TimerEvent>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for CallbackBus {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl CallbackBus {
|
impl CallbackBus {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -35,8 +41,18 @@ impl CallbackBus {
|
|||||||
pub fn fire(&self, plugin_id: &str, event: TimerEvent) {
|
pub fn fire(&self, plugin_id: &str, event: TimerEvent) {
|
||||||
let senders = self.senders.lock().unwrap();
|
let senders = self.senders.lock().unwrap();
|
||||||
if let Some(tx) = senders.get(plugin_id) {
|
if let Some(tx) = senders.get(plugin_id) {
|
||||||
// Non-blocking send; drop if channel full
|
match tx.try_send(event) {
|
||||||
let _ = tx.try_send(event);
|
Ok(()) => {}
|
||||||
|
Err(mpsc::error::TrySendError::Full(_)) => {
|
||||||
|
tracing::warn!("timer event dropped: channel full for plugin={}", plugin_id);
|
||||||
|
}
|
||||||
|
Err(mpsc::error::TrySendError::Closed(_)) => {
|
||||||
|
tracing::debug!(
|
||||||
|
"timer event dropped: channel closed for plugin={}",
|
||||||
|
plugin_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
mod syscall;
|
pub mod callback;
|
||||||
|
pub mod paths;
|
||||||
|
pub mod permission;
|
||||||
mod plugin;
|
mod plugin;
|
||||||
mod server;
|
mod server;
|
||||||
pub mod paths;
|
mod syscall;
|
||||||
pub mod callback;
|
|
||||||
pub mod permission;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ use std::path::PathBuf;
|
|||||||
/// Creates it if it does not exist.
|
/// Creates it if it does not exist.
|
||||||
pub fn data_dir() -> PathBuf {
|
pub fn data_dir() -> PathBuf {
|
||||||
let exe = std::env::current_exe().unwrap_or_else(|_| PathBuf::from("."));
|
let exe = std::env::current_exe().unwrap_or_else(|_| PathBuf::from("."));
|
||||||
let dir = exe.parent().unwrap_or_else(|| std::path::Path::new(".")).join("data");
|
let dir = exe
|
||||||
|
.parent()
|
||||||
|
.unwrap_or_else(|| std::path::Path::new("."))
|
||||||
|
.join("data");
|
||||||
if !dir.exists() {
|
if !dir.exists() {
|
||||||
std::fs::create_dir_all(&dir).ok();
|
std::fs::create_dir_all(&dir).ok();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,13 +12,14 @@ pub fn plugin_id_from_metadata(req_metadata: &tonic::metadata::MetadataMap) -> O
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Load a plugin and require it to be currently running.
|
/// Load a plugin and require it to be currently running.
|
||||||
|
#[allow(clippy::result_large_err)]
|
||||||
pub fn require_running_plugin(
|
pub fn require_running_plugin(
|
||||||
plugins: &Arc<PluginManager>,
|
plugins: &Arc<PluginManager>,
|
||||||
plugin_id: &str,
|
plugin_id: &str,
|
||||||
) -> Result<PluginRecord, Status> {
|
) -> Result<PluginRecord, Status> {
|
||||||
let record = plugins.get(plugin_id).ok_or_else(|| {
|
let record = plugins
|
||||||
Status::permission_denied(format!("unknown plugin: {}", plugin_id))
|
.get(plugin_id)
|
||||||
})?;
|
.ok_or_else(|| Status::permission_denied(format!("unknown plugin: {}", plugin_id)))?;
|
||||||
|
|
||||||
if record.status != PluginStatus::Running {
|
if record.status != PluginStatus::Running {
|
||||||
return Err(Status::permission_denied(format!(
|
return Err(Status::permission_denied(format!(
|
||||||
@@ -38,6 +39,7 @@ pub fn require_running_plugin(
|
|||||||
/// - `fs` grants all fs syscalls
|
/// - `fs` grants all fs syscalls
|
||||||
/// - `fs.*` grants all fs syscalls
|
/// - `fs.*` grants all fs syscalls
|
||||||
/// - `fs.list` grants only that method
|
/// - `fs.list` grants only that method
|
||||||
|
#[allow(clippy::result_large_err)]
|
||||||
pub fn check_permission(
|
pub fn check_permission(
|
||||||
plugins: &Arc<PluginManager>,
|
plugins: &Arc<PluginManager>,
|
||||||
req_metadata: &tonic::metadata::MetadataMap,
|
req_metadata: &tonic::metadata::MetadataMap,
|
||||||
@@ -73,6 +75,7 @@ pub fn check_permission(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Helper to extract plugin_id and check permission in one call within a service method.
|
/// Helper to extract plugin_id and check permission in one call within a service method.
|
||||||
|
#[allow(clippy::result_large_err)]
|
||||||
pub fn guard<T>(
|
pub fn guard<T>(
|
||||||
plugins: &Arc<PluginManager>,
|
plugins: &Arc<PluginManager>,
|
||||||
req: &Request<T>,
|
req: &Request<T>,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
|||||||
use std::io::Write as IoWrite;
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
/// Display syscall: output to user.
|
/// Display syscall: output to user.
|
||||||
pub struct DisplaySyscall;
|
pub struct DisplaySyscall;
|
||||||
@@ -8,32 +8,34 @@ impl DisplaySyscall {
|
|||||||
Self
|
Self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn text(&self, content: &str) -> Result<(), String> {
|
pub async fn text(&self, content: &str) -> Result<(), String> {
|
||||||
let stdout = std::io::stdout();
|
let mut stdout = tokio::io::stdout();
|
||||||
let mut handle = stdout.lock();
|
stdout
|
||||||
handle
|
|
||||||
.write_all(content.as_bytes())
|
.write_all(content.as_bytes())
|
||||||
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
handle.write_all(b"\n").map_err(|e| e.to_string())?;
|
stdout.write_all(b"\n").await.map_err(|e| e.to_string())?;
|
||||||
handle.flush().map_err(|e| e.to_string())
|
stdout.flush().await.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rich(&self, markup: &str, _format: &str) -> Result<(), String> {
|
pub async fn rich(&self, markup: &str, _format: &str) -> Result<(), String> {
|
||||||
// For now, render as plain text. Rich rendering is a system plugin concern.
|
self.text(markup).await
|
||||||
self.text(markup)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn image(&self, _data: &[u8], _format: &str) -> Result<(), String> {
|
pub async fn image(&self, _data: &[u8], _format: &str) -> Result<(), String> {
|
||||||
self.text("[image]")
|
self.text("[image]").await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear(&self) -> Result<(), String> {
|
pub async fn clear(&self) -> Result<(), String> {
|
||||||
// ANSI clear
|
let mut stdout = tokio::io::stdout();
|
||||||
print!("\x1b[2J\x1b[H");
|
stdout
|
||||||
std::io::stdout().flush().map_err(|e| e.to_string())
|
.write_all(b"\x1b[2J\x1b[H")
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
stdout.flush().await.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn notify(&self, message: &str) -> Result<(), String> {
|
pub async fn notify(&self, message: &str) -> Result<(), String> {
|
||||||
self.text(&format!("[notify] {}", message))
|
self.text(&format!("[notify] {}", message)).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use tokio::fs as async_fs;
|
use tokio::fs as async_fs;
|
||||||
|
use tokio::io::{AsyncReadExt, AsyncSeekExt};
|
||||||
|
|
||||||
|
const MAX_READ_SIZE: usize = 64 * 1024 * 1024; // 64 MB
|
||||||
|
|
||||||
/// FS syscall: file system operations.
|
/// FS syscall: file system operations.
|
||||||
pub struct FsSyscall;
|
pub struct FsSyscall;
|
||||||
@@ -10,17 +13,25 @@ impl FsSyscall {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read(&self, path: &str, offset: i64, length: i64) -> Result<Vec<u8>, String> {
|
pub async fn read(&self, path: &str, offset: i64, length: i64) -> Result<Vec<u8>, String> {
|
||||||
let data = async_fs::read(path).await.map_err(|e| e.to_string())?;
|
let mut file = tokio::fs::File::open(path)
|
||||||
let start = if offset > 0 { offset as usize } else { 0 };
|
.await
|
||||||
let end = if length > 0 {
|
.map_err(|e| e.to_string())?;
|
||||||
std::cmp::min(start + length as usize, data.len())
|
if offset > 0 {
|
||||||
} else {
|
file.seek(std::io::SeekFrom::Start(offset as u64))
|
||||||
data.len()
|
.await
|
||||||
};
|
.map_err(|e| e.to_string())?;
|
||||||
if start >= data.len() {
|
|
||||||
return Ok(Vec::new());
|
|
||||||
}
|
}
|
||||||
Ok(data[start..end].to_vec())
|
let len = if length > 0 {
|
||||||
|
std::cmp::min(length as usize, MAX_READ_SIZE)
|
||||||
|
} else {
|
||||||
|
let meta = file.metadata().await.map_err(|e| e.to_string())?;
|
||||||
|
let remaining = (meta.len() as usize).saturating_sub(offset.max(0) as usize);
|
||||||
|
std::cmp::min(remaining, MAX_READ_SIZE)
|
||||||
|
};
|
||||||
|
let mut buf = vec![0u8; len];
|
||||||
|
let n = file.read(&mut buf).await.map_err(|e| e.to_string())?;
|
||||||
|
buf.truncate(n);
|
||||||
|
Ok(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn write(&self, path: &str, data: &[u8], append: bool) -> Result<(), String> {
|
pub async fn write(&self, path: &str, data: &[u8], append: bool) -> Result<(), String> {
|
||||||
@@ -50,9 +61,7 @@ impl FsSyscall {
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())
|
.map_err(|e| e.to_string())
|
||||||
} else {
|
} else {
|
||||||
async_fs::remove_file(path)
|
async_fs::remove_file(path).await.map_err(|e| e.to_string())
|
||||||
.await
|
|
||||||
.map_err(|e| e.to_string())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use rusqlite::{params, Connection};
|
use rusqlite::{params, Connection};
|
||||||
use std::sync::Mutex;
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
/// Memory syscall: structured KV storage with FTS5 full-text search.
|
/// Memory syscall: structured KV storage with FTS5 full-text search.
|
||||||
/// Data is persisted to `<exe_dir>/data/memory.db`.
|
/// Data is persisted to `<exe_dir>/data/memory.db`.
|
||||||
pub struct MemorySyscall {
|
pub struct MemorySyscall {
|
||||||
conn: Mutex<Connection>,
|
conn: Arc<Mutex<Connection>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MemorySyscall {
|
impl MemorySyscall {
|
||||||
@@ -12,7 +12,9 @@ impl MemorySyscall {
|
|||||||
let db_path = crate::paths::data_dir().join("memory.db");
|
let db_path = crate::paths::data_dir().join("memory.db");
|
||||||
let conn = Connection::open(&db_path)?;
|
let conn = Connection::open(&db_path)?;
|
||||||
conn.execute_batch(
|
conn.execute_batch(
|
||||||
"CREATE TABLE IF NOT EXISTS kv (
|
"PRAGMA journal_mode=WAL;
|
||||||
|
PRAGMA busy_timeout=5000;
|
||||||
|
CREATE TABLE IF NOT EXISTS kv (
|
||||||
key TEXT PRIMARY KEY,
|
key TEXT PRIMARY KEY,
|
||||||
value BLOB NOT NULL,
|
value BLOB NOT NULL,
|
||||||
text_value TEXT,
|
text_value TEXT,
|
||||||
@@ -34,12 +36,15 @@ impl MemorySyscall {
|
|||||||
)?;
|
)?;
|
||||||
tracing::info!("memory: opened {}", db_path.display());
|
tracing::info!("memory: opened {}", db_path.display());
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
conn: Mutex::new(conn),
|
conn: Arc::new(Mutex::new(conn)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read(&self, key: &str) -> Result<Option<Vec<u8>>, String> {
|
pub async fn read(&self, key: &str) -> Result<Option<Vec<u8>>, String> {
|
||||||
let conn = self.conn.lock().unwrap();
|
let conn = self.conn.clone();
|
||||||
|
let key = key.to_string();
|
||||||
|
tokio::task::spawn_blocking(move || {
|
||||||
|
let conn = conn.lock().unwrap();
|
||||||
let mut stmt = conn
|
let mut stmt = conn
|
||||||
.prepare("SELECT value FROM kv WHERE key = ?1")
|
.prepare("SELECT value FROM kv WHERE key = ?1")
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
@@ -47,28 +52,48 @@ impl MemorySyscall {
|
|||||||
.query_row(params![key], |row| row.get::<_, Vec<u8>>(0))
|
.query_row(params![key], |row| row.get::<_, Vec<u8>>(0))
|
||||||
.ok();
|
.ok();
|
||||||
Ok(result)
|
Ok(result)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(&self, key: &str, value: &[u8]) -> Result<(), String> {
|
pub async fn write(&self, key: &str, value: &[u8]) -> Result<(), String> {
|
||||||
let conn = self.conn.lock().unwrap();
|
let conn = self.conn.clone();
|
||||||
let text_value = String::from_utf8(value.to_vec()).ok();
|
let key = key.to_string();
|
||||||
|
let value = value.to_vec();
|
||||||
|
tokio::task::spawn_blocking(move || {
|
||||||
|
let conn = conn.lock().unwrap();
|
||||||
|
let text_value: Option<String> =
|
||||||
|
std::str::from_utf8(&value).ok().map(str::to_owned);
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT OR REPLACE INTO kv (key, value, text_value, updated_at) VALUES (?1, ?2, ?3, strftime('%s','now'))",
|
"INSERT OR REPLACE INTO kv (key, value, text_value, updated_at) VALUES (?1, ?2, ?3, strftime('%s','now'))",
|
||||||
params![key, value, text_value],
|
params![key, value, text_value],
|
||||||
)
|
)
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(&self, key: &str) -> Result<(), String> {
|
pub async fn delete(&self, key: &str) -> Result<(), String> {
|
||||||
let conn = self.conn.lock().unwrap();
|
let conn = self.conn.clone();
|
||||||
|
let key = key.to_string();
|
||||||
|
tokio::task::spawn_blocking(move || {
|
||||||
|
let conn = conn.lock().unwrap();
|
||||||
conn.execute("DELETE FROM kv WHERE key = ?1", params![key])
|
conn.execute("DELETE FROM kv WHERE key = ?1", params![key])
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list(&self, prefix: &str, limit: i32) -> Result<Vec<String>, String> {
|
pub async fn list(&self, prefix: &str, limit: i32) -> Result<Vec<String>, String> {
|
||||||
let conn = self.conn.lock().unwrap();
|
let conn = self.conn.clone();
|
||||||
|
let prefix = prefix.to_string();
|
||||||
|
tokio::task::spawn_blocking(move || {
|
||||||
|
let conn = conn.lock().unwrap();
|
||||||
let lim = if limit > 0 { limit } else { 1000 };
|
let lim = if limit > 0 { limit } else { 1000 };
|
||||||
let mut stmt = conn
|
let mut stmt = conn
|
||||||
.prepare("SELECT key FROM kv WHERE key LIKE ?1 ORDER BY key LIMIT ?2")
|
.prepare("SELECT key FROM kv WHERE key LIKE ?1 ORDER BY key LIMIT ?2")
|
||||||
@@ -82,10 +107,20 @@ impl MemorySyscall {
|
|||||||
keys.push(row.map_err(|e| e.to_string())?);
|
keys.push(row.map_err(|e| e.to_string())?);
|
||||||
}
|
}
|
||||||
Ok(keys)
|
Ok(keys)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn search(&self, query: &str, limit: i32) -> Result<Vec<(String, String, f32)>, String> {
|
pub async fn search(
|
||||||
let conn = self.conn.lock().unwrap();
|
&self,
|
||||||
|
query: &str,
|
||||||
|
limit: i32,
|
||||||
|
) -> Result<Vec<(String, String, f32)>, String> {
|
||||||
|
let conn = self.conn.clone();
|
||||||
|
let query = query.to_string();
|
||||||
|
tokio::task::spawn_blocking(move || {
|
||||||
|
let conn = conn.lock().unwrap();
|
||||||
let lim = if limit > 0 { limit } else { 20 };
|
let lim = if limit > 0 { limit } else { 20 };
|
||||||
let mut stmt = conn
|
let mut stmt = conn
|
||||||
.prepare(
|
.prepare(
|
||||||
@@ -107,5 +142,8 @@ impl MemorySyscall {
|
|||||||
hits.push(row.map_err(|e| e.to_string())?);
|
hits.push(row.map_err(|e| e.to_string())?);
|
||||||
}
|
}
|
||||||
Ok(hits)
|
Ok(hits)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
pub mod display;
|
|
||||||
pub mod audio;
|
pub mod audio;
|
||||||
|
pub mod display;
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
|
pub mod hid;
|
||||||
|
pub mod input;
|
||||||
pub mod memory;
|
pub mod memory;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
pub mod process;
|
pub mod process;
|
||||||
pub mod timer;
|
pub mod timer;
|
||||||
pub mod hid;
|
|
||||||
pub mod input;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|||||||
@@ -1,60 +1,94 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
const MAX_RESPONSE_BODY: u64 = 16 * 1024 * 1024; // 16 MB
|
||||||
|
|
||||||
/// Network syscall: remote communication.
|
/// Network syscall: remote communication.
|
||||||
pub struct NetworkSyscall;
|
pub struct NetworkSyscall {
|
||||||
|
client: reqwest::Client,
|
||||||
|
}
|
||||||
|
|
||||||
impl NetworkSyscall {
|
impl NetworkSyscall {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self
|
let client = reqwest::Client::builder()
|
||||||
|
.connect_timeout(Duration::from_secs(5))
|
||||||
|
.read_timeout(Duration::from_secs(30))
|
||||||
|
.timeout(Duration::from_secs(60))
|
||||||
|
.redirect(reqwest::redirect::Policy::limited(5))
|
||||||
|
.build()
|
||||||
|
.unwrap_or_else(|_| reqwest::Client::new());
|
||||||
|
Self { client }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn available(&self) -> bool {
|
pub async fn available(&self) -> bool {
|
||||||
// Simple connectivity check: try to resolve a known host
|
tokio::time::timeout(
|
||||||
std::net::TcpStream::connect_timeout(
|
Duration::from_secs(2),
|
||||||
&std::net::SocketAddr::from(([8, 8, 8, 8], 53)),
|
tokio::net::TcpStream::connect(SocketAddr::from(([8, 8, 8, 8], 53))),
|
||||||
std::time::Duration::from_secs(2),
|
|
||||||
)
|
)
|
||||||
.is_ok()
|
.await
|
||||||
|
.map(|result| result.is_ok())
|
||||||
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn request(
|
pub async fn request(
|
||||||
&self,
|
&self,
|
||||||
url: &str,
|
url: &str,
|
||||||
method: &str,
|
method: &str,
|
||||||
headers: &std::collections::HashMap<String, String>,
|
headers: &HashMap<String, String>,
|
||||||
body: &[u8],
|
body: &[u8],
|
||||||
) -> Result<(i32, std::collections::HashMap<String, String>, Vec<u8>), String> {
|
) -> Result<(i32, HashMap<String, String>, Vec<u8>), String> {
|
||||||
// Minimal HTTP client using tokio TcpStream + manual HTTP.
|
let method = method
|
||||||
// For production, use hyper or reqwest. For now, shell out to a basic impl.
|
.parse::<reqwest::Method>()
|
||||||
use tokio::process::Command;
|
.map_err(|e| format!("invalid HTTP method {method:?}: {e}"))?;
|
||||||
|
let mut request = self.client.request(method, url);
|
||||||
|
|
||||||
let mut cmd = Command::new("curl");
|
for (name, value) in headers {
|
||||||
cmd.arg("-s")
|
let header_name = name
|
||||||
.arg("-X")
|
.parse::<reqwest::header::HeaderName>()
|
||||||
.arg(method)
|
.map_err(|e| format!("invalid HTTP header name {name:?}: {e}"))?;
|
||||||
.arg("-o")
|
let header_value = value
|
||||||
.arg("-")
|
.parse::<reqwest::header::HeaderValue>()
|
||||||
.arg("-w")
|
.map_err(|e| format!("invalid HTTP header value for {name}: {e}"))?;
|
||||||
.arg("\n%{http_code}");
|
request = request.header(header_name, header_value);
|
||||||
|
|
||||||
for (k, v) in headers {
|
|
||||||
cmd.arg("-H").arg(format!("{}: {}", k, v));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !body.is_empty() {
|
if !body.is_empty() {
|
||||||
cmd.arg("-d").arg(String::from_utf8_lossy(body).to_string());
|
request = request.body(body.to_vec());
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.arg(url);
|
let response = request.send().await.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let output = cmd.output().await.map_err(|e| e.to_string())?;
|
// Check content-length before downloading body
|
||||||
let raw = String::from_utf8_lossy(&output.stdout);
|
if let Some(len) = response.content_length() {
|
||||||
let lines: Vec<&str> = raw.rsplitn(2, '\n').collect();
|
if len > MAX_RESPONSE_BODY {
|
||||||
|
return Err(format!(
|
||||||
let status = lines
|
"response body too large: {} bytes (limit {} bytes)",
|
||||||
.first()
|
len, MAX_RESPONSE_BODY
|
||||||
.and_then(|s| s.parse::<i32>().ok())
|
));
|
||||||
.unwrap_or(0);
|
}
|
||||||
let response_body = lines.get(1).unwrap_or(&"").as_bytes().to_vec();
|
}
|
||||||
|
|
||||||
Ok((status, std::collections::HashMap::new(), response_body))
|
let status = i32::from(response.status().as_u16());
|
||||||
|
let resp_headers = response
|
||||||
|
.headers()
|
||||||
|
.iter()
|
||||||
|
.map(|(name, value)| {
|
||||||
|
(
|
||||||
|
name.to_string(),
|
||||||
|
value.to_str().unwrap_or_default().to_string(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let resp_body = response.bytes().await.map_err(|e| e.to_string())?;
|
||||||
|
if resp_body.len() as u64 > MAX_RESPONSE_BODY {
|
||||||
|
return Err(format!(
|
||||||
|
"response body too large: {} bytes (limit {} bytes)",
|
||||||
|
resp_body.len(),
|
||||||
|
MAX_RESPONSE_BODY
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((status, resp_headers, resp_body.to_vec()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,31 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Mutex;
|
use std::sync::Arc;
|
||||||
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
use tokio::sync::{mpsc, Mutex, RwLock};
|
||||||
|
|
||||||
|
/// Per-process managed state, decoupled from the Child handle.
|
||||||
struct ManagedProcess {
|
struct ManagedProcess {
|
||||||
cmd: String,
|
cmd: String,
|
||||||
|
status: Arc<Mutex<String>>,
|
||||||
|
stdin_tx: Option<mpsc::Sender<Vec<u8>>>,
|
||||||
|
stdout_rx: Arc<Mutex<mpsc::Receiver<Vec<u8>>>>,
|
||||||
|
/// Notified when the process exits. Clone the receiver to wait.
|
||||||
|
exit_notify: Arc<tokio::sync::Notify>,
|
||||||
|
/// Handle to abort background tasks on kill.
|
||||||
|
_tasks: Vec<tokio::task::JoinHandle<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Process syscall: spawn and manage subprocesses.
|
/// Process syscall: spawn and manage subprocesses.
|
||||||
/// Uses the real OS PID as the identifier.
|
/// Uses the real OS PID as the identifier.
|
||||||
pub struct ProcessSyscall {
|
pub struct ProcessSyscall {
|
||||||
procs: Mutex<HashMap<u64, ManagedProcess>>,
|
procs: RwLock<HashMap<u64, Arc<ManagedProcess>>>,
|
||||||
children: tokio::sync::Mutex<HashMap<u64, tokio::process::Child>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProcessSyscall {
|
impl ProcessSyscall {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
procs: Mutex::new(HashMap::new()),
|
procs: RwLock::new(HashMap::new()),
|
||||||
children: tokio::sync::Mutex::new(HashMap::new()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,86 +47,244 @@ impl ProcessSyscall {
|
|||||||
command
|
command
|
||||||
.stdin(std::process::Stdio::piped())
|
.stdin(std::process::Stdio::piped())
|
||||||
.stdout(std::process::Stdio::piped())
|
.stdout(std::process::Stdio::piped())
|
||||||
.stderr(std::process::Stdio::piped());
|
.stderr(std::process::Stdio::piped())
|
||||||
|
.kill_on_drop(true);
|
||||||
|
|
||||||
let child = command.spawn().map_err(|e| e.to_string())?;
|
let mut child = command.spawn().map_err(|e| e.to_string())?;
|
||||||
let pid = child
|
let pid = child
|
||||||
.id()
|
.id()
|
||||||
.ok_or_else(|| "spawned process has no OS pid".to_string())? as u64;
|
.ok_or_else(|| "spawned process has no OS pid".to_string())? as u64;
|
||||||
|
|
||||||
self.procs.lock().unwrap().insert(
|
// Take ownership of stdio handles
|
||||||
pid,
|
let child_stdin = child.stdin.take();
|
||||||
ManagedProcess {
|
let child_stdout = child.stdout.take();
|
||||||
cmd: cmd.to_string(),
|
let child_stderr = child.stderr.take();
|
||||||
},
|
|
||||||
);
|
|
||||||
self.children.lock().await.insert(pid, child);
|
|
||||||
|
|
||||||
|
let status = Arc::new(Mutex::new("running".to_string()));
|
||||||
|
let exit_notify = Arc::new(tokio::sync::Notify::new());
|
||||||
|
|
||||||
|
// Stdin writer task
|
||||||
|
let (stdin_tx, mut stdin_rx) = mpsc::channel::<Vec<u8>>(64);
|
||||||
|
let stdin_task = tokio::spawn(async move {
|
||||||
|
if let Some(mut stdin) = child_stdin {
|
||||||
|
while let Some(data) = stdin_rx.recv().await {
|
||||||
|
if stdin.write_all(&data).await.is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if stdin.flush().await.is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Stdout reader task
|
||||||
|
let (stdout_tx, stdout_rx) = mpsc::channel::<Vec<u8>>(64);
|
||||||
|
let stdout_task = tokio::spawn(async move {
|
||||||
|
if let Some(mut stdout) = child_stdout {
|
||||||
|
loop {
|
||||||
|
let mut buf = vec![0u8; 4096];
|
||||||
|
match stdout.read(&mut buf).await {
|
||||||
|
Ok(0) => break,
|
||||||
|
Ok(n) => {
|
||||||
|
buf.truncate(n);
|
||||||
|
if stdout_tx.send(buf).await.is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Stderr drain task (prevent pipe fill-up)
|
||||||
|
let stderr_task = tokio::spawn(async move {
|
||||||
|
if let Some(mut stderr) = child_stderr {
|
||||||
|
let mut buf = vec![0u8; 4096];
|
||||||
|
loop {
|
||||||
|
match stderr.read(&mut buf).await {
|
||||||
|
Ok(0) | Err(_) => break,
|
||||||
|
Ok(_) => {} // discard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reaper task: waits for exit and updates status
|
||||||
|
let status_clone = status.clone();
|
||||||
|
let exit_notify_clone = exit_notify.clone();
|
||||||
|
let reaper_task = tokio::spawn(async move {
|
||||||
|
let exit_status = child.wait().await;
|
||||||
|
let code = exit_status.map(|s| s.code().unwrap_or(-1)).unwrap_or(-1);
|
||||||
|
*status_clone.lock().await = format!("exited({})", code);
|
||||||
|
exit_notify_clone.notify_waiters();
|
||||||
|
});
|
||||||
|
|
||||||
|
let managed = Arc::new(ManagedProcess {
|
||||||
|
cmd: cmd.to_string(),
|
||||||
|
status,
|
||||||
|
stdin_tx: Some(stdin_tx),
|
||||||
|
stdout_rx: Arc::new(Mutex::new(stdout_rx)),
|
||||||
|
exit_notify,
|
||||||
|
_tasks: vec![stdin_task, stdout_task, stderr_task, reaper_task],
|
||||||
|
});
|
||||||
|
|
||||||
|
self.procs.write().await.insert(pid, managed);
|
||||||
tracing::info!("process spawned: pid={} cmd={}", pid, cmd);
|
tracing::info!("process spawned: pid={} cmd={}", pid, cmd);
|
||||||
Ok(pid)
|
Ok(pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn kill(&self, pid: u64) -> Result<(), String> {
|
pub async fn kill(&self, pid: u64) -> Result<(), String> {
|
||||||
let mut child = {
|
let procs = self.procs.read().await;
|
||||||
let mut children = self.children.lock().await;
|
let proc = procs
|
||||||
children.remove(&pid).ok_or_else(|| format!("pid {} not found", pid))?
|
.get(&pid)
|
||||||
};
|
.ok_or_else(|| format!("pid {} not found", pid))?
|
||||||
|
.clone();
|
||||||
|
drop(procs);
|
||||||
|
|
||||||
child.kill().await.map_err(|e| e.to_string())?;
|
// Abort all background tasks (stdin writer, stdout reader, stderr drain, reaper)
|
||||||
self.procs.lock().unwrap().remove(&pid);
|
for task in proc._tasks.iter() {
|
||||||
|
task.abort();
|
||||||
|
}
|
||||||
|
*proc.status.lock().await = "killed".to_string();
|
||||||
|
proc.exit_notify.notify_waiters();
|
||||||
|
|
||||||
|
// Also send OS kill
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
let os_pid = pid as i32;
|
||||||
|
unsafe { libc::kill(os_pid as libc::pid_t, 9) };
|
||||||
|
}
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
{
|
||||||
|
// On Windows, aborting the reaper task triggers kill_on_drop on the Child
|
||||||
|
}
|
||||||
|
|
||||||
|
self.procs.write().await.remove(&pid);
|
||||||
tracing::info!("process killed: pid={}", pid);
|
tracing::info!("process killed: pid={}", pid);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn wait(&self, pid: u64) -> Result<i32, String> {
|
pub async fn wait(&self, pid: u64) -> Result<i32, String> {
|
||||||
let mut child = {
|
let procs = self.procs.read().await;
|
||||||
let mut children = self.children.lock().await;
|
let proc = procs
|
||||||
children.remove(&pid).ok_or_else(|| format!("pid {} not found", pid))?
|
.get(&pid)
|
||||||
};
|
.ok_or_else(|| format!("pid {} not found", pid))?
|
||||||
self.procs.lock().unwrap().remove(&pid);
|
.clone();
|
||||||
let status = child.wait().await.map_err(|e| e.to_string())?;
|
drop(procs);
|
||||||
Ok(status.code().unwrap_or(-1))
|
|
||||||
|
// Check if already exited before waiting
|
||||||
|
{
|
||||||
|
let status = proc.status.lock().await;
|
||||||
|
if status.starts_with("exited") || *status == "killed" {
|
||||||
|
let s = status.clone();
|
||||||
|
drop(status);
|
||||||
|
self.procs.write().await.remove(&pid);
|
||||||
|
return parse_exit_code(&s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for exit notification
|
||||||
|
proc.exit_notify.notified().await;
|
||||||
|
let status = proc.status.lock().await.clone();
|
||||||
|
|
||||||
|
// Clean up from map
|
||||||
|
self.procs.write().await.remove(&pid);
|
||||||
|
parse_exit_code(&status)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn write_stdin(&self, pid: u64, data: &[u8]) -> Result<(), String> {
|
pub async fn write_stdin(&self, pid: u64, data: &[u8]) -> Result<(), String> {
|
||||||
use tokio::io::AsyncWriteExt;
|
let procs = self.procs.read().await;
|
||||||
let mut children = self.children.lock().await;
|
let proc = procs
|
||||||
if let Some(child) = children.get_mut(&pid) {
|
.get(&pid)
|
||||||
if let Some(stdin) = child.stdin.as_mut() {
|
.ok_or_else(|| format!("pid {} not found", pid))?
|
||||||
stdin.write_all(data).await.map_err(|e| e.to_string())?;
|
.clone();
|
||||||
stdin.flush().await.map_err(|e| e.to_string())?;
|
drop(procs);
|
||||||
Ok(())
|
|
||||||
|
if let Some(ref tx) = proc.stdin_tx {
|
||||||
|
tx.send(data.to_vec())
|
||||||
|
.await
|
||||||
|
.map_err(|_| "stdin channel closed".to_string())
|
||||||
} else {
|
} else {
|
||||||
Err("stdin not available".to_string())
|
Err("stdin not available".to_string())
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Err(format!("pid {} not found", pid))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read_stdout(&self, pid: u64, buf: &mut [u8]) -> Result<usize, String> {
|
pub async fn read_stdout(&self, pid: u64, buf: &mut [u8]) -> Result<usize, String> {
|
||||||
use tokio::io::AsyncReadExt;
|
let procs = self.procs.read().await;
|
||||||
let mut children = self.children.lock().await;
|
let proc = procs
|
||||||
if let Some(child) = children.get_mut(&pid) {
|
.get(&pid)
|
||||||
if let Some(stdout) = child.stdout.as_mut() {
|
.ok_or_else(|| format!("pid {} not found", pid))?
|
||||||
stdout.read(buf).await.map_err(|e| e.to_string())
|
.clone();
|
||||||
} else {
|
drop(procs);
|
||||||
Err("stdout not available".to_string())
|
|
||||||
|
let mut rx = proc.stdout_rx.lock().await;
|
||||||
|
match rx.recv().await {
|
||||||
|
Some(data) => {
|
||||||
|
let n = std::cmp::min(data.len(), buf.len());
|
||||||
|
buf[..n].copy_from_slice(&data[..n]);
|
||||||
|
Ok(n)
|
||||||
}
|
}
|
||||||
} else {
|
None => Ok(0), // EOF
|
||||||
Err(format!("pid {} not found", pid))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn signal(&self, pid: u64, _signal: i32) -> Result<(), String> {
|
pub async fn signal(&self, pid: u64, signal: i32) -> Result<(), String> {
|
||||||
self.kill(pid).await
|
if signal == 9 {
|
||||||
|
return self.kill(pid).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list(&self) -> Vec<(u64, String, String)> {
|
let procs = self.procs.read().await;
|
||||||
let procs = self.procs.lock().unwrap();
|
if !procs.contains_key(&pid) {
|
||||||
procs
|
return Err(format!("pid {} not found", pid));
|
||||||
.iter()
|
}
|
||||||
.map(|(pid, mp)| (*pid, mp.cmd.clone(), "running".to_string()))
|
drop(procs);
|
||||||
.collect()
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
let os_pid = i32::try_from(pid)
|
||||||
|
.map_err(|_| format!("pid {} out of range for platform signal API", pid))?;
|
||||||
|
let result = unsafe { libc::kill(os_pid as libc::pid_t, signal as libc::c_int) };
|
||||||
|
if result == 0 {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(format!(
|
||||||
|
"signal {} to pid {} failed: {}",
|
||||||
|
signal,
|
||||||
|
pid,
|
||||||
|
std::io::Error::last_os_error()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
{
|
||||||
|
Err(format!(
|
||||||
|
"process.signal only supports signal 9 on this platform, got {}",
|
||||||
|
signal
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list(&self) -> Vec<(u64, String, String)> {
|
||||||
|
let procs = self.procs.read().await;
|
||||||
|
let mut result = Vec::with_capacity(procs.len());
|
||||||
|
for (pid, mp) in procs.iter() {
|
||||||
|
let status = mp.status.lock().await.clone();
|
||||||
|
result.push((*pid, mp.cmd.clone(), status));
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_exit_code(status: &str) -> Result<i32, String> {
|
||||||
|
if let Some(code_str) = status
|
||||||
|
.strip_prefix("exited(")
|
||||||
|
.and_then(|s| s.strip_suffix(')'))
|
||||||
|
{
|
||||||
|
code_str.parse::<i32>().map_err(|e| e.to_string())
|
||||||
|
} else {
|
||||||
|
Ok(-1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,12 +52,10 @@ impl TimerSyscall {
|
|||||||
timers.lock().unwrap().remove(&tid);
|
timers.lock().unwrap().remove(&tid);
|
||||||
});
|
});
|
||||||
|
|
||||||
self.timers.lock().unwrap().insert(
|
self.timers
|
||||||
timer_id.clone(),
|
.lock()
|
||||||
TimerEntry {
|
.unwrap()
|
||||||
handle,
|
.insert(timer_id.clone(), TimerEntry { handle });
|
||||||
},
|
|
||||||
);
|
|
||||||
timer_id
|
timer_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ impl CheckRunner {
|
|||||||
.into_inner();
|
.into_inner();
|
||||||
let names: Vec<&str> = list.entries.iter().map(|e| e.name.as_str()).collect();
|
let names: Vec<&str> = list.entries.iter().map(|e| e.name.as_str()).collect();
|
||||||
ensure!(
|
ensure!(
|
||||||
list.ok && names.iter().any(|name| *name == "sample.txt"),
|
list.ok && names.contains(&"sample.txt"),
|
||||||
"list mismatch ok={} names={:?} error={:?}",
|
"list mismatch ok={} names={:?} error={:?}",
|
||||||
list.ok,
|
list.ok,
|
||||||
names,
|
names,
|
||||||
@@ -341,12 +341,13 @@ impl CheckRunner {
|
|||||||
|
|
||||||
async fn process_roundtrip(&mut self) -> Result<String> {
|
async fn process_roundtrip(&mut self) -> Result<String> {
|
||||||
let mut process = ProcessClient::new(self.channel.clone());
|
let mut process = ProcessClient::new(self.channel.clone());
|
||||||
|
let (cmd, args) = process_command();
|
||||||
let resp = process
|
let resp = process
|
||||||
.spawn(request_with_plugin(
|
.spawn(request_with_plugin(
|
||||||
PLUGIN_ID,
|
PLUGIN_ID,
|
||||||
SpawnRequest {
|
SpawnRequest {
|
||||||
cmd: "cmd".to_string(),
|
cmd,
|
||||||
args: vec!["/C".to_string(), format!("echo {PROCESS_OK}")],
|
args,
|
||||||
env: HashMap::new(),
|
env: HashMap::new(),
|
||||||
cwd: executable_dir()
|
cwd: executable_dir()
|
||||||
.map(|path| path_string(&path))
|
.map(|path| path_string(&path))
|
||||||
@@ -504,6 +505,20 @@ fn executable_data_dir() -> PathBuf {
|
|||||||
.join("data")
|
.join("data")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn process_command() -> (String, Vec<String>) {
|
||||||
|
if cfg!(windows) {
|
||||||
|
(
|
||||||
|
"cmd".to_string(),
|
||||||
|
vec!["/C".to_string(), format!("echo {PROCESS_OK}")],
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
"sh".to_string(),
|
||||||
|
vec!["-c".to_string(), format!("printf %s {PROCESS_OK}")],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn path_string(path: &std::path::Path) -> String {
|
fn path_string(path: &std::path::Path) -> String {
|
||||||
path.to_string_lossy().to_string()
|
path.to_string_lossy().to_string()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ anyhow = { workspace = true }
|
|||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
tokio-stream = { workspace = true }
|
||||||
tonic = { workspace = true }
|
tonic = { workspace = true }
|
||||||
futures-core = { workspace = true }
|
futures-core = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
tracing-subscriber = { workspace = true }
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use tonic::{transport::Server, Request, Response, Status};
|
|||||||
const PLUGIN_ID: &str = "claudecode";
|
const PLUGIN_ID: &str = "claudecode";
|
||||||
const DEFAULT_PORT: u16 = 50053;
|
const DEFAULT_PORT: u16 = 50053;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Default, Deserialize)]
|
||||||
struct CallArgs {
|
struct CallArgs {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
prompt: String,
|
prompt: String,
|
||||||
@@ -32,26 +32,22 @@ fn find_claude_bin() -> Option<String> {
|
|||||||
return Some(bin);
|
return Some(bin);
|
||||||
}
|
}
|
||||||
|
|
||||||
let candidates = vec![
|
let appdata_claude = std::env::var("APPDATA")
|
||||||
"claude",
|
.ok()
|
||||||
"claude.cmd",
|
.map(|p| format!("{}\\npm\\claude.cmd", p));
|
||||||
&std::env::var("APPDATA")
|
let mut candidates = vec!["claude".to_string(), "claude.cmd".to_string()];
|
||||||
.map(|p| format!("{}\\npm\\claude.cmd", p))
|
if let Some(path) = appdata_claude {
|
||||||
.unwrap_or_default(),
|
candidates.push(path);
|
||||||
];
|
}
|
||||||
|
|
||||||
for candidate in candidates {
|
candidates.into_iter().find(|candidate| {
|
||||||
if std::process::Command::new(candidate)
|
std::process::Command::new(candidate)
|
||||||
.arg("--version")
|
.arg("--version")
|
||||||
.stdout(Stdio::null())
|
.stdout(Stdio::null())
|
||||||
.stderr(Stdio::null())
|
.stderr(Stdio::null())
|
||||||
.status()
|
.status()
|
||||||
.is_ok()
|
.is_ok()
|
||||||
{
|
})
|
||||||
return Some(candidate.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn call_claude(
|
async fn call_claude(
|
||||||
@@ -97,7 +93,10 @@ async fn call_claude(
|
|||||||
drop(stdin);
|
drop(stdin);
|
||||||
}
|
}
|
||||||
|
|
||||||
let output = tokio::time::timeout(std::time::Duration::from_secs(300), child.wait_with_output())
|
let output = tokio::time::timeout(
|
||||||
|
std::time::Duration::from_secs(300),
|
||||||
|
child.wait_with_output(),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.context("claude timeout (300s)")??;
|
.context("claude timeout (300s)")??;
|
||||||
|
|
||||||
@@ -148,8 +147,10 @@ impl ClaudeCodePlugin {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
"review" => {
|
"review" => {
|
||||||
let review_prompt =
|
let review_prompt = format!(
|
||||||
format!("Review the following code or changes. Be concise and actionable:\n\n{}", prompt);
|
"Review the following code or changes. Be concise and actionable:\n\n{}",
|
||||||
|
prompt
|
||||||
|
);
|
||||||
call_claude(
|
call_claude(
|
||||||
&self.claude_bin,
|
&self.claude_bin,
|
||||||
&review_prompt,
|
&review_prompt,
|
||||||
@@ -184,7 +185,10 @@ impl ClaudeCodePlugin {
|
|||||||
|
|
||||||
#[tonic::async_trait]
|
#[tonic::async_trait]
|
||||||
impl PluginBridge for ClaudeCodePlugin {
|
impl PluginBridge for ClaudeCodePlugin {
|
||||||
async fn register(&self, _req: Request<PluginInfo>) -> Result<Response<RegisterResponse>, Status> {
|
async fn register(
|
||||||
|
&self,
|
||||||
|
_req: Request<PluginInfo>,
|
||||||
|
) -> Result<Response<RegisterResponse>, Status> {
|
||||||
Err(Status::unimplemented("not a bridge"))
|
Err(Status::unimplemented("not a bridge"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,13 +199,19 @@ impl PluginBridge for ClaudeCodePlugin {
|
|||||||
Ok(Response::new(response))
|
Ok(Response::new(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
type StreamCallStream = futures_core::stream::Empty<Result<CallResponse, Status>>;
|
type StreamCallStream = tokio_stream::Empty<Result<CallResponse, Status>>;
|
||||||
|
|
||||||
async fn stream_call(&self, _req: Request<CallRequest>) -> Result<Response<Self::StreamCallStream>, Status> {
|
async fn stream_call(
|
||||||
|
&self,
|
||||||
|
_req: Request<CallRequest>,
|
||||||
|
) -> Result<Response<Self::StreamCallStream>, Status> {
|
||||||
Err(Status::unimplemented("stream_call not supported"))
|
Err(Status::unimplemented("stream_call not supported"))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn heartbeat(&self, _req: Request<HeartbeatRequest>) -> Result<Response<HeartbeatResponse>, Status> {
|
async fn heartbeat(
|
||||||
|
&self,
|
||||||
|
_req: Request<HeartbeatRequest>,
|
||||||
|
) -> Result<Response<HeartbeatResponse>, Status> {
|
||||||
Ok(Response::new(HeartbeatResponse { ok: true }))
|
Ok(Response::new(HeartbeatResponse { ok: true }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -210,7 +220,8 @@ impl PluginBridge for ClaudeCodePlugin {
|
|||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
let claude_bin = find_claude_bin().context("Claude Code CLI not found. Install: npm install -g @anthropic-ai/claude-code")?;
|
let claude_bin = find_claude_bin()
|
||||||
|
.context("Claude Code CLI not found. Install: npm install -g @anthropic-ai/claude-code")?;
|
||||||
tracing::info!("Found Claude Code CLI: {}", claude_bin);
|
tracing::info!("Found Claude Code CLI: {}", claude_bin);
|
||||||
|
|
||||||
let endpoint = agentsd_plugin_sdk::plugin_endpoint_from_env(DEFAULT_PORT);
|
let endpoint = agentsd_plugin_sdk::plugin_endpoint_from_env(DEFAULT_PORT);
|
||||||
@@ -224,21 +235,42 @@ async fn main() -> Result<()> {
|
|||||||
"Claude Code",
|
"Claude Code",
|
||||||
env!("CARGO_PKG_VERSION"),
|
env!("CARGO_PKG_VERSION"),
|
||||||
"standard",
|
"standard",
|
||||||
&["process.spawn", "display.text", "fs.read", "fs.write", "network.request"],
|
&[
|
||||||
|
"process.spawn",
|
||||||
|
"display.text",
|
||||||
|
"fs.read",
|
||||||
|
"fs.write",
|
||||||
|
"network.request",
|
||||||
|
],
|
||||||
&["hermes"],
|
&["hermes"],
|
||||||
vec![
|
vec![
|
||||||
export("chat", "Send prompt to Claude Code, get text response (no tools)"),
|
export(
|
||||||
export("code", "Generate or edit code with Claude Code (Bash/Read/Edit/Write)"),
|
"chat",
|
||||||
|
"Send prompt to Claude Code, get text response (no tools)",
|
||||||
|
),
|
||||||
|
export(
|
||||||
|
"code",
|
||||||
|
"Generate or edit code with Claude Code (Bash/Read/Edit/Write)",
|
||||||
|
),
|
||||||
export("review", "Review code changes (Read/Glob/Grep only)"),
|
export("review", "Review code changes (Read/Glob/Grep only)"),
|
||||||
export("ask", "Ask about the codebase (Read/Glob/Grep, max 3 turns)"),
|
export(
|
||||||
export("run", "Full Claude Code access (all tools, unlimited turns)"),
|
"ask",
|
||||||
|
"Ask about the codebase (Read/Glob/Grep, max 3 turns)",
|
||||||
|
),
|
||||||
|
export(
|
||||||
|
"run",
|
||||||
|
"Full Claude Code access (all tools, unlimited turns)",
|
||||||
|
),
|
||||||
],
|
],
|
||||||
&endpoint,
|
&endpoint,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
tracing::info!("Registered with agentsd: session={}", register_resp.session_id);
|
tracing::info!(
|
||||||
|
"Registered with agentsd: session={}",
|
||||||
|
register_resp.session_id
|
||||||
|
);
|
||||||
|
|
||||||
let plugin = ClaudeCodePlugin::new(claude_bin);
|
let plugin = ClaudeCodePlugin::new(claude_bin);
|
||||||
let addr = listen_addr.parse()?;
|
let addr = listen_addr.parse()?;
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ async fn main() -> Result<()> {
|
|||||||
.await?
|
.await?
|
||||||
.into_inner();
|
.into_inner();
|
||||||
ensure!(register.ok, "register failed: {}", register.error);
|
ensure!(register.ok, "register failed: {}", register.error);
|
||||||
println!("Register: ok={} session={}", register.ok, register.session_id);
|
println!(
|
||||||
|
"Register: ok={} session={}",
|
||||||
|
register.ok, register.session_id
|
||||||
|
);
|
||||||
|
|
||||||
let mut display = DisplayClient::new(channel.clone());
|
let mut display = DisplayClient::new(channel.clone());
|
||||||
let resp = display
|
let resp = display
|
||||||
@@ -84,7 +87,11 @@ async fn main() -> Result<()> {
|
|||||||
ensure!(resp.ok, "FS.List failed: {}", resp.error);
|
ensure!(resp.ok, "FS.List failed: {}", resp.error);
|
||||||
println!("FS.List: ok={} entries={}", resp.ok, resp.entries.len());
|
println!("FS.List: ok={} entries={}", resp.ok, resp.entries.len());
|
||||||
for entry in resp.entries.iter().take(5) {
|
for entry in resp.entries.iter().take(5) {
|
||||||
println!(" {} {}", entry.name, if entry.is_dir { "[dir]" } else { "" });
|
println!(
|
||||||
|
" {} {}",
|
||||||
|
entry.name,
|
||||||
|
if entry.is_dir { "[dir]" } else { "" }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\nAll syscalls verified OK!");
|
println!("\nAll syscalls verified OK!");
|
||||||
|
|||||||
@@ -44,7 +44,9 @@ impl PluginBridge for HermesPlugin {
|
|||||||
&self,
|
&self,
|
||||||
_req: Request<PluginInfo>,
|
_req: Request<PluginInfo>,
|
||||||
) -> std::result::Result<Response<RegisterResponse>, Status> {
|
) -> std::result::Result<Response<RegisterResponse>, Status> {
|
||||||
Err(Status::unimplemented("hermes plugin is not an agentsd bridge"))
|
Err(Status::unimplemented(
|
||||||
|
"hermes plugin is not an agentsd bridge",
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn call(
|
async fn call(
|
||||||
@@ -62,13 +64,16 @@ impl PluginBridge for HermesPlugin {
|
|||||||
Ok(Response::new(response))
|
Ok(Response::new(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
type StreamCallStream = tokio_stream::wrappers::ReceiverStream<std::result::Result<CallResponse, Status>>;
|
type StreamCallStream =
|
||||||
|
tokio_stream::wrappers::ReceiverStream<std::result::Result<CallResponse, Status>>;
|
||||||
|
|
||||||
async fn stream_call(
|
async fn stream_call(
|
||||||
&self,
|
&self,
|
||||||
_req: Request<CallRequest>,
|
_req: Request<CallRequest>,
|
||||||
) -> std::result::Result<Response<Self::StreamCallStream>, Status> {
|
) -> std::result::Result<Response<Self::StreamCallStream>, Status> {
|
||||||
Err(Status::unimplemented("stream_call is not implemented for hermes"))
|
Err(Status::unimplemented(
|
||||||
|
"stream_call is not implemented for hermes",
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn heartbeat(
|
async fn heartbeat(
|
||||||
@@ -110,7 +115,7 @@ async fn main() -> Result<()> {
|
|||||||
println!("Hermes PluginBridge server listening on {}", listen_addr);
|
println!("Hermes PluginBridge server listening on {}", listen_addr);
|
||||||
|
|
||||||
Server::builder()
|
Server::builder()
|
||||||
.add_service(PluginBridgeServer::new(HermesPlugin::default()))
|
.add_service(PluginBridgeServer::new(HermesPlugin))
|
||||||
.serve_with_incoming(incoming)
|
.serve_with_incoming(incoming)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ pub fn plugin_info(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn plugin_info_with_endpoint(
|
pub fn plugin_info_with_endpoint(
|
||||||
id: &str,
|
id: &str,
|
||||||
name: &str,
|
name: &str,
|
||||||
@@ -95,7 +96,11 @@ pub fn encode_json<T: Serialize>(value: &T) -> Result<Vec<u8>> {
|
|||||||
serde_json::to_vec(value).context("encode JSON")
|
serde_json::to_vec(value).context("encode JSON")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn call_response_json<T: Serialize>(value: &T, ok: bool, error: impl Into<String>) -> CallResponse {
|
pub fn call_response_json<T: Serialize>(
|
||||||
|
value: &T,
|
||||||
|
ok: bool,
|
||||||
|
error: impl Into<String>,
|
||||||
|
) -> CallResponse {
|
||||||
match encode_json(value) {
|
match encode_json(value) {
|
||||||
Ok(result) => CallResponse {
|
Ok(result) => CallResponse {
|
||||||
result,
|
result,
|
||||||
|
|||||||
Reference in New Issue
Block a user