commit 1bf1f08f9a9a5c117e1c5b6f1a9e4fd4f3800f2d Author: 未知时光 <732857315@qq.com> Date: Tue Jun 9 15:09:56 2026 +0800 Initial Agentsd project commit Co-Authored-By: Claude Opus 4.8 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..502a5a8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# Rust build output +/target/ + +# Runtime data (created next to the executable) +/data/ +**/data/ +*.db +*.sqlite +*.sqlite3 +plugins.json + +# Python cache / virtual environments +__pycache__/ +*.py[cod] +*$py.class +.venv/ +venv/ +env/ + +# Logs and local env files +*.log +.env +.env.* + +# Editor / OS files +.vscode/ +.idea/ +.DS_Store +Thumbs.db diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..4cf4e1a --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1763 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "agentsd" +version = "0.1.0" +dependencies = [ + "agentsd-proto", + "anyhow", + "notify", + "prost", + "rusqlite", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tonic", + "tracing", + "tracing-subscriber", + "uuid", +] + +[[package]] +name = "agentsd-proto" +version = "0.1.0" +dependencies = [ + "prost", + "tonic", + "tonic-build", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower 0.5.3", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[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 = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "h2" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.14.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +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 = "hyper" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[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-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2 0.6.4", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[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", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "inotify" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[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 = "js-sys" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2025f20d7a4fa7785846e7b63d10a76d3f1cee98ee5cb79ea59703f95e42162" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "kqueue" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "273c0752728918e0ac4976f2b275b6fefb9ecd400585dec929419f3844cd87b5" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" +dependencies = [ + "bitflags 2.13.0", + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[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.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" + +[[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.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "memchr" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mio" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +dependencies = [ + "libc", + "log", + "wasi", + "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 = "notify" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c533b4c39709f9ba5005d8002048266593c1cfaf3c5f0739d5b8ab0c6c504009" +dependencies = [ + "bitflags 2.13.0", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.52.0", +] + +[[package]] +name = "notify-types" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585d3cb5e12e01aed9e8a1f70d5c6b5e86fe2a6e48fc8cd0b3e0b8df6f6eb174" +dependencies = [ + "instant", +] + +[[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 = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[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", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap 2.14.0", +] + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" +dependencies = [ + "heck", + "itertools", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.0", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "rusqlite" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" +dependencies = [ + "bitflags 2.13.0", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.4", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tonic" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost", + "socket2 0.5.10", + "tokio", + "tokio-stream", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "prost-types", + "quote", + "syn", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +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-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", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "uuid" +version = "1.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "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_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a254a4b10c19a76f09a27640e7ffbf9bc30bf67e16a3bf28aaefa4920fe81563" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24a40fc75b0ec6f3746ceb10d36f53a93dcd68a93b11b6445983945d79eba0dc" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "908f34bd9b9ce3d4caf07b72dfab63d61504d156856c6bd3cd87fa350cf3985b" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7acbf7616c27b194bbb550bf77ed0c2c3e5b7fd1260a93082b95fb7f47959b92" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.14.0", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.13.0", + "hashbrown 0.15.5", + "indexmap 2.14.0", + "semver", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.14.0", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.13.0", + "indexmap 2.14.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.14.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "zerocopy" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9d97a0e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[workspace] +resolver = "2" +members = ["core/agentsd", "core/proto"] + +[workspace.dependencies] +tokio = { version = "1", features = ["full"] } +tokio-stream = "0.1" +tonic = "0.12" +prost = "0.13" +tonic-build = "0.12" +rusqlite = { version = "0.32", features = ["bundled"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +anyhow = "1" +uuid = { version = "1", features = ["v4"] } +notify = "7" diff --git a/README.md b/README.md new file mode 100644 index 0000000..2c60cfb --- /dev/null +++ b/README.md @@ -0,0 +1,84 @@ +# Agentsd + +AI Agent 的 init 系统 + 内核。对标 Linux 内核 + systemd。 + +> 状态:设计草案 + +## 对标 + +| Linux | Agentsd | 说明 | +|---|---|---| +| 内核 syscall 表 | 9 个 syscall 接口 | 进程与硬件之间的抽象层 | +| 设备驱动 (.ko) | 系统插件 | 实现 syscall(如 alsa = audio 驱动) | +| systemd unit | 插件(basic / standard) | 声明依赖、由 init 管理生命周期 | +| systemd(PID 1) | agentsd 进程 | 启动排序、依赖解析、看门狗、日志 | +| D-Bus | gRPC over TCP(后续切 UDS/Named Pipe) | 进程间通信总线 | +| /dev | syscall 接口 | 插件访问能力的统一入口 | +| cgroup / seccomp | 权限白名单 | 访问控制 | + +## 架构 + +``` +标准插件 ← 对标 systemd 管理的应用服务(nginx、docker...) +基础插件 ← 对标 systemd 管理的基础服务(udevd、resolved...) +系统插件 ← 对标 Linux 内核模块 / 设备驱动(.ko) +─────────────────────────────────────── +agentsd ← 对标 Linux 内核 + systemd(PID 1) +``` + +## 9 个系统调用 + +| # | syscall | Linux 类比 | +|---|---|---| +| 1 | Display | write(stdout) / framebuffer | +| 2 | Audio | /dev/snd (ALSA) | +| 3 | FS | open / read / write / stat / inotify | +| 4 | Memory | mmap / shmem(面向结构化数据) | +| 5 | Network | socket / connect / send / recv | +| 6 | Process | fork / exec / wait / kill | +| 7 | Timer | timer_create / timerfd | +| 8 | HID | /dev/input + /dev/fb | +| 9 | Input | read(stdin) / evdev | + +## 三种插件 + +| 类型 | Linux 类比 | 通信 | 依赖 | +|---|---|---|---| +| 系统插件 | 内核模块 / 驱动 | FFI | 无 | +| 基础插件 | 基础 daemon | gRPC/TCP(后续 UDS/Named Pipe) | 只 syscall | +| 标准插件 | 应用服务 | gRPC/TCP(后续 UDS/Named Pipe) | syscall + 其他插件 | + +## agentsd 作为 PID 1 的职责 + +| 职责 | systemd 对标 | +|---|---| +| 启动排序 | unit ordering (After/Before) | +| 看门狗 | Restart=always | +| 依赖解析 | Requires / Wants | +| 日志收集 | journald | +| 资源控制 | cgroup + seccomp | +| 状态查询 | systemctl status | +| 热更新 | systemctl reload | + +## 文档 + +| 文档 | 内容 | +|---|---| +| [01-内核系统调用.md](./docs/01-内核系统调用.md) | 9 个 syscall 定义(= 内核 syscall 表) | +| [02-插件模型.md](./docs/02-插件模型.md) | 三层插件 + gRPC + 权限(= 驱动 + unit + D-Bus) | +| [03-实施路线.md](./docs/03-实施路线.md) | 阶段进度 | +| [04-项目结构.md](./docs/04-项目结构.md) | 代码目录 + 构建说明 | + +## 当前状态 + +阶段 1 已完成:内核骨架可运行,9 个 syscall gRPC service + PluginBridge 已注册,Python 测试插件验证全链路通过。默认运行数据写入程序所在目录的 `data/` 文件夹。`plugins/ai_test` 提供手动 AI 验证插件,默认不开启/不自动加载。Release 二进制约 6.1MB。 + +## 手动验证 + +```bash +cargo build --release +RUST_LOG=agentsd=info ./target/release/agentsd.exe +python plugins/ai_test/ai_test_plugin.py +``` + +`plugins/ai_test/plugin.yaml` 中设置了 `enabled: false` 和 `autoload: false`,未来实现插件自动加载时应默认跳过它。 diff --git a/core/agentsd/Cargo.toml b/core/agentsd/Cargo.toml new file mode 100644 index 0000000..637f61c --- /dev/null +++ b/core/agentsd/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "agentsd" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "agentsd" +path = "src/main.rs" + +[dependencies] +agentsd-proto = { path = "../proto" } +tokio = { workspace = true } +tokio-stream = { workspace = true } +tonic = { workspace = true } +prost = { workspace = true } +rusqlite = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } +anyhow = { workspace = true } +uuid = { workspace = true } +notify = { workspace = true } diff --git a/core/agentsd/src/callback.rs b/core/agentsd/src/callback.rs new file mode 100644 index 0000000..7a1e5fb --- /dev/null +++ b/core/agentsd/src/callback.rs @@ -0,0 +1,47 @@ +use std::collections::HashMap; +use std::sync::Mutex; +use tokio::sync::mpsc; + +/// A fired timer event delivered to the plugin that created it. +#[derive(Clone, Debug)] +pub struct TimerEvent { + pub timer_id: String, + pub callback_id: String, +} + +/// Manages per-plugin event channels for timer callbacks. +pub struct CallbackBus { + senders: Mutex>>, +} + +impl CallbackBus { + pub fn new() -> Self { + Self { + senders: Mutex::new(HashMap::new()), + } + } + + /// Register a plugin and get a receiver for its timer events. + pub fn subscribe(&self, plugin_id: &str) -> mpsc::Receiver { + let (tx, rx) = mpsc::channel(64); + self.senders + .lock() + .unwrap() + .insert(plugin_id.to_string(), tx); + rx + } + + /// Deliver a timer event to the target plugin. + pub fn fire(&self, plugin_id: &str, event: TimerEvent) { + let senders = self.senders.lock().unwrap(); + if let Some(tx) = senders.get(plugin_id) { + // Non-blocking send; drop if channel full + let _ = tx.try_send(event); + } + } + + /// Remove a plugin's channel (on unregister/disconnect). + pub fn remove(&self, plugin_id: &str) { + self.senders.lock().unwrap().remove(plugin_id); + } +} diff --git a/core/agentsd/src/main.rs b/core/agentsd/src/main.rs new file mode 100644 index 0000000..e9fdfa6 --- /dev/null +++ b/core/agentsd/src/main.rs @@ -0,0 +1,22 @@ +mod syscall; +mod plugin; +mod server; +pub mod paths; +pub mod callback; +pub mod permission; + +use anyhow::Result; +use tracing_subscriber::EnvFilter; + +#[tokio::main] +async fn main() -> Result<()> { + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env().add_directive("agentsd=info".parse()?)) + .init(); + + tracing::info!("agentsd starting"); + tracing::info!("data dir: {}", paths::data_dir().display()); + + let kernel = syscall::Kernel::new()?; + server::run(kernel).await +} diff --git a/core/agentsd/src/paths.rs b/core/agentsd/src/paths.rs new file mode 100644 index 0000000..c4e22d1 --- /dev/null +++ b/core/agentsd/src/paths.rs @@ -0,0 +1,12 @@ +use std::path::PathBuf; + +/// Returns the `data/` directory next to the running executable. +/// Creates it if it does not exist. +pub fn data_dir() -> PathBuf { + let exe = std::env::current_exe().unwrap_or_else(|_| PathBuf::from(".")); + let dir = exe.parent().unwrap_or_else(|| std::path::Path::new(".")).join("data"); + if !dir.exists() { + std::fs::create_dir_all(&dir).ok(); + } + dir +} diff --git a/core/agentsd/src/permission.rs b/core/agentsd/src/permission.rs new file mode 100644 index 0000000..b8d6915 --- /dev/null +++ b/core/agentsd/src/permission.rs @@ -0,0 +1,82 @@ +use crate::plugin::{PluginManager, PluginRecord, PluginStatus}; +use std::sync::Arc; +use tonic::{Request, Status}; + +/// Extract plugin_id from gRPC metadata. +pub fn plugin_id_from_metadata(req_metadata: &tonic::metadata::MetadataMap) -> Option { + req_metadata + .get("x-plugin-id") + .and_then(|v| v.to_str().ok()) + .filter(|s| !s.is_empty()) + .map(|s| s.to_string()) +} + +/// Load a plugin and require it to be currently running. +pub fn require_running_plugin( + plugins: &Arc, + plugin_id: &str, +) -> Result { + let record = plugins.get(plugin_id).ok_or_else(|| { + Status::permission_denied(format!("unknown plugin: {}", plugin_id)) + })?; + + if record.status != PluginStatus::Running { + return Err(Status::permission_denied(format!( + "plugin '{}' is not running", + plugin_id + ))); + } + + Ok(record) +} + +/// Verify that the caller has permission to call an exact syscall name, +/// e.g. `fs.list`, `memory.write`, `timer.once`. +/// +/// Supported grants: +/// - `*` grants everything +/// - `fs` grants all fs syscalls +/// - `fs.*` grants all fs syscalls +/// - `fs.list` grants only that method +pub fn check_permission( + plugins: &Arc, + req_metadata: &tonic::metadata::MetadataMap, + syscall_name: &str, +) -> Result { + let Some(plugin_id) = plugin_id_from_metadata(req_metadata) else { + // Direct/internal/manual testing call. + return Ok(String::new()); + }; + + let record = require_running_plugin(plugins, &plugin_id)?; + + // System plugins have unrestricted access. + if record.plugin_type == "system" { + return Ok(plugin_id); + } + + let service_name = syscall_name.split('.').next().unwrap_or(syscall_name); + let service_wildcard = format!("{}.*", service_name); + + let allowed = record.syscalls.iter().any(|grant| { + grant == "*" || grant == syscall_name || grant == service_name || grant == &service_wildcard + }); + + if !allowed { + return Err(Status::permission_denied(format!( + "plugin '{}' has no permission for syscall '{}'", + plugin_id, syscall_name + ))); + } + + Ok(plugin_id) +} + +/// Helper to extract plugin_id and check permission in one call within a service method. +pub fn guard( + plugins: &Arc, + req: &Request, + syscall_name: &str, +) -> Result { + check_permission(plugins, req.metadata(), syscall_name) +} diff --git a/core/agentsd/src/plugin.rs b/core/agentsd/src/plugin.rs new file mode 100644 index 0000000..c4e4b90 --- /dev/null +++ b/core/agentsd/src/plugin.rs @@ -0,0 +1,139 @@ +use std::collections::HashMap; +use std::sync::Mutex; + +/// Plugin registry: tracks loaded plugins and their exports. +/// Persists state to `/data/plugins.json`. +pub struct PluginManager { + plugins: Mutex>, +} + +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct PluginRecord { + pub id: String, + pub name: String, + pub version: String, + pub plugin_type: String, // "system" | "basic" | "standard" + pub syscalls: Vec, + pub depends: Vec, + pub exports: Vec<(String, String)>, // (name, description) + pub status: PluginStatus, +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub enum PluginStatus { + Registered, + Running, + Stopped, + Failed(String), +} + +impl PluginManager { + pub fn new() -> Self { + let mut mgr = Self { + plugins: Mutex::new(HashMap::new()), + }; + mgr.load_from_disk(); + mgr + } + + pub fn register(&self, record: PluginRecord) -> Result<(), String> { + let mut plugins = self.plugins.lock().unwrap(); + if plugins.contains_key(&record.id) { + tracing::info!( + "plugin re-registered: id={} type={} exports={:?}", + record.id, + record.plugin_type, + record.exports.iter().map(|(n, _)| n).collect::>() + ); + plugins.insert(record.id.clone(), record); + drop(plugins); + self.save_to_disk(); + return Ok(()); + } + tracing::info!( + "plugin registered: id={} type={} exports={:?}", + record.id, + record.plugin_type, + record.exports.iter().map(|(n, _)| n).collect::>() + ); + plugins.insert(record.id.clone(), record); + drop(plugins); + self.save_to_disk(); + Ok(()) + } + + pub fn get(&self, id: &str) -> Option { + self.plugins.lock().unwrap().get(id).cloned() + } + + pub fn list(&self) -> Vec { + self.plugins.lock().unwrap().values().cloned().collect() + } + + pub fn set_status(&self, id: &str, status: PluginStatus) { + let mut plugins = self.plugins.lock().unwrap(); + if let Some(record) = plugins.get_mut(id) { + record.status = status; + } + drop(plugins); + self.save_to_disk(); + } + + pub fn find_export(&self, target: &str, fn_name: &str) -> Option { + let plugins = self.plugins.lock().unwrap(); + if let Some(record) = plugins.get(target) { + if record.exports.iter().any(|(n, _)| n == fn_name) { + return Some(target.to_string()); + } + } + // Search all plugins for the export + for record in plugins.values() { + if record.exports.iter().any(|(n, _)| n == fn_name) { + return Some(record.id.clone()); + } + } + None + } + + pub fn unregister(&self, id: &str) -> Result<(), String> { + let mut plugins = self.plugins.lock().unwrap(); + plugins + .remove(id) + .map(|_| ()) + .ok_or_else(|| format!("plugin '{}' not found", id))?; + drop(plugins); + self.save_to_disk(); + Ok(()) + } + + // --- Persistence --- + + fn state_path() -> std::path::PathBuf { + crate::paths::data_dir().join("plugins.json") + } + + fn save_to_disk(&self) { + let plugins = self.plugins.lock().unwrap(); + let records: Vec<&PluginRecord> = plugins.values().collect(); + if let Ok(json) = serde_json::to_string_pretty(&records) { + let _ = std::fs::write(Self::state_path(), json); + } + } + + fn load_from_disk(&mut self) { + let path = Self::state_path(); + if let Ok(data) = std::fs::read_to_string(&path) { + if let Ok(records) = serde_json::from_str::>(&data) { + let mut plugins = self.plugins.lock().unwrap(); + for mut record in records { + // Mark previously-running plugins as stopped on reload + if record.status == PluginStatus::Running { + record.status = PluginStatus::Stopped; + } + plugins.insert(record.id.clone(), record); + } + tracing::info!("loaded {} plugins from {}", plugins.len(), path.display()); + } + } + } +} diff --git a/core/agentsd/src/server.rs b/core/agentsd/src/server.rs new file mode 100644 index 0000000..c9ca45a --- /dev/null +++ b/core/agentsd/src/server.rs @@ -0,0 +1,706 @@ +use crate::permission; +use crate::plugin::{PluginManager, PluginRecord, PluginStatus}; +use crate::syscall::Kernel; +use agentsd_proto::agentsd::*; +use agentsd_proto::agentsd::{ + audio_server::Audio as AudioService, display_server::Display as DisplayService, + fs_server::Fs as FsService, hid_server::Hid as HidService, + input_server::Input as InputService, memory_server::Memory as MemoryService, + network_server::Network as NetworkService, + plugin_bridge_server::PluginBridge as PluginBridgeService, + process_server::Process as ProcessService, timer_server::Timer as TimerService, +}; +use std::sync::Arc; +use tonic::{Request, Response, Status}; + +pub struct AgentsdServer { + kernel: Arc, + plugins: Arc, +} + +impl AgentsdServer { + fn new(kernel: Kernel) -> Self { + Self { + kernel: Arc::new(kernel), + plugins: Arc::new(PluginManager::new()), + } + } +} + +fn auth(plugins: &Arc, req: &Request, syscall: &str) -> Result { + permission::guard(plugins, req, syscall) +} + +// ===== Display ===== +pub struct DisplaySvc { + kernel: Arc, + plugins: Arc, +} + +#[tonic::async_trait] +impl DisplayService for DisplaySvc { + async fn text(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "display.text")?; + let r = req.into_inner(); + match self.kernel.display.text(&r.content) { + Ok(()) => Ok(Response::new(DisplayResponse { ok: true, error: String::new() })), + Err(e) => Ok(Response::new(DisplayResponse { ok: false, error: e })), + } + } + + async fn rich(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "display.rich")?; + let r = req.into_inner(); + match self.kernel.display.rich(&r.markup, &r.format) { + Ok(()) => Ok(Response::new(DisplayResponse { ok: true, error: String::new() })), + Err(e) => Ok(Response::new(DisplayResponse { ok: false, error: e })), + } + } + + async fn image(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "display.image")?; + let r = req.into_inner(); + match self.kernel.display.image(&r.data, &r.format) { + Ok(()) => Ok(Response::new(DisplayResponse { ok: true, error: String::new() })), + Err(e) => Ok(Response::new(DisplayResponse { ok: false, error: e })), + } + } + + async fn clear(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "display.clear")?; + match self.kernel.display.clear() { + Ok(()) => Ok(Response::new(DisplayResponse { ok: true, error: String::new() })), + Err(e) => Ok(Response::new(DisplayResponse { ok: false, error: e })), + } + } + + async fn notify(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "display.notify")?; + let r = req.into_inner(); + match self.kernel.display.notify(&r.message) { + Ok(()) => Ok(Response::new(DisplayResponse { ok: true, error: String::new() })), + Err(e) => Ok(Response::new(DisplayResponse { ok: false, error: e })), + } + } +} + +// ===== Audio ===== +pub struct AudioSvc { + kernel: Arc, + plugins: Arc, +} + +#[tonic::async_trait] +impl AudioService for AudioSvc { + async fn play(&self, req: Request>) -> Result, Status> { + auth(&self.plugins, &req, "audio.play")?; + use tokio_stream::StreamExt; + let mut stream = req.into_inner(); + let mut all_data = Vec::new(); + let mut format = String::new(); + while let Some(chunk) = stream.next().await { + let chunk = chunk?; + if format.is_empty() { + format = chunk.format; + } + all_data.extend(chunk.data); + } + match self.kernel.audio.play(&all_data, &format) { + Ok(()) => Ok(Response::new(AudioResponse { ok: true, error: String::new() })), + Err(e) => Ok(Response::new(AudioResponse { ok: false, error: e })), + } + } + + async fn stop(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "audio.stop")?; + match self.kernel.audio.stop() { + Ok(()) => Ok(Response::new(AudioResponse { ok: true, error: String::new() })), + Err(e) => Ok(Response::new(AudioResponse { ok: false, error: e })), + } + } + + async fn volume(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "audio.volume")?; + let r = req.into_inner(); + match self.kernel.audio.volume(r.level) { + Ok(()) => Ok(Response::new(AudioResponse { ok: true, error: String::new() })), + Err(e) => Ok(Response::new(AudioResponse { ok: false, error: e })), + } + } + + async fn record_start(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "audio.record_start")?; + let r = req.into_inner(); + match self.kernel.audio.record_start(&r.format) { + Ok(()) => Ok(Response::new(AudioResponse { ok: true, error: String::new() })), + Err(e) => Ok(Response::new(AudioResponse { ok: false, error: e })), + } + } + + async fn record_stop(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "audio.record_stop")?; + match self.kernel.audio.record_stop() { + Ok(data) => Ok(Response::new(AudioData { data, format: String::new() })), + Err(e) => Err(Status::internal(e)), + } + } + + type RecordStreamStream = tokio_stream::wrappers::ReceiverStream>; + + async fn record_stream(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "audio.record_stream")?; + let (_tx, rx) = tokio::sync::mpsc::channel(16); + Ok(Response::new(tokio_stream::wrappers::ReceiverStream::new(rx))) + } +} + +// ===== FS ===== +pub struct FsSvc { + kernel: Arc, + plugins: Arc, +} + +#[tonic::async_trait] +impl FsService for FsSvc { + async fn read(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "fs.read")?; + let r = req.into_inner(); + match self.kernel.fs.read(&r.path, r.offset, r.length).await { + Ok(data) => Ok(Response::new(FsReadResponse { data, ok: true, error: String::new() })), + Err(e) => Ok(Response::new(FsReadResponse { data: vec![], ok: false, error: e })), + } + } + + async fn write(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "fs.write")?; + let r = req.into_inner(); + match self.kernel.fs.write(&r.path, &r.data, r.append).await { + Ok(()) => Ok(Response::new(FsResponse { ok: true, error: String::new() })), + Err(e) => Ok(Response::new(FsResponse { ok: false, error: e })), + } + } + + async fn delete(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "fs.delete")?; + let r = req.into_inner(); + match self.kernel.fs.delete(&r.path).await { + Ok(()) => Ok(Response::new(FsResponse { ok: true, error: String::new() })), + Err(e) => Ok(Response::new(FsResponse { ok: false, error: e })), + } + } + + async fn rename(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "fs.rename")?; + let r = req.into_inner(); + match self.kernel.fs.rename(&r.from, &r.to).await { + Ok(()) => Ok(Response::new(FsResponse { ok: true, error: String::new() })), + Err(e) => Ok(Response::new(FsResponse { ok: false, error: e })), + } + } + + async fn stat(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "fs.stat")?; + let r = req.into_inner(); + match self.kernel.fs.stat(&r.path).await { + Ok(info) => Ok(Response::new(FsStatResponse { + ok: true, error: String::new(), + size: info.size, is_dir: info.is_dir, is_file: info.is_file, + modified: info.modified, created: info.created, permissions: 0, + })), + Err(e) => Ok(Response::new(FsStatResponse { + ok: false, error: e, size: 0, is_dir: false, is_file: false, + modified: 0, created: 0, permissions: 0, + })), + } + } + + async fn list(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "fs.list")?; + let r = req.into_inner(); + match self.kernel.fs.list(&r.path).await { + Ok(entries) => Ok(Response::new(FsListResponse { + ok: true, error: String::new(), + entries: entries.into_iter().map(|e| FsEntry { + name: e.name, is_dir: e.is_dir, size: e.size, + }).collect(), + })), + Err(e) => Ok(Response::new(FsListResponse { ok: false, error: e, entries: vec![] })), + } + } + + type WatchStream = tokio_stream::wrappers::ReceiverStream>; + + async fn watch(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "fs.watch")?; + let (_tx, rx) = tokio::sync::mpsc::channel(16); + Ok(Response::new(tokio_stream::wrappers::ReceiverStream::new(rx))) + } +} + +// ===== Memory ===== +pub struct MemorySvc { + kernel: Arc, + plugins: Arc, +} + +#[tonic::async_trait] +impl MemoryService for MemorySvc { + async fn read(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "memory.read")?; + let r = req.into_inner(); + match self.kernel.memory.read(&r.key) { + Ok(Some(v)) => Ok(Response::new(MemoryReadResponse { value: v, found: true, error: String::new() })), + Ok(None) => Ok(Response::new(MemoryReadResponse { value: vec![], found: false, error: String::new() })), + Err(e) => Ok(Response::new(MemoryReadResponse { value: vec![], found: false, error: e })), + } + } + + async fn write(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "memory.write")?; + let r = req.into_inner(); + match self.kernel.memory.write(&r.key, &r.value) { + Ok(()) => Ok(Response::new(MemoryResponse { ok: true, error: String::new() })), + Err(e) => Ok(Response::new(MemoryResponse { ok: false, error: e })), + } + } + + async fn delete(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "memory.delete")?; + let r = req.into_inner(); + match self.kernel.memory.delete(&r.key) { + Ok(()) => Ok(Response::new(MemoryResponse { ok: true, error: String::new() })), + Err(e) => Ok(Response::new(MemoryResponse { ok: false, error: e })), + } + } + + async fn list(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "memory.list")?; + let r = req.into_inner(); + match self.kernel.memory.list(&r.prefix, r.limit) { + Ok(keys) => Ok(Response::new(MemoryListResponse { keys, error: String::new() })), + Err(e) => Ok(Response::new(MemoryListResponse { keys: vec![], error: e })), + } + } + + async fn search(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "memory.search")?; + let r = req.into_inner(); + match self.kernel.memory.search(&r.query, r.limit) { + Ok(hits) => Ok(Response::new(MemorySearchResponse { + hits: hits.into_iter().map(|(k, s, sc)| MemorySearchHit { key: k, snippet: s, score: sc }).collect(), + error: String::new(), + })), + Err(e) => Ok(Response::new(MemorySearchResponse { hits: vec![], error: e })), + } + } +} + +// ===== Network ===== +pub struct NetworkSvc { + kernel: Arc, + plugins: Arc, +} + +#[tonic::async_trait] +impl NetworkService for NetworkSvc { + async fn request(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "network.request")?; + let r = req.into_inner(); + match self.kernel.network.request(&r.url, &r.method, &r.headers, &r.body).await { + Ok((status, headers, body)) => Ok(Response::new(HttpResponse { status, headers, body, error: String::new() })), + Err(e) => Ok(Response::new(HttpResponse { status: 0, headers: Default::default(), body: vec![], error: e })), + } + } + + type OpenConnStream = tokio_stream::wrappers::ReceiverStream>; + + async fn open_conn(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "network.open_conn")?; + let (_tx, rx) = tokio::sync::mpsc::channel(16); + Ok(Response::new(tokio_stream::wrappers::ReceiverStream::new(rx))) + } + + type ListenStream = tokio_stream::wrappers::ReceiverStream>; + + async fn listen(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "network.listen")?; + let (_tx, rx) = tokio::sync::mpsc::channel(16); + Ok(Response::new(tokio_stream::wrappers::ReceiverStream::new(rx))) + } + + async fn send(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "network.send")?; + Ok(Response::new(NetResponse { ok: false, error: "send: not yet implemented".to_string() })) + } + + async fn close(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "network.close")?; + Ok(Response::new(NetResponse { ok: false, error: "close: not yet implemented".to_string() })) + } + + async fn available(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "network.available")?; + Ok(Response::new(NetAvailableResponse { available: self.kernel.network.available() })) + } +} + +// ===== Input ===== +pub struct InputSvc { + kernel: Arc, + plugins: Arc, +} + +#[tonic::async_trait] +impl InputService for InputSvc { + async fn text(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "input.text")?; + match self.kernel.input.text().await { + Ok(t) => Ok(Response::new(InputTextResponse { text: t, ok: true, error: String::new() })), + Err(e) => Ok(Response::new(InputTextResponse { text: String::new(), ok: false, error: e })), + } + } + + async fn key(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "input.key")?; + Ok(Response::new(InputKeyResponse { key: String::new(), action: String::new(), modifiers: vec![] })) + } + + async fn clipboard(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "input.clipboard")?; + Ok(Response::new(InputClipboardResponse { + data: vec![], mime: String::new(), ok: false, error: "not implemented".to_string(), + })) + } + + type EventsStream = tokio_stream::wrappers::ReceiverStream>; + + async fn events(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "input.events")?; + let (_tx, rx) = tokio::sync::mpsc::channel(16); + Ok(Response::new(tokio_stream::wrappers::ReceiverStream::new(rx))) + } +} + +// ===== Process ===== +pub struct ProcessSvc { + kernel: Arc, + plugins: Arc, +} + +#[tonic::async_trait] +impl ProcessService for ProcessSvc { + async fn spawn(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "process.spawn")?; + let r = req.into_inner(); + let args: Vec = r.args; + match self.kernel.process.spawn(&r.cmd, &args, &r.env, &r.cwd).await { + Ok(pid) => Ok(Response::new(SpawnResponse { pid, ok: true, error: String::new() })), + Err(e) => Ok(Response::new(SpawnResponse { pid: 0, ok: false, error: e })), + } + } + + async fn kill(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "process.kill")?; + let r = req.into_inner(); + match self.kernel.process.kill(r.pid).await { + Ok(()) => Ok(Response::new(ProcResponse { ok: true, error: String::new() })), + Err(e) => Ok(Response::new(ProcResponse { ok: false, error: e })), + } + } + + async fn wait(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "process.wait")?; + let r = req.into_inner(); + match self.kernel.process.wait(r.pid).await { + Ok(code) => Ok(Response::new(WaitResponse { exit_code: code, ok: true, error: String::new() })), + Err(e) => Ok(Response::new(WaitResponse { exit_code: -1, ok: false, error: e })), + } + } + + async fn stdin(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "process.stdin")?; + let r = req.into_inner(); + match self.kernel.process.write_stdin(r.pid, &r.data).await { + Ok(()) => Ok(Response::new(ProcResponse { ok: true, error: String::new() })), + Err(e) => Ok(Response::new(ProcResponse { ok: false, error: e })), + } + } + + type StdoutStream = tokio_stream::wrappers::ReceiverStream>; + + async fn stdout(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "process.stdout")?; + let r = req.into_inner(); + let (tx, rx) = tokio::sync::mpsc::channel(64); + let kernel = self.kernel.clone(); + let pid = r.pid; + tokio::spawn(async move { + loop { + let mut buf = vec![0u8; 4096]; + let result = kernel.process.read_stdout(pid, &mut buf).await; + match result { + Ok(0) => break, + Ok(n) => { + if tx.send(Ok(DataChunk { data: buf[..n].to_vec() })).await.is_err() { + break; + } + } + Err(_) => break, + } + } + }); + Ok(Response::new(tokio_stream::wrappers::ReceiverStream::new(rx))) + } + + async fn signal(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "process.signal")?; + let r = req.into_inner(); + match self.kernel.process.signal(r.pid, r.signal).await { + Ok(()) => Ok(Response::new(ProcResponse { ok: true, error: String::new() })), + Err(e) => Ok(Response::new(ProcResponse { ok: false, error: e })), + } + } + + async fn list_proc(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "process.list_proc")?; + let procs = self.kernel.process.list(); + Ok(Response::new(ProcListResponse { + procs: procs.into_iter().map(|(pid, cmd, status)| ProcInfo { pid, cmd, status }).collect(), + })) + } +} + +// ===== Timer ===== +pub struct TimerSvc { + kernel: Arc, + plugins: Arc, +} + +#[tonic::async_trait] +impl TimerService for TimerSvc { + async fn once(&self, req: Request) -> Result, Status> { + let plugin_id = auth(&self.plugins, &req, "timer.once")?; + let r = req.into_inner(); + let id = self.kernel.timer.once(r.delay_ms, r.callback_id, plugin_id); + Ok(Response::new(TimerResponse { timer_id: id, ok: true, error: String::new() })) + } + + async fn cron(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "timer.cron")?; + Ok(Response::new(TimerResponse { + timer_id: String::new(), ok: false, error: "cron: not yet implemented".to_string(), + })) + } + + async fn cancel(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "timer.cancel")?; + let r = req.into_inner(); + match self.kernel.timer.cancel(&r.timer_id) { + Ok(()) => Ok(Response::new(TimerResponse { timer_id: r.timer_id, ok: true, error: String::new() })), + Err(e) => Ok(Response::new(TimerResponse { timer_id: r.timer_id, ok: false, error: e })), + } + } + + async fn now(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "timer.now")?; + Ok(Response::new(TimerNowResponse { timestamp_ms: self.kernel.timer.now() })) + } +} + +// ===== HID ===== +pub struct HidSvc { + kernel: Arc, + plugins: Arc, +} + +#[tonic::async_trait] +impl HidService for HidSvc { + async fn capture(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "hid.capture")?; + let r = req.into_inner(); + match self.kernel.hid.capture(r.x, r.y, r.width, r.height) { + Ok(image) => Ok(Response::new(CaptureResponse { image, format: "png".to_string(), ok: true, error: String::new() })), + Err(e) => Ok(Response::new(CaptureResponse { image: vec![], format: String::new(), ok: false, error: e })), + } + } + + async fn mouse(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "hid.mouse")?; + let r = req.into_inner(); + match self.kernel.hid.mouse(r.x, r.y, &r.action, &r.button) { + Ok(()) => Ok(Response::new(HidResponse { ok: true, error: String::new() })), + Err(e) => Ok(Response::new(HidResponse { ok: false, error: e })), + } + } + + async fn keyboard(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "hid.keyboard")?; + let r = req.into_inner(); + match self.kernel.hid.keyboard(&r.key, &r.action) { + Ok(()) => Ok(Response::new(HidResponse { ok: true, error: String::new() })), + Err(e) => Ok(Response::new(HidResponse { ok: false, error: e })), + } + } + + async fn ocr(&self, req: Request) -> Result, Status> { + auth(&self.plugins, &req, "hid.ocr")?; + let r = req.into_inner(); + match self.kernel.hid.ocr(r.x, r.y, r.width, r.height) { + Ok(text) => Ok(Response::new(OcrResponse { text, ok: true, error: String::new() })), + Err(e) => Ok(Response::new(OcrResponse { text: String::new(), ok: false, error: e })), + } + } +} + +// ===== Plugin Bridge ===== +pub struct BridgeSvc { + kernel: Arc, + plugins: Arc, +} + +#[tonic::async_trait] +impl PluginBridgeService for BridgeSvc { + async fn register(&self, req: Request) -> Result, Status> { + let r = req.into_inner(); + let record = PluginRecord { + id: r.id, + name: r.name, + version: r.version, + plugin_type: r.plugin_type, + syscalls: r.syscalls, + depends: r.depends, + exports: r.exports.into_iter().map(|e| (e.name, e.description)).collect(), + status: PluginStatus::Running, + }; + match self.plugins.register(record) { + Ok(()) => Ok(Response::new(RegisterResponse { ok: true, error: String::new(), session_id: uuid::Uuid::new_v4().to_string() })), + Err(e) => Ok(Response::new(RegisterResponse { ok: false, error: e, session_id: String::new() })), + } + } + + async fn call(&self, req: Request) -> Result, Status> { + let r = req.into_inner(); + let fn_name = &r.r#fn; + match self.plugins.find_export(&r.target, fn_name) { + Some(_plugin_id) => Ok(Response::new(CallResponse { + result: vec![], ok: false, + error: format!("call routing not yet implemented: {}.{}", r.target, fn_name), + })), + None => Ok(Response::new(CallResponse { + result: vec![], ok: false, + error: format!("export not found: {}.{}", r.target, fn_name), + })), + } + } + + type StreamCallStream = tokio_stream::wrappers::ReceiverStream>; + + async fn stream_call(&self, req: Request) -> Result, Status> { + let metadata_plugin_id = permission::plugin_id_from_metadata(req.metadata()); + let r = req.into_inner(); + let target = r.target; + + if let Some(ref metadata_id) = metadata_plugin_id { + if !target.is_empty() && metadata_id != &target { + return Err(Status::permission_denied(format!( + "plugin '{}' cannot subscribe to '{}' events", + metadata_id, target + ))); + } + } + + let plugin_id = metadata_plugin_id.unwrap_or(target); + if plugin_id.is_empty() { + return Err(Status::invalid_argument("stream_call requires target or x-plugin-id")); + } + permission::require_running_plugin(&self.plugins, &plugin_id)?; + + let mut events = self.kernel.callback_bus.subscribe(&plugin_id); + let (tx, rx) = tokio::sync::mpsc::channel(16); + let bus = self.kernel.callback_bus.clone(); + let subscribed_plugin_id = plugin_id.clone(); + tokio::spawn(async move { + while let Some(event) = events.recv().await { + let payload = serde_json::json!({ + "event": "timer", + "timer_id": event.timer_id, + "callback_id": event.callback_id, + }); + let response = CallResponse { result: payload.to_string().into_bytes(), ok: true, error: String::new() }; + if tx.send(Ok(response)).await.is_err() { + break; + } + } + bus.remove(&subscribed_plugin_id); + }); + Ok(Response::new(tokio_stream::wrappers::ReceiverStream::new(rx))) + } + + async fn heartbeat(&self, _req: Request) -> Result, Status> { + Ok(Response::new(HeartbeatResponse { ok: true })) + } +} + +fn display_svc(kernel: &Arc, plugins: &Arc) -> DisplaySvc { + DisplaySvc { kernel: kernel.clone(), plugins: plugins.clone() } +} +fn audio_svc(kernel: &Arc, plugins: &Arc) -> AudioSvc { + AudioSvc { kernel: kernel.clone(), plugins: plugins.clone() } +} +fn fs_svc(kernel: &Arc, plugins: &Arc) -> FsSvc { + FsSvc { kernel: kernel.clone(), plugins: plugins.clone() } +} +fn memory_svc(kernel: &Arc, plugins: &Arc) -> MemorySvc { + MemorySvc { kernel: kernel.clone(), plugins: plugins.clone() } +} +fn network_svc(kernel: &Arc, plugins: &Arc) -> NetworkSvc { + NetworkSvc { kernel: kernel.clone(), plugins: plugins.clone() } +} +fn input_svc(kernel: &Arc, plugins: &Arc) -> InputSvc { + InputSvc { kernel: kernel.clone(), plugins: plugins.clone() } +} +fn process_svc(kernel: &Arc, plugins: &Arc) -> ProcessSvc { + ProcessSvc { kernel: kernel.clone(), plugins: plugins.clone() } +} +fn timer_svc(kernel: &Arc, plugins: &Arc) -> TimerSvc { + TimerSvc { kernel: kernel.clone(), plugins: plugins.clone() } +} +fn hid_svc(kernel: &Arc, plugins: &Arc) -> HidSvc { + HidSvc { kernel: kernel.clone(), plugins: plugins.clone() } +} + +// ===== Server startup ===== +pub async fn run(kernel: Kernel) -> anyhow::Result<()> { + use agentsd_proto::agentsd::{ + audio_server::AudioServer, display_server::DisplayServer, fs_server::FsServer, + hid_server::HidServer, input_server::InputServer, memory_server::MemoryServer, + network_server::NetworkServer, plugin_bridge_server::PluginBridgeServer, + process_server::ProcessServer, timer_server::TimerServer, + }; + use tonic::transport::Server; + + let server = AgentsdServer::new(kernel); + let addr = "[::1]:50051".parse()?; + + tracing::info!("agentsd listening on {}", addr); + + Server::builder() + .add_service(DisplayServer::new(display_svc(&server.kernel, &server.plugins))) + .add_service(AudioServer::new(audio_svc(&server.kernel, &server.plugins))) + .add_service(FsServer::new(fs_svc(&server.kernel, &server.plugins))) + .add_service(MemoryServer::new(memory_svc(&server.kernel, &server.plugins))) + .add_service(NetworkServer::new(network_svc(&server.kernel, &server.plugins))) + .add_service(InputServer::new(input_svc(&server.kernel, &server.plugins))) + .add_service(ProcessServer::new(process_svc(&server.kernel, &server.plugins))) + .add_service(TimerServer::new(timer_svc(&server.kernel, &server.plugins))) + .add_service(HidServer::new(hid_svc(&server.kernel, &server.plugins))) + .add_service(PluginBridgeServer::new(BridgeSvc { kernel: server.kernel.clone(), plugins: server.plugins.clone() })) + .serve_with_shutdown(addr, async { + tokio::signal::ctrl_c().await.ok(); + tracing::info!("shutdown signal received, stopping..."); + }) + .await?; + + tracing::info!("agentsd stopped"); + Ok(()) +} diff --git a/core/agentsd/src/syscall/audio.rs b/core/agentsd/src/syscall/audio.rs new file mode 100644 index 0000000..bf5d074 --- /dev/null +++ b/core/agentsd/src/syscall/audio.rs @@ -0,0 +1,54 @@ +/// Audio syscall: playback and recording. +/// Actual implementation delegated to system plugins. +/// This is the kernel-side stub that dispatches to the active audio driver. +pub struct AudioSyscall { + available: bool, +} + +impl AudioSyscall { + pub fn new() -> Self { + // Audio availability detected at runtime + Self { available: false } + } + + #[allow(dead_code)] + pub fn is_available(&self) -> bool { + self.available + } + + pub fn play(&self, _data: &[u8], _format: &str) -> Result<(), String> { + if !self.available { + return Err("audio: no driver loaded".to_string()); + } + // Dispatched to system plugin via FFI + Ok(()) + } + + pub fn stop(&self) -> Result<(), String> { + if !self.available { + return Err("audio: no driver loaded".to_string()); + } + Ok(()) + } + + pub fn volume(&self, _level: f32) -> Result<(), String> { + if !self.available { + return Err("audio: no driver loaded".to_string()); + } + Ok(()) + } + + pub fn record_start(&self, _format: &str) -> Result<(), String> { + if !self.available { + return Err("audio: no driver loaded".to_string()); + } + Ok(()) + } + + pub fn record_stop(&self) -> Result, String> { + if !self.available { + return Err("audio: no driver loaded".to_string()); + } + Ok(Vec::new()) + } +} diff --git a/core/agentsd/src/syscall/display.rs b/core/agentsd/src/syscall/display.rs new file mode 100644 index 0000000..00dcaf4 --- /dev/null +++ b/core/agentsd/src/syscall/display.rs @@ -0,0 +1,39 @@ +use std::io::Write as IoWrite; + +/// Display syscall: output to user. +pub struct DisplaySyscall; + +impl DisplaySyscall { + pub fn new() -> Self { + Self + } + + pub fn text(&self, content: &str) -> Result<(), String> { + let stdout = std::io::stdout(); + let mut handle = stdout.lock(); + handle + .write_all(content.as_bytes()) + .map_err(|e| e.to_string())?; + handle.write_all(b"\n").map_err(|e| e.to_string())?; + handle.flush().map_err(|e| e.to_string()) + } + + pub 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) + } + + pub fn image(&self, _data: &[u8], _format: &str) -> Result<(), String> { + self.text("[image]") + } + + pub fn clear(&self) -> Result<(), String> { + // ANSI clear + print!("\x1b[2J\x1b[H"); + std::io::stdout().flush().map_err(|e| e.to_string()) + } + + pub fn notify(&self, message: &str) -> Result<(), String> { + self.text(&format!("[notify] {}", message)) + } +} diff --git a/core/agentsd/src/syscall/fs.rs b/core/agentsd/src/syscall/fs.rs new file mode 100644 index 0000000..eeaed3e --- /dev/null +++ b/core/agentsd/src/syscall/fs.rs @@ -0,0 +1,114 @@ +use std::path::Path; +use tokio::fs as async_fs; + +/// FS syscall: file system operations. +pub struct FsSyscall; + +impl FsSyscall { + pub fn new() -> Self { + Self + } + + pub async fn read(&self, path: &str, offset: i64, length: i64) -> Result, String> { + let data = async_fs::read(path).await.map_err(|e| e.to_string())?; + let start = if offset > 0 { offset as usize } else { 0 }; + let end = if length > 0 { + std::cmp::min(start + length as usize, data.len()) + } else { + data.len() + }; + if start >= data.len() { + return Ok(Vec::new()); + } + Ok(data[start..end].to_vec()) + } + + pub async fn write(&self, path: &str, data: &[u8], append: bool) -> Result<(), String> { + if let Some(parent) = Path::new(path).parent() { + async_fs::create_dir_all(parent) + .await + .map_err(|e| e.to_string())?; + } + if append { + use tokio::io::AsyncWriteExt; + let mut file = tokio::fs::OpenOptions::new() + .create(true) + .append(true) + .open(path) + .await + .map_err(|e| e.to_string())?; + file.write_all(data).await.map_err(|e| e.to_string()) + } else { + async_fs::write(path, data).await.map_err(|e| e.to_string()) + } + } + + pub async fn delete(&self, path: &str) -> Result<(), String> { + let meta = async_fs::metadata(path).await.map_err(|e| e.to_string())?; + if meta.is_dir() { + async_fs::remove_dir_all(path) + .await + .map_err(|e| e.to_string()) + } else { + async_fs::remove_file(path) + .await + .map_err(|e| e.to_string()) + } + } + + pub async fn rename(&self, from: &str, to: &str) -> Result<(), String> { + async_fs::rename(from, to).await.map_err(|e| e.to_string()) + } + + pub async fn stat(&self, path: &str) -> Result { + let meta = async_fs::metadata(path).await.map_err(|e| e.to_string())?; + let modified = meta + .modified() + .ok() + .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok()) + .map(|d| d.as_millis() as i64) + .unwrap_or(0); + let created = meta + .created() + .ok() + .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok()) + .map(|d| d.as_millis() as i64) + .unwrap_or(0); + + Ok(FsStatInfo { + size: meta.len() as i64, + is_dir: meta.is_dir(), + is_file: meta.is_file(), + modified, + created, + }) + } + + pub async fn list(&self, dir: &str) -> Result, String> { + let mut entries = Vec::new(); + let mut read_dir = async_fs::read_dir(dir).await.map_err(|e| e.to_string())?; + while let Some(entry) = read_dir.next_entry().await.map_err(|e| e.to_string())? { + let meta = entry.metadata().await.map_err(|e| e.to_string())?; + entries.push(FsEntryInfo { + name: entry.file_name().to_string_lossy().to_string(), + is_dir: meta.is_dir(), + size: meta.len() as i64, + }); + } + Ok(entries) + } +} + +pub struct FsStatInfo { + pub size: i64, + pub is_dir: bool, + pub is_file: bool, + pub modified: i64, + pub created: i64, +} + +pub struct FsEntryInfo { + pub name: String, + pub is_dir: bool, + pub size: i64, +} diff --git a/core/agentsd/src/syscall/hid.rs b/core/agentsd/src/syscall/hid.rs new file mode 100644 index 0000000..10b6590 --- /dev/null +++ b/core/agentsd/src/syscall/hid.rs @@ -0,0 +1,50 @@ +/// HID syscall: screen capture and input simulation. +/// Capability can be absent (headless environments). +pub struct HidSyscall { + available: bool, +} + +impl HidSyscall { + pub fn new() -> Self { + // Detect if desktop environment is available + let available = std::env::var("DISPLAY").is_ok() + || std::env::var("WAYLAND_DISPLAY").is_ok() + || cfg!(target_os = "windows") + || cfg!(target_os = "macos"); + Self { available } + } + + #[allow(dead_code)] + pub fn is_available(&self) -> bool { + self.available + } + + pub fn capture(&self, _x: i32, _y: i32, _w: i32, _h: i32) -> Result, String> { + if !self.available { + return Err("hid: no desktop environment".to_string()); + } + // Stub: actual implementation via system plugin + Err("hid.capture: not implemented (requires system plugin)".to_string()) + } + + pub fn mouse(&self, _x: i32, _y: i32, _action: &str, _button: &str) -> Result<(), String> { + if !self.available { + return Err("hid: no desktop environment".to_string()); + } + Err("hid.mouse: not implemented (requires system plugin)".to_string()) + } + + pub fn keyboard(&self, _key: &str, _action: &str) -> Result<(), String> { + if !self.available { + return Err("hid: no desktop environment".to_string()); + } + Err("hid.keyboard: not implemented (requires system plugin)".to_string()) + } + + pub fn ocr(&self, _x: i32, _y: i32, _w: i32, _h: i32) -> Result { + if !self.available { + return Err("hid: no desktop environment".to_string()); + } + Err("hid.ocr: not implemented (requires system plugin)".to_string()) + } +} diff --git a/core/agentsd/src/syscall/input.rs b/core/agentsd/src/syscall/input.rs new file mode 100644 index 0000000..cb978f3 --- /dev/null +++ b/core/agentsd/src/syscall/input.rs @@ -0,0 +1,21 @@ +use tokio::io::{self, AsyncBufReadExt, BufReader}; + +/// Input syscall: receive user input. +pub struct InputSyscall; + +impl InputSyscall { + pub fn new() -> Self { + Self + } + + pub async fn text(&self) -> Result { + let stdin = io::stdin(); + let mut reader = BufReader::new(stdin); + let mut line = String::new(); + reader + .read_line(&mut line) + .await + .map_err(|e| e.to_string())?; + Ok(line.trim_end().to_string()) + } +} diff --git a/core/agentsd/src/syscall/memory.rs b/core/agentsd/src/syscall/memory.rs new file mode 100644 index 0000000..e2cf667 --- /dev/null +++ b/core/agentsd/src/syscall/memory.rs @@ -0,0 +1,111 @@ +use rusqlite::{params, Connection}; +use std::sync::Mutex; + +/// Memory syscall: structured KV storage with FTS5 full-text search. +/// Data is persisted to `/data/memory.db`. +pub struct MemorySyscall { + conn: Mutex, +} + +impl MemorySyscall { + pub fn new() -> anyhow::Result { + let db_path = crate::paths::data_dir().join("memory.db"); + let conn = Connection::open(&db_path)?; + conn.execute_batch( + "CREATE TABLE IF NOT EXISTS kv ( + key TEXT PRIMARY KEY, + value BLOB NOT NULL, + text_value TEXT, + updated_at INTEGER DEFAULT (strftime('%s','now')) + ); + CREATE VIRTUAL TABLE IF NOT EXISTS kv_fts USING fts5( + key, text_value, tokenize='trigram' + ); + CREATE TRIGGER IF NOT EXISTS kv_ai AFTER INSERT ON kv BEGIN + INSERT INTO kv_fts(key, text_value) VALUES (NEW.key, NEW.text_value); + END; + CREATE TRIGGER IF NOT EXISTS kv_au AFTER UPDATE ON kv BEGIN + DELETE FROM kv_fts WHERE key = OLD.key; + INSERT INTO kv_fts(key, text_value) VALUES (NEW.key, NEW.text_value); + END; + CREATE TRIGGER IF NOT EXISTS kv_ad AFTER DELETE ON kv BEGIN + DELETE FROM kv_fts WHERE key = OLD.key; + END;", + )?; + tracing::info!("memory: opened {}", db_path.display()); + Ok(Self { + conn: Mutex::new(conn), + }) + } + + pub fn read(&self, key: &str) -> Result>, String> { + let conn = self.conn.lock().unwrap(); + let mut stmt = conn + .prepare("SELECT value FROM kv WHERE key = ?1") + .map_err(|e| e.to_string())?; + let result = stmt + .query_row(params![key], |row| row.get::<_, Vec>(0)) + .ok(); + Ok(result) + } + + pub fn write(&self, key: &str, value: &[u8]) -> Result<(), String> { + let conn = self.conn.lock().unwrap(); + let text_value = String::from_utf8(value.to_vec()).ok(); + conn.execute( + "INSERT OR REPLACE INTO kv (key, value, text_value, updated_at) VALUES (?1, ?2, ?3, strftime('%s','now'))", + params![key, value, text_value], + ) + .map_err(|e| e.to_string())?; + Ok(()) + } + + pub fn delete(&self, key: &str) -> Result<(), String> { + let conn = self.conn.lock().unwrap(); + conn.execute("DELETE FROM kv WHERE key = ?1", params![key]) + .map_err(|e| e.to_string())?; + Ok(()) + } + + pub fn list(&self, prefix: &str, limit: i32) -> Result, String> { + let conn = self.conn.lock().unwrap(); + let lim = if limit > 0 { limit } else { 1000 }; + let mut stmt = conn + .prepare("SELECT key FROM kv WHERE key LIKE ?1 ORDER BY key LIMIT ?2") + .map_err(|e| e.to_string())?; + let pattern = format!("{}%", prefix); + let rows = stmt + .query_map(params![pattern, lim], |row| row.get::<_, String>(0)) + .map_err(|e| e.to_string())?; + let mut keys = Vec::new(); + for row in rows { + keys.push(row.map_err(|e| e.to_string())?); + } + Ok(keys) + } + + pub fn search(&self, query: &str, limit: i32) -> Result, String> { + let conn = self.conn.lock().unwrap(); + let lim = if limit > 0 { limit } else { 20 }; + let mut stmt = conn + .prepare( + "SELECT key, snippet(kv_fts, 1, '', '', '...', 32), rank + FROM kv_fts WHERE kv_fts MATCH ?1 ORDER BY rank LIMIT ?2", + ) + .map_err(|e| e.to_string())?; + let rows = stmt + .query_map(params![query, lim], |row| { + Ok(( + row.get::<_, String>(0)?, + row.get::<_, String>(1)?, + row.get::<_, f64>(2)? as f32, + )) + }) + .map_err(|e| e.to_string())?; + let mut hits = Vec::new(); + for row in rows { + hits.push(row.map_err(|e| e.to_string())?); + } + Ok(hits) + } +} diff --git a/core/agentsd/src/syscall/mod.rs b/core/agentsd/src/syscall/mod.rs new file mode 100644 index 0000000..1b65918 --- /dev/null +++ b/core/agentsd/src/syscall/mod.rs @@ -0,0 +1,46 @@ +pub mod display; +pub mod audio; +pub mod fs; +pub mod memory; +pub mod network; +pub mod process; +pub mod timer; +pub mod hid; +pub mod input; + +use anyhow::Result; +use std::sync::Arc; + +use crate::callback::CallbackBus; + +/// The kernel holds all syscall implementations. +pub struct Kernel { + pub display: Arc, + pub audio: Arc, + pub fs: Arc, + pub memory: Arc, + pub network: Arc, + pub process: Arc, + pub timer: Arc, + pub hid: Arc, + pub input: Arc, + pub callback_bus: Arc, +} + +impl Kernel { + pub fn new() -> Result { + let callback_bus = Arc::new(CallbackBus::new()); + Ok(Self { + display: Arc::new(display::DisplaySyscall::new()), + audio: Arc::new(audio::AudioSyscall::new()), + fs: Arc::new(fs::FsSyscall::new()), + memory: Arc::new(memory::MemorySyscall::new()?), + network: Arc::new(network::NetworkSyscall::new()), + process: Arc::new(process::ProcessSyscall::new()), + timer: Arc::new(timer::TimerSyscall::new(callback_bus.clone())), + hid: Arc::new(hid::HidSyscall::new()), + input: Arc::new(input::InputSyscall::new()), + callback_bus, + }) + } +} diff --git a/core/agentsd/src/syscall/network.rs b/core/agentsd/src/syscall/network.rs new file mode 100644 index 0000000..8cf70ed --- /dev/null +++ b/core/agentsd/src/syscall/network.rs @@ -0,0 +1,60 @@ +/// Network syscall: remote communication. +pub struct NetworkSyscall; + +impl NetworkSyscall { + pub fn new() -> Self { + Self + } + + pub fn available(&self) -> bool { + // Simple connectivity check: try to resolve a known host + std::net::TcpStream::connect_timeout( + &std::net::SocketAddr::from(([8, 8, 8, 8], 53)), + std::time::Duration::from_secs(2), + ) + .is_ok() + } + + pub async fn request( + &self, + url: &str, + method: &str, + headers: &std::collections::HashMap, + body: &[u8], + ) -> Result<(i32, std::collections::HashMap, Vec), String> { + // Minimal HTTP client using tokio TcpStream + manual HTTP. + // For production, use hyper or reqwest. For now, shell out to a basic impl. + use tokio::process::Command; + + let mut cmd = Command::new("curl"); + cmd.arg("-s") + .arg("-X") + .arg(method) + .arg("-o") + .arg("-") + .arg("-w") + .arg("\n%{http_code}"); + + for (k, v) in headers { + cmd.arg("-H").arg(format!("{}: {}", k, v)); + } + + if !body.is_empty() { + cmd.arg("-d").arg(String::from_utf8_lossy(body).to_string()); + } + + cmd.arg(url); + + let output = cmd.output().await.map_err(|e| e.to_string())?; + let raw = String::from_utf8_lossy(&output.stdout); + let lines: Vec<&str> = raw.rsplitn(2, '\n').collect(); + + let status = lines + .first() + .and_then(|s| s.parse::().ok()) + .unwrap_or(0); + let response_body = lines.get(1).unwrap_or(&"").as_bytes().to_vec(); + + Ok((status, std::collections::HashMap::new(), response_body)) + } +} diff --git a/core/agentsd/src/syscall/process.rs b/core/agentsd/src/syscall/process.rs new file mode 100644 index 0000000..dd88f25 --- /dev/null +++ b/core/agentsd/src/syscall/process.rs @@ -0,0 +1,124 @@ +use std::collections::HashMap; +use std::sync::Mutex; +use tokio::process::Command; + +struct ManagedProcess { + cmd: String, +} + +/// Process syscall: spawn and manage subprocesses. +/// Uses the real OS PID as the identifier. +pub struct ProcessSyscall { + procs: Mutex>, + children: tokio::sync::Mutex>, +} + +impl ProcessSyscall { + pub fn new() -> Self { + Self { + procs: Mutex::new(HashMap::new()), + children: tokio::sync::Mutex::new(HashMap::new()), + } + } + + pub async fn spawn( + &self, + cmd: &str, + args: &[String], + env: &HashMap, + cwd: &str, + ) -> Result { + let mut command = Command::new(cmd); + command.args(args); + if !cwd.is_empty() { + command.current_dir(cwd); + } + for (k, v) in env { + command.env(k, v); + } + command + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()); + + let child = command.spawn().map_err(|e| e.to_string())?; + let pid = child + .id() + .ok_or_else(|| "spawned process has no OS pid".to_string())? as u64; + + self.procs.lock().unwrap().insert( + pid, + ManagedProcess { + cmd: cmd.to_string(), + }, + ); + self.children.lock().await.insert(pid, child); + + tracing::info!("process spawned: pid={} cmd={}", pid, cmd); + Ok(pid) + } + + pub async fn kill(&self, pid: u64) -> Result<(), String> { + let mut child = { + let mut children = self.children.lock().await; + children.remove(&pid).ok_or_else(|| format!("pid {} not found", pid))? + }; + + child.kill().await.map_err(|e| e.to_string())?; + self.procs.lock().unwrap().remove(&pid); + tracing::info!("process killed: pid={}", pid); + Ok(()) + } + + pub async fn wait(&self, pid: u64) -> Result { + let mut child = { + let mut children = self.children.lock().await; + children.remove(&pid).ok_or_else(|| format!("pid {} not found", pid))? + }; + self.procs.lock().unwrap().remove(&pid); + let status = child.wait().await.map_err(|e| e.to_string())?; + Ok(status.code().unwrap_or(-1)) + } + + pub async fn write_stdin(&self, pid: u64, data: &[u8]) -> Result<(), String> { + use tokio::io::AsyncWriteExt; + let mut children = self.children.lock().await; + if let Some(child) = children.get_mut(&pid) { + if let Some(stdin) = child.stdin.as_mut() { + stdin.write_all(data).await.map_err(|e| e.to_string())?; + stdin.flush().await.map_err(|e| e.to_string())?; + Ok(()) + } else { + 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 { + use tokio::io::AsyncReadExt; + let mut children = self.children.lock().await; + if let Some(child) = children.get_mut(&pid) { + if let Some(stdout) = child.stdout.as_mut() { + stdout.read(buf).await.map_err(|e| e.to_string()) + } else { + Err("stdout not available".to_string()) + } + } else { + Err(format!("pid {} not found", pid)) + } + } + + pub async fn signal(&self, pid: u64, _signal: i32) -> Result<(), String> { + self.kill(pid).await + } + + pub fn list(&self) -> Vec<(u64, String, String)> { + let procs = self.procs.lock().unwrap(); + procs + .iter() + .map(|(pid, mp)| (*pid, mp.cmd.clone(), "running".to_string())) + .collect() + } +} diff --git a/core/agentsd/src/syscall/timer.rs b/core/agentsd/src/syscall/timer.rs new file mode 100644 index 0000000..033e8b4 --- /dev/null +++ b/core/agentsd/src/syscall/timer.rs @@ -0,0 +1,80 @@ +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; +use tokio::time::{self, Duration}; + +use crate::callback::{CallbackBus, TimerEvent}; + +struct TimerEntry { + handle: tokio::task::JoinHandle<()>, +} + +/// Timer syscall: delayed and periodic execution with callback delivery. +pub struct TimerSyscall { + timers: Arc>>, + next_id: Mutex, + bus: Arc, +} + +impl TimerSyscall { + pub fn new(bus: Arc) -> Self { + Self { + timers: Arc::new(Mutex::new(HashMap::new())), + next_id: Mutex::new(1), + bus, + } + } + + /// Schedule a one-shot timer. When it fires, a TimerEvent is sent to the plugin. + pub fn once(&self, delay_ms: u64, callback_id: String, plugin_id: String) -> String { + let timer_id = { + let mut next = self.next_id.lock().unwrap(); + let id = format!("timer_{}", *next); + *next += 1; + id + }; + + let tid = timer_id.clone(); + let cb = callback_id.clone(); + let pid = plugin_id.clone(); + let bus = self.bus.clone(); + let timers = self.timers.clone(); + + let handle = tokio::spawn(async move { + time::sleep(Duration::from_millis(delay_ms)).await; + tracing::info!("timer fired: id={} callback={} plugin={}", tid, cb, pid); + bus.fire( + &pid, + TimerEvent { + timer_id: tid.clone(), + callback_id: cb, + }, + ); + timers.lock().unwrap().remove(&tid); + }); + + self.timers.lock().unwrap().insert( + timer_id.clone(), + TimerEntry { + handle, + }, + ); + timer_id + } + + pub fn cancel(&self, timer_id: &str) -> Result<(), String> { + let mut timers = self.timers.lock().unwrap(); + if let Some(entry) = timers.remove(timer_id) { + entry.handle.abort(); + Ok(()) + } else { + Err(format!("timer {} not found", timer_id)) + } + } + + pub fn now(&self) -> i64 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as i64 + } +} diff --git a/core/proto/Cargo.toml b/core/proto/Cargo.toml new file mode 100644 index 0000000..9db922a --- /dev/null +++ b/core/proto/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "agentsd-proto" +version = "0.1.0" +edition = "2021" + +[dependencies] +tonic = { workspace = true } +prost = { workspace = true } + +[build-dependencies] +tonic-build = { workspace = true } diff --git a/core/proto/build.rs b/core/proto/build.rs new file mode 100644 index 0000000..de33892 --- /dev/null +++ b/core/proto/build.rs @@ -0,0 +1,7 @@ +fn main() -> Result<(), Box> { + tonic_build::configure() + .build_server(true) + .build_client(true) + .compile_protos(&["../../proto/agentsd.proto"], &["../../proto/"])?; + Ok(()) +} diff --git a/core/proto/src/lib.rs b/core/proto/src/lib.rs new file mode 100644 index 0000000..fc074f0 --- /dev/null +++ b/core/proto/src/lib.rs @@ -0,0 +1,5 @@ +pub mod agentsd { + tonic::include_proto!("agentsd"); +} + +pub use agentsd::*; diff --git a/docs/01-内核系统调用.md b/docs/01-内核系统调用.md new file mode 100644 index 0000000..925aa45 --- /dev/null +++ b/docs/01-内核系统调用.md @@ -0,0 +1,164 @@ +# 01 - 内核系统调用 + +agentsd 定义 9 个 syscall 接口。类比 Linux 内核 syscall 表——接口稳定,实现可换(由系统插件提供)。 + +--- + +## 1. Display + +向用户呈现信息。类比: `write(stdout)` / framebuffer / DRM。 + +**管**:给人看的输出 | **不管**:文件写入、设备控制 + +``` +display.text(content) +display.rich(markup) +display.image(data, format) +display.clear() +display.notify(message) +``` + +--- + +## 2. Audio + +声音播放与采集。类比: `/dev/snd` / ALSA / PipeWire。 + +**管**:扬声器输出、麦克风输入 | **不管**:TTS/STT(插件的事) + +``` +audio.play(data, format) +audio.stop() +audio.volume(level) +audio.record.start(format) +audio.record.stop() -> data +audio.record.stream(callback) +``` + +--- + +## 3. FS + +文件系统。类比: `open / read / write / stat / readdir / inotify`。 + +**管**:磁盘文件 | **不管**:结构化数据(Memory) + +``` +fs.read(path) -> data +fs.write(path, data) +fs.delete(path) +fs.rename(from, to) +fs.stat(path) -> metadata +fs.list(dir) -> entries +fs.watch(path, callback) +``` + +--- + +## 4. Memory + +结构化存储。类比: `mmap / shmem`,但面向 KV + 索引。 + +**管**:持久数据、查询 | **不管**:文件(FS) + +``` +memory.read(key) -> value +memory.write(key, value) +memory.delete(key) +memory.list(prefix?) -> keys +memory.search(query) -> results +``` + +--- + +## 5. Network + +远程通信。类比: `socket / connect / bind / listen / send / recv`。 + +**管**:对外连接、监听 | **不管**:本地 IPC(内核 gRPC 总线) + +``` +net.request(url, method, body?) -> response +net.connect(addr, protocol) -> conn +net.listen(port, protocol) -> listener +net.send(conn, data) +net.recv(conn) -> data +net.close(conn) +net.available() -> bool +``` + +--- + +## 6. Process + +子进程管理。类比: `fork / execve / waitpid / kill / pipe`。 + +**管**:子进程生命周期、stdio | **不管**:插件间路由(内核调度) + +``` +proc.spawn(cmd, args, env?) -> pid +proc.kill(pid) +proc.wait(pid) -> exitcode +proc.stdin(pid, data) +proc.stdout(pid) -> data +proc.signal(pid, sig) +proc.list() -> [pid] +``` + +--- + +## 7. Timer + +定时调度。类比: `timer_create / timerfd_create`。 + +**管**:时间触发 | **不管**:用户事件(Input) + +``` +timer.once(delay, callback) -> id # 到期后通过 PluginBridge.StreamCall 投递事件 +timer.cron(expr, callback) -> id +timer.cancel(id) +timer.now() -> timestamp +``` + +--- + +## 8. HID + +屏幕截取与输入模拟。类比: `/dev/input` + `/dev/fb` + uinput。 + +能力可缺失。 + +**管**:屏幕读取、输入模拟 | **不管**:向用户展示(Display)、用户主动输入(Input) + +``` +hid.capture(region?) -> image +hid.mouse(x, y, action) +hid.keyboard(key, action) +hid.ocr(region?) -> text +``` + +--- + +## 9. Input + +用户主动输入。类比: `read(stdin)` / evdev。 + +**管**:用户给的东西 | **不管**:文件读(FS)、录音(Audio)、屏幕读(HID) + +``` +input.text() -> string +input.key() -> keyevent +input.clipboard() -> data +input.event() -> event +``` + +--- + +## 设计原则 + +1. **接口稳定,实现可换** — syscall 签名不变,系统插件可替换(= 换驱动不改应用) +2. **每个独立** — 可单独实现、测试、编译(= 内核模块独立) +3. **能力可缺失** — 无声卡则 Audio 不可用,不崩(= Linux 无设备则驱动不加载) +4. **权限在此层** — 白名单外的 syscall 拒绝(= seccomp) +5. **离线 = Network 不可用** — 其余八个正常(= 拔网线不影响本地) +6. **内核不做业务** — TTS/浏览器/Agent 全是插件(= 内核不含 Web 服务器) diff --git a/docs/02-插件模型.md b/docs/02-插件模型.md new file mode 100644 index 0000000..61935aa --- /dev/null +++ b/docs/02-插件模型.md @@ -0,0 +1,179 @@ +# 02 - 插件模型 + +## 三种插件(对标 Linux 三层) + +``` +┌─────────────────────────────────────────────────────┐ +│ 标准插件 ← 应用服务(nginx、docker) │ +│ 可依赖基础插件 + 其他标准插件 │ +├─────────────────────────────────────────────────────┤ +│ 基础插件 ← 基础 daemon(udevd、resolved) │ +│ 只依赖 9 个 syscall,无插件依赖 │ +├─────────────────────────────────────────────────────┤ +│ 系统插件 ← 内核模块 / 设备驱动(.ko) │ +│ 注册为 syscall 的实现 │ +├─────────────────────────────────────────────────────┤ +│ agentsd ← Linux 内核 + systemd(PID 1) │ +└─────────────────────────────────────────────────────┘ +``` + +--- + +### 系统插件(= 内核模块 / 驱动) + +为 syscall 提供具体实现。类比 `insmod alsa_driver.ko`。 + +```yaml +id: alsa-audio +type: system +provides: audio +entry: ./libalsa_audio.so +``` + +- FFI 加载(.so/.dylib/.dll),零开销 +- 只限 Rust / C ABI +- 同一 syscall 可有多个驱动,同时只激活一个 +- 无依赖 + +--- + +### 基础插件(= systemd 基础 unit,无依赖) + +只调 syscall,不调其他插件。类比 `systemd-resolved.service`(只用内核网络栈)。 + +```yaml +id: tts-edge +type: basic +syscalls: + - audio.play + - network.request +exports: + - name: speak +``` + +- 独立进程,gRPC/TCP 通信(后续切 UDS/Named Pipe) +- 零插件依赖,加载顺序无所谓(= 无 After/Requires) +- 任意语言 + +--- + +### 标准插件(= systemd 应用 unit,有依赖) + +组合其他插件。类比 `nginx.service`(After=network.target, Requires=...)。 + +```yaml +id: voice-agent +type: standard +syscalls: + - input.text + - display.text +depends: + - tts-edge + - stt-whisper + - chat-agent +exports: + - name: voice_chat +``` + +- 可依赖基础插件 + 其他标准插件 +- 按依赖拓扑排序加载(= systemd ordering) +- 任意语言 + +--- + +## 通信:gRPC(= D-Bus) + +类比 Linux 的 D-Bus 进程间通信总线,agentsd 用 gRPC。 + +### 为什么 gRPC + +- protobuf 二进制,快 +- 流式 RPC(音频流、文件 watch) +- 所有语言都有库 +- `.proto` = 接口契约 + 代码生成 + +### 传输分层 + +| 插件类型 | 传输 | 延迟 | Linux 类比 | +|---|---|---|---| +| 系统插件 | FFI | ~纳秒 | 内核态函数调用 | +| 基础/标准(本机,当前) | gRPC/TCP | ~毫秒 | D-Bus over TCP | +| 基础/标准(目标) | gRPC/Unix socket / Windows Named Pipe | ~微秒 | D-Bus over UDS | +| 远程(可选) | gRPC/TCP | ~毫秒 | D-Bus over TCP | + +### proto 示例 + +```protobuf +syntax = "proto3"; + +service FS { + rpc Read(ReadRequest) returns (ReadResponse); + rpc Write(WriteRequest) returns (WriteResponse); + rpc Delete(DeleteRequest) returns (DeleteResponse); + rpc List(ListRequest) returns (ListResponse); + rpc Watch(WatchRequest) returns (stream WatchEvent); +} + +service Audio { + rpc Play(stream AudioChunk) returns (PlayResponse); + rpc Record(RecordRequest) returns (stream AudioChunk); +} +``` + +### 插件间调用(经内核路由) + +```protobuf +service PluginBridge { + rpc Call(CallRequest) returns (CallResponse); + rpc StreamCall(CallRequest) returns (stream CallResponse); +} + +message CallRequest { + string target = 1; + string fn = 2; + bytes args = 3; +} +``` + +--- + +## 生命周期管理(= systemctl) + +| agentsd 命令 | systemd 对标 | 作用 | +|---|---|---| +| `agentsd start ` | `systemctl start` | 启动插件 | +| `agentsd stop ` | `systemctl stop` | 停止插件 | +| `agentsd restart ` | `systemctl restart` | 重启 | +| `agentsd reload ` | `systemctl reload` | 热更新 | +| `agentsd status` | `systemctl status` | 状态查询 | +| `agentsd list` | `systemctl list-units` | 列出所有插件 | +| `agentsd logs ` | `journalctl -u` | 查日志 | +| `agentsd enable ` | `systemctl enable` | 开机自启 | +| `agentsd disable ` | `systemctl disable` | 取消自启 | + +--- + +## 加载顺序(= systemd boot) + +``` +1. 系统插件(FFI,注册 syscall 实现) ← 类比内核模块加载 +2. 基础插件(gRPC,任意顺序) ← 类比 basic.target +3. 标准插件(gRPC,拓扑排序) ← 类比 multi-user.target +``` + +--- + +## 看门狗(= Restart=always) + +插件崩溃 → agentsd 自动重启(可配置策略): +- `always`:总是重启 +- `on-failure`:异常退出才重启 +- `never`:不重启 + +--- + +## 权限(= seccomp + cgroup) + +- 系统插件:内核信任,无限制 +- 基础插件:按 `syscalls` 白名单精确到方法(如 `fs.list`)校验,也支持 `fs` / `fs.*` / `*` 授权(= seccomp filter) +- 标准插件:syscalls + depends 白名单,未声明的一律拒绝 diff --git a/docs/03-实施路线.md b/docs/03-实施路线.md new file mode 100644 index 0000000..c4342df --- /dev/null +++ b/docs/03-实施路线.md @@ -0,0 +1,50 @@ +# 03 - 实施路线 + +## 阶段 1:内核骨架(= 跑起来的 PID 1) ✅ 已完成 + +- [x] Rust workspace:`agentsd` 主二进制 +- [x] gRPC server(tonic over TCP,后续切 UDS) +- [x] 实现 FS syscall(read/write/delete/rename/stat/list) +- [x] 实现 Memory syscall(SQLite KV + FTS5 trigram search,默认写入 data/memory.db) +- [x] 实现 Input syscall(stdin) +- [x] 实现 Display syscall(stdout) +- [x] 实现 Process syscall(spawn/kill/wait/stdin/stdout) +- [x] 实现 Timer syscall(once/cancel/now + 回调事件通道) +- [x] 实现 Audio syscall(stub,待系统插件) +- [x] 实现 HID syscall(stub,待系统插件) +- [x] 实现 Network syscall(HTTP via curl,待替换) +- [x] Plugin Bridge(注册/调用路由/心跳,注册状态写入 data/plugins.json) +- [x] 插件加载:gRPC 握手 + 注册 +- [x] echo 测试插件(Python)验证基础链路 +- [x] ai_test 手动验证插件(Python,默认 disabled/autoload false)覆盖 Display/Memory/FS/Process/Timer/权限拒绝 +- [x] proto 定义:完整 9 个 service + PluginBridge + +**验证结果**:agentsd 启动 → Python 插件通过 gRPC 注册 → ai_test 调用 Display/Memory/FS/Process/Timer syscall 并验证权限拒绝。 + +## 阶段 2:完善核心 syscall + +- [ ] Network:替换 curl 为 hyper/reqwest 原生 HTTP client +- [ ] FS.Watch:接入 notify-rs 文件监听 +- [ ] Timer.Cron:接入 cron 表达式解析 +- [ ] Audio:接入系统音频后端(wasapi/alsa) +- [ ] HID:接入屏幕截取(Windows: win32 API) +- [ ] Unix socket / Named pipe 传输(替代 TCP) + +## 阶段 3:接入 Agent + +- [ ] Agent 插件:Input → LLM(Network) → Display +- [ ] Hermes adapter(Python gRPC 插件) +- [ ] 本地模型插件(llama.cpp via Process syscall) + +## 阶段 4:插件管理(= systemctl) + +- [ ] agentsd start / stop / restart / status / list / logs +- [ ] 看门狗(崩溃自动重启) +- [ ] 依赖解析 + 拓扑排序 +- [ ] install / uninstall / enable / disable + +## 阶段 5:生态 + +- [ ] Web UI 插件(shell) +- [ ] 语音交互(Audio + TTS/STT 插件) +- [ ] 桌面自动化(HID + computer-use 插件) diff --git a/docs/04-项目结构.md b/docs/04-项目结构.md new file mode 100644 index 0000000..548497b --- /dev/null +++ b/docs/04-项目结构.md @@ -0,0 +1,117 @@ +# 04 - 项目结构 + +``` +Agentsd/ +├── Cargo.toml # workspace 根 +├── README.md # 设计总览 +│ +├── docs/ # 设计文档 +│ ├── 01-内核系统调用.md +│ ├── 02-插件模型.md +│ ├── 03-实施路线.md +│ └── 04-项目结构.md # 本文件 +│ +├── proto/ # 协议定义(单一事实来源) +│ └── agentsd.proto # 9 service + PluginBridge +│ +├── core/ # Rust 源码 +│ ├── proto/ # gRPC 协议 crate +│ │ ├── Cargo.toml +│ │ ├── build.rs +│ │ └── src/lib.rs +│ │ +│ └── agentsd/ # 主二进制 crate +│ ├── Cargo.toml +│ └── src/ +│ ├── main.rs # 入口 +│ ├── server.rs # gRPC server +│ ├── plugin.rs # 插件注册表 + data/plugins.json 持久化 +│ ├── paths.rs # 程序目录 data/ 路径 +│ ├── callback.rs # Timer 回调事件通道 +│ ├── permission.rs # syscall 权限白名单校验 +│ └── syscall/ # 9 个 syscall 模块 +│ ├── mod.rs +│ ├── display.rs +│ ├── audio.rs +│ ├── fs.rs +│ ├── memory.rs +│ ├── network.rs +│ ├── process.rs +│ ├── timer.rs +│ ├── hid.rs +│ └── input.rs +│ +├── data/ # 运行时数据(程序启动后自动创建) +│ ├── memory.db # Memory syscall SQLite 数据库 +│ └── plugins.json # 插件注册状态 +│ +└── plugins/ # 插件(任意语言) + ├── _sdk/ # 共享 Python gRPC stub + │ ├── __init__.py + │ ├── agentsd_pb2.py + │ └── agentsd_pb2_grpc.py + │ + ├── echo/ # 测试插件 + │ ├── plugin.yaml + │ └── echo_plugin.py + │ + ├── ai_test/ # AI 手动验证插件(enabled=false,autoload=false) + │ ├── plugin.yaml + │ └── ai_test_plugin.py + │ + ├── hermes/ # Hermes Agent 桥接(63 tool) + │ ├── plugin.yaml + │ └── hermes_plugin.py + │ + └── claudecode/ # Claude Code CLI 桥接 + ├── plugin.yaml + ├── claudecode_plugin.py + └── test_chat.py +``` + +## 布局原则 + +| 目录 | 职责 | 规则 | +|---|---|---| +| `docs/` | 设计文档 | 只放 .md | +| `proto/` | 协议定义 | 只放 .proto,单一事实来源 | +| `core/` | Rust 代码 | workspace members | +| `data/` | 运行时数据 | 默认路径为程序所在目录的 data 文件夹 | +| `plugins/` | 所有插件 | 每个插件一个目录,含 plugin.yaml | +| `plugins/_sdk/` | 共享 stub | 生成一次,插件 import 引用,不复制 | + +## 构建 + +```bash +cargo build --release # 约 6.1MB 单二进制 +``` + +## 运行 + +```bash +# 启动内核 +./target/release/agentsd.exe + +# 启动插件(各开一个终端) +python plugins/hermes/hermes_plugin.py +python plugins/claudecode/claudecode_plugin.py +``` + +## 手动验证插件 + +`plugins/ai_test` 是专门给 AI/人工做端到端验证的插件,`plugin.yaml` 中 `enabled: false`、`autoload: false`,默认不应被自动加载。 + +```bash +RUST_LOG=agentsd=info ./target/release/agentsd.exe +python plugins/ai_test/ai_test_plugin.py +``` + +## 重新生成 SDK + +```bash +python -m grpc_tools.protoc \ + -Iproto \ + --python_out=plugins/_sdk \ + --grpc_python_out=plugins/_sdk \ + proto/agentsd.proto +``` diff --git a/plugins/_sdk/__init__.py b/plugins/_sdk/__init__.py new file mode 100644 index 0000000..377e152 --- /dev/null +++ b/plugins/_sdk/__init__.py @@ -0,0 +1 @@ +# Agentsd Python SDK - shared gRPC stubs for plugins diff --git a/plugins/_sdk/agentsd_pb2.py b/plugins/_sdk/agentsd_pb2.py new file mode 100644 index 0000000..3bf1d10 --- /dev/null +++ b/plugins/_sdk/agentsd_pb2.py @@ -0,0 +1,210 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: agentsd.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'agentsd.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\ragentsd.proto\x12\x07\x61gentsd\"%\n\x12\x44isplayTextRequest\x12\x0f\n\x07\x63ontent\x18\x01 \x01(\t\"4\n\x12\x44isplayRichRequest\x12\x0e\n\x06markup\x18\x01 \x01(\t\x12\x0e\n\x06\x66ormat\x18\x02 \x01(\t\"3\n\x13\x44isplayImageRequest\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12\x0e\n\x06\x66ormat\x18\x02 \x01(\t\"\'\n\x14\x44isplayNotifyRequest\x12\x0f\n\x07message\x18\x01 \x01(\t\",\n\x0f\x44isplayResponse\x12\n\n\x02ok\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"*\n\nAudioChunk\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12\x0e\n\x06\x66ormat\x18\x02 \x01(\t\"\x1e\n\rVolumeRequest\x12\r\n\x05level\x18\x01 \x01(\x02\"$\n\x12RecordStartRequest\x12\x0e\n\x06\x66ormat\x18\x01 \x01(\t\")\n\tAudioData\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12\x0e\n\x06\x66ormat\x18\x02 \x01(\t\"*\n\rAudioResponse\x12\n\n\x02ok\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"\x1d\n\rFsPathRequest\x12\x0c\n\x04path\x18\x01 \x01(\t\"=\n\rFsReadRequest\x12\x0c\n\x04path\x18\x01 \x01(\t\x12\x0e\n\x06offset\x18\x02 \x01(\x03\x12\x0e\n\x06length\x18\x03 \x01(\x03\"9\n\x0e\x46sReadResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12\n\n\x02ok\x18\x02 \x01(\x08\x12\r\n\x05\x65rror\x18\x03 \x01(\t\"<\n\x0e\x46sWriteRequest\x12\x0c\n\x04path\x18\x01 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\x0e\n\x06\x61ppend\x18\x03 \x01(\x08\"+\n\x0f\x46sRenameRequest\x12\x0c\n\x04\x66rom\x18\x01 \x01(\t\x12\n\n\x02to\x18\x02 \x01(\t\"\x92\x01\n\x0e\x46sStatResponse\x12\n\n\x02ok\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12\x0c\n\x04size\x18\x03 \x01(\x03\x12\x0e\n\x06is_dir\x18\x04 \x01(\x08\x12\x0f\n\x07is_file\x18\x05 \x01(\x08\x12\x10\n\x08modified\x18\x06 \x01(\x03\x12\x0f\n\x07\x63reated\x18\x07 \x01(\x03\x12\x13\n\x0bpermissions\x18\x08 \x01(\r\"N\n\x0e\x46sListResponse\x12\n\n\x02ok\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12!\n\x07\x65ntries\x18\x03 \x03(\x0b\x32\x10.agentsd.FsEntry\"5\n\x07\x46sEntry\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0e\n\x06is_dir\x18\x02 \x01(\x08\x12\x0c\n\x04size\x18\x03 \x01(\x03\"*\n\x0c\x46sWatchEvent\x12\x0c\n\x04path\x18\x01 \x01(\t\x12\x0c\n\x04kind\x18\x02 \x01(\t\"\'\n\nFsResponse\x12\n\n\x02ok\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"\x1f\n\x10MemoryKeyRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\" \n\x11MemoryReadRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\"A\n\x12MemoryReadResponse\x12\r\n\x05value\x18\x01 \x01(\x0c\x12\r\n\x05\x66ound\x18\x02 \x01(\x08\x12\r\n\x05\x65rror\x18\x03 \x01(\t\"0\n\x12MemoryWriteRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\"2\n\x11MemoryListRequest\x12\x0e\n\x06prefix\x18\x01 \x01(\t\x12\r\n\x05limit\x18\x02 \x01(\x05\"1\n\x12MemoryListResponse\x12\x0c\n\x04keys\x18\x01 \x03(\t\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"3\n\x13MemorySearchRequest\x12\r\n\x05query\x18\x01 \x01(\t\x12\r\n\x05limit\x18\x02 \x01(\x05\"M\n\x14MemorySearchResponse\x12&\n\x04hits\x18\x01 \x03(\x0b\x32\x18.agentsd.MemorySearchHit\x12\r\n\x05\x65rror\x18\x02 \x01(\t\">\n\x0fMemorySearchHit\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x0f\n\x07snippet\x18\x02 \x01(\t\x12\r\n\x05score\x18\x03 \x01(\x02\"+\n\x0eMemoryResponse\x12\n\n\x02ok\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"\x9c\x01\n\x0bHttpRequest\x12\x0b\n\x03url\x18\x01 \x01(\t\x12\x0e\n\x06method\x18\x02 \x01(\t\x12\x32\n\x07headers\x18\x03 \x03(\x0b\x32!.agentsd.HttpRequest.HeadersEntry\x12\x0c\n\x04\x62ody\x18\x04 \x01(\x0c\x1a.\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xa0\x01\n\x0cHttpResponse\x12\x0e\n\x06status\x18\x01 \x01(\x05\x12\x33\n\x07headers\x18\x02 \x03(\x0b\x32\".agentsd.HttpResponse.HeadersEntry\x12\x0c\n\x04\x62ody\x18\x03 \x01(\x0c\x12\r\n\x05\x65rror\x18\x04 \x01(\t\x1a.\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"0\n\x0e\x43onnectRequest\x12\x0c\n\x04\x61\x64\x64r\x18\x01 \x01(\t\x12\x10\n\x08protocol\x18\x02 \x01(\t\"/\n\rListenRequest\x12\x0c\n\x04port\x18\x01 \x01(\r\x12\x10\n\x08protocol\x18\x02 \x01(\t\"8\n\tConnEvent\x12\x0f\n\x07\x63onn_id\x18\x01 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\x0c\n\x04kind\x18\x03 \x01(\t\",\n\x0bSendRequest\x12\x0f\n\x07\x63onn_id\x18\x01 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\"\x1f\n\x0c\x43loseRequest\x12\x0f\n\x07\x63onn_id\x18\x01 \x01(\t\"\x19\n\tDataChunk\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"(\n\x0bNetResponse\x12\n\n\x02ok\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\")\n\x14NetAvailableResponse\x12\x11\n\tavailable\x18\x01 \x01(\x08\"\x8f\x01\n\x0cSpawnRequest\x12\x0b\n\x03\x63md\x18\x01 \x01(\t\x12\x0c\n\x04\x61rgs\x18\x02 \x03(\t\x12+\n\x03\x65nv\x18\x03 \x03(\x0b\x32\x1e.agentsd.SpawnRequest.EnvEntry\x12\x0b\n\x03\x63wd\x18\x04 \x01(\t\x1a*\n\x08\x45nvEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"7\n\rSpawnResponse\x12\x0b\n\x03pid\x18\x01 \x01(\x04\x12\n\n\x02ok\x18\x02 \x01(\x08\x12\r\n\x05\x65rror\x18\x03 \x01(\t\"\x19\n\nPidRequest\x12\x0b\n\x03pid\x18\x01 \x01(\x04\"<\n\x0cWaitResponse\x12\x11\n\texit_code\x18\x01 \x01(\x05\x12\n\n\x02ok\x18\x02 \x01(\x08\x12\r\n\x05\x65rror\x18\x03 \x01(\t\")\n\x0cStdinRequest\x12\x0b\n\x03pid\x18\x01 \x01(\x04\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\",\n\rSignalRequest\x12\x0b\n\x03pid\x18\x01 \x01(\x04\x12\x0e\n\x06signal\x18\x02 \x01(\x05\")\n\x0cProcResponse\x12\n\n\x02ok\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"4\n\x10ProcListResponse\x12 \n\x05procs\x18\x01 \x03(\x0b\x32\x11.agentsd.ProcInfo\"4\n\x08ProcInfo\x12\x0b\n\x03pid\x18\x01 \x01(\x04\x12\x0b\n\x03\x63md\x18\x02 \x01(\t\x12\x0e\n\x06status\x18\x03 \x01(\t\"9\n\x10TimerOnceRequest\x12\x10\n\x08\x64\x65lay_ms\x18\x01 \x01(\x04\x12\x13\n\x0b\x63\x61llback_id\x18\x02 \x01(\t\"5\n\x10TimerCronRequest\x12\x0c\n\x04\x65xpr\x18\x01 \x01(\t\x12\x13\n\x0b\x63\x61llback_id\x18\x02 \x01(\t\"&\n\x12TimerCancelRequest\x12\x10\n\x08timer_id\x18\x01 \x01(\t\"<\n\rTimerResponse\x12\x10\n\x08timer_id\x18\x01 \x01(\t\x12\n\n\x02ok\x18\x02 \x01(\x08\x12\r\n\x05\x65rror\x18\x03 \x01(\t\"(\n\x10TimerNowResponse\x12\x14\n\x0ctimestamp_ms\x18\x01 \x01(\x03\"E\n\x0e\x43\x61ptureRequest\x12\t\n\x01x\x18\x01 \x01(\x05\x12\t\n\x01y\x18\x02 \x01(\x05\x12\r\n\x05width\x18\x03 \x01(\x05\x12\x0e\n\x06height\x18\x04 \x01(\x05\"K\n\x0f\x43\x61ptureResponse\x12\r\n\x05image\x18\x01 \x01(\x0c\x12\x0e\n\x06\x66ormat\x18\x02 \x01(\t\x12\n\n\x02ok\x18\x03 \x01(\x08\x12\r\n\x05\x65rror\x18\x04 \x01(\t\"D\n\x0cMouseRequest\x12\t\n\x01x\x18\x01 \x01(\x05\x12\t\n\x01y\x18\x02 \x01(\x05\x12\x0e\n\x06\x61\x63tion\x18\x03 \x01(\t\x12\x0e\n\x06\x62utton\x18\x04 \x01(\t\"A\n\x0fKeyboardRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x0e\n\x06\x61\x63tion\x18\x02 \x01(\t\x12\x11\n\tmodifiers\x18\x03 \x03(\t\"(\n\x0bHidResponse\x12\n\n\x02ok\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"6\n\x0bOcrResponse\x12\x0c\n\x04text\x18\x01 \x01(\t\x12\n\n\x02ok\x18\x02 \x01(\x08\x12\r\n\x05\x65rror\x18\x03 \x01(\t\"<\n\x11InputTextResponse\x12\x0c\n\x04text\x18\x01 \x01(\t\x12\n\n\x02ok\x18\x02 \x01(\x08\x12\r\n\x05\x65rror\x18\x03 \x01(\t\"B\n\x10InputKeyResponse\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x0e\n\x06\x61\x63tion\x18\x02 \x01(\t\x12\x11\n\tmodifiers\x18\x03 \x03(\t\"O\n\x16InputClipboardResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12\x0c\n\x04mime\x18\x02 \x01(\t\x12\n\n\x02ok\x18\x03 \x01(\x08\x12\r\n\x05\x65rror\x18\x04 \x01(\t\"(\n\nInputEvent\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\"\x94\x01\n\nPluginInfo\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0f\n\x07version\x18\x03 \x01(\t\x12\x13\n\x0bplugin_type\x18\x04 \x01(\t\x12\x10\n\x08syscalls\x18\x05 \x03(\t\x12\x0f\n\x07\x64\x65pends\x18\x06 \x03(\t\x12#\n\x07\x65xports\x18\x07 \x03(\x0b\x32\x12.agentsd.ExportDef\".\n\tExportDef\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\"A\n\x10RegisterResponse\x12\n\n\x02ok\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12\x12\n\nsession_id\x18\x03 \x01(\t\"7\n\x0b\x43\x61llRequest\x12\x0e\n\x06target\x18\x01 \x01(\t\x12\n\n\x02\x66n\x18\x02 \x01(\t\x12\x0c\n\x04\x61rgs\x18\x03 \x01(\x0c\"9\n\x0c\x43\x61llResponse\x12\x0e\n\x06result\x18\x01 \x01(\x0c\x12\n\n\x02ok\x18\x02 \x01(\x08\x12\r\n\x05\x65rror\x18\x03 \x01(\t\"%\n\x10HeartbeatRequest\x12\x11\n\tplugin_id\x18\x01 \x01(\t\"\x1f\n\x11HeartbeatResponse\x12\n\n\x02ok\x18\x01 \x01(\x08\"\x07\n\x05\x45mpty2\xbe\x02\n\x07\x44isplay\x12=\n\x04Text\x12\x1b.agentsd.DisplayTextRequest\x1a\x18.agentsd.DisplayResponse\x12=\n\x04Rich\x12\x1b.agentsd.DisplayRichRequest\x1a\x18.agentsd.DisplayResponse\x12?\n\x05Image\x12\x1c.agentsd.DisplayImageRequest\x1a\x18.agentsd.DisplayResponse\x12\x31\n\x05\x43lear\x12\x0e.agentsd.Empty\x1a\x18.agentsd.DisplayResponse\x12\x41\n\x06Notify\x12\x1d.agentsd.DisplayNotifyRequest\x1a\x18.agentsd.DisplayResponse2\xe2\x02\n\x05\x41udio\x12\x35\n\x04Play\x12\x13.agentsd.AudioChunk\x1a\x16.agentsd.AudioResponse(\x01\x12.\n\x04Stop\x12\x0e.agentsd.Empty\x1a\x16.agentsd.AudioResponse\x12\x38\n\x06Volume\x12\x16.agentsd.VolumeRequest\x1a\x16.agentsd.AudioResponse\x12\x42\n\x0bRecordStart\x12\x1b.agentsd.RecordStartRequest\x1a\x16.agentsd.AudioResponse\x12\x30\n\nRecordStop\x12\x0e.agentsd.Empty\x1a\x12.agentsd.AudioData\x12\x42\n\x0cRecordStream\x12\x1b.agentsd.RecordStartRequest\x1a\x13.agentsd.AudioChunk0\x01\x32\x90\x03\n\x02\x46S\x12\x37\n\x04Read\x12\x16.agentsd.FsReadRequest\x1a\x17.agentsd.FsReadResponse\x12\x35\n\x05Write\x12\x17.agentsd.FsWriteRequest\x1a\x13.agentsd.FsResponse\x12\x35\n\x06\x44\x65lete\x12\x16.agentsd.FsPathRequest\x1a\x13.agentsd.FsResponse\x12\x37\n\x06Rename\x12\x18.agentsd.FsRenameRequest\x1a\x13.agentsd.FsResponse\x12\x37\n\x04Stat\x12\x16.agentsd.FsPathRequest\x1a\x17.agentsd.FsStatResponse\x12\x37\n\x04List\x12\x16.agentsd.FsPathRequest\x1a\x17.agentsd.FsListResponse\x12\x38\n\x05Watch\x12\x16.agentsd.FsPathRequest\x1a\x15.agentsd.FsWatchEvent0\x01\x32\xce\x02\n\x06Memory\x12?\n\x04Read\x12\x1a.agentsd.MemoryReadRequest\x1a\x1b.agentsd.MemoryReadResponse\x12=\n\x05Write\x12\x1b.agentsd.MemoryWriteRequest\x1a\x17.agentsd.MemoryResponse\x12<\n\x06\x44\x65lete\x12\x19.agentsd.MemoryKeyRequest\x1a\x17.agentsd.MemoryResponse\x12?\n\x04List\x12\x1a.agentsd.MemoryListRequest\x1a\x1b.agentsd.MemoryListResponse\x12\x45\n\x06Search\x12\x1c.agentsd.MemorySearchRequest\x1a\x1d.agentsd.MemorySearchResponse2\xda\x02\n\x07Network\x12\x36\n\x07Request\x12\x14.agentsd.HttpRequest\x1a\x15.agentsd.HttpResponse\x12\x39\n\x08OpenConn\x12\x17.agentsd.ConnectRequest\x1a\x12.agentsd.DataChunk0\x01\x12\x36\n\x06Listen\x12\x16.agentsd.ListenRequest\x1a\x12.agentsd.ConnEvent0\x01\x12\x32\n\x04Send\x12\x14.agentsd.SendRequest\x1a\x14.agentsd.NetResponse\x12\x34\n\x05\x43lose\x12\x15.agentsd.CloseRequest\x1a\x14.agentsd.NetResponse\x12:\n\tAvailable\x12\x0e.agentsd.Empty\x1a\x1d.agentsd.NetAvailableResponse2\x85\x03\n\x07Process\x12\x36\n\x05Spawn\x12\x15.agentsd.SpawnRequest\x1a\x16.agentsd.SpawnResponse\x12\x32\n\x04Kill\x12\x13.agentsd.PidRequest\x1a\x15.agentsd.ProcResponse\x12\x32\n\x04Wait\x12\x13.agentsd.PidRequest\x1a\x15.agentsd.WaitResponse\x12\x35\n\x05Stdin\x12\x15.agentsd.StdinRequest\x1a\x15.agentsd.ProcResponse\x12\x33\n\x06Stdout\x12\x13.agentsd.PidRequest\x1a\x12.agentsd.DataChunk0\x01\x12\x37\n\x06Signal\x12\x16.agentsd.SignalRequest\x1a\x15.agentsd.ProcResponse\x12\x35\n\x08ListProc\x12\x0e.agentsd.Empty\x1a\x19.agentsd.ProcListResponse2\xee\x01\n\x05Timer\x12\x39\n\x04Once\x12\x19.agentsd.TimerOnceRequest\x1a\x16.agentsd.TimerResponse\x12\x39\n\x04\x43ron\x12\x19.agentsd.TimerCronRequest\x1a\x16.agentsd.TimerResponse\x12=\n\x06\x43\x61ncel\x12\x1b.agentsd.TimerCancelRequest\x1a\x16.agentsd.TimerResponse\x12\x30\n\x03Now\x12\x0e.agentsd.Empty\x1a\x19.agentsd.TimerNowResponse2\xeb\x01\n\x03HID\x12<\n\x07\x43\x61pture\x12\x17.agentsd.CaptureRequest\x1a\x18.agentsd.CaptureResponse\x12\x34\n\x05Mouse\x12\x15.agentsd.MouseRequest\x1a\x14.agentsd.HidResponse\x12:\n\x08Keyboard\x12\x18.agentsd.KeyboardRequest\x1a\x14.agentsd.HidResponse\x12\x34\n\x03Ocr\x12\x17.agentsd.CaptureRequest\x1a\x14.agentsd.OcrResponse2\xdc\x01\n\x05Input\x12\x32\n\x04Text\x12\x0e.agentsd.Empty\x1a\x1a.agentsd.InputTextResponse\x12\x30\n\x03Key\x12\x0e.agentsd.Empty\x1a\x19.agentsd.InputKeyResponse\x12<\n\tClipboard\x12\x0e.agentsd.Empty\x1a\x1f.agentsd.InputClipboardResponse\x12/\n\x06\x45vents\x12\x0e.agentsd.Empty\x1a\x13.agentsd.InputEvent0\x01\x32\x80\x02\n\x0cPluginBridge\x12:\n\x08Register\x12\x13.agentsd.PluginInfo\x1a\x19.agentsd.RegisterResponse\x12\x33\n\x04\x43\x61ll\x12\x14.agentsd.CallRequest\x1a\x15.agentsd.CallResponse\x12;\n\nStreamCall\x12\x14.agentsd.CallRequest\x1a\x15.agentsd.CallResponse0\x01\x12\x42\n\tHeartbeat\x12\x19.agentsd.HeartbeatRequest\x1a\x1a.agentsd.HeartbeatResponseb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'agentsd_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_HTTPREQUEST_HEADERSENTRY']._loaded_options = None + _globals['_HTTPREQUEST_HEADERSENTRY']._serialized_options = b'8\001' + _globals['_HTTPRESPONSE_HEADERSENTRY']._loaded_options = None + _globals['_HTTPRESPONSE_HEADERSENTRY']._serialized_options = b'8\001' + _globals['_SPAWNREQUEST_ENVENTRY']._loaded_options = None + _globals['_SPAWNREQUEST_ENVENTRY']._serialized_options = b'8\001' + _globals['_DISPLAYTEXTREQUEST']._serialized_start=26 + _globals['_DISPLAYTEXTREQUEST']._serialized_end=63 + _globals['_DISPLAYRICHREQUEST']._serialized_start=65 + _globals['_DISPLAYRICHREQUEST']._serialized_end=117 + _globals['_DISPLAYIMAGEREQUEST']._serialized_start=119 + _globals['_DISPLAYIMAGEREQUEST']._serialized_end=170 + _globals['_DISPLAYNOTIFYREQUEST']._serialized_start=172 + _globals['_DISPLAYNOTIFYREQUEST']._serialized_end=211 + _globals['_DISPLAYRESPONSE']._serialized_start=213 + _globals['_DISPLAYRESPONSE']._serialized_end=257 + _globals['_AUDIOCHUNK']._serialized_start=259 + _globals['_AUDIOCHUNK']._serialized_end=301 + _globals['_VOLUMEREQUEST']._serialized_start=303 + _globals['_VOLUMEREQUEST']._serialized_end=333 + _globals['_RECORDSTARTREQUEST']._serialized_start=335 + _globals['_RECORDSTARTREQUEST']._serialized_end=371 + _globals['_AUDIODATA']._serialized_start=373 + _globals['_AUDIODATA']._serialized_end=414 + _globals['_AUDIORESPONSE']._serialized_start=416 + _globals['_AUDIORESPONSE']._serialized_end=458 + _globals['_FSPATHREQUEST']._serialized_start=460 + _globals['_FSPATHREQUEST']._serialized_end=489 + _globals['_FSREADREQUEST']._serialized_start=491 + _globals['_FSREADREQUEST']._serialized_end=552 + _globals['_FSREADRESPONSE']._serialized_start=554 + _globals['_FSREADRESPONSE']._serialized_end=611 + _globals['_FSWRITEREQUEST']._serialized_start=613 + _globals['_FSWRITEREQUEST']._serialized_end=673 + _globals['_FSRENAMEREQUEST']._serialized_start=675 + _globals['_FSRENAMEREQUEST']._serialized_end=718 + _globals['_FSSTATRESPONSE']._serialized_start=721 + _globals['_FSSTATRESPONSE']._serialized_end=867 + _globals['_FSLISTRESPONSE']._serialized_start=869 + _globals['_FSLISTRESPONSE']._serialized_end=947 + _globals['_FSENTRY']._serialized_start=949 + _globals['_FSENTRY']._serialized_end=1002 + _globals['_FSWATCHEVENT']._serialized_start=1004 + _globals['_FSWATCHEVENT']._serialized_end=1046 + _globals['_FSRESPONSE']._serialized_start=1048 + _globals['_FSRESPONSE']._serialized_end=1087 + _globals['_MEMORYKEYREQUEST']._serialized_start=1089 + _globals['_MEMORYKEYREQUEST']._serialized_end=1120 + _globals['_MEMORYREADREQUEST']._serialized_start=1122 + _globals['_MEMORYREADREQUEST']._serialized_end=1154 + _globals['_MEMORYREADRESPONSE']._serialized_start=1156 + _globals['_MEMORYREADRESPONSE']._serialized_end=1221 + _globals['_MEMORYWRITEREQUEST']._serialized_start=1223 + _globals['_MEMORYWRITEREQUEST']._serialized_end=1271 + _globals['_MEMORYLISTREQUEST']._serialized_start=1273 + _globals['_MEMORYLISTREQUEST']._serialized_end=1323 + _globals['_MEMORYLISTRESPONSE']._serialized_start=1325 + _globals['_MEMORYLISTRESPONSE']._serialized_end=1374 + _globals['_MEMORYSEARCHREQUEST']._serialized_start=1376 + _globals['_MEMORYSEARCHREQUEST']._serialized_end=1427 + _globals['_MEMORYSEARCHRESPONSE']._serialized_start=1429 + _globals['_MEMORYSEARCHRESPONSE']._serialized_end=1506 + _globals['_MEMORYSEARCHHIT']._serialized_start=1508 + _globals['_MEMORYSEARCHHIT']._serialized_end=1570 + _globals['_MEMORYRESPONSE']._serialized_start=1572 + _globals['_MEMORYRESPONSE']._serialized_end=1615 + _globals['_HTTPREQUEST']._serialized_start=1618 + _globals['_HTTPREQUEST']._serialized_end=1774 + _globals['_HTTPREQUEST_HEADERSENTRY']._serialized_start=1728 + _globals['_HTTPREQUEST_HEADERSENTRY']._serialized_end=1774 + _globals['_HTTPRESPONSE']._serialized_start=1777 + _globals['_HTTPRESPONSE']._serialized_end=1937 + _globals['_HTTPRESPONSE_HEADERSENTRY']._serialized_start=1728 + _globals['_HTTPRESPONSE_HEADERSENTRY']._serialized_end=1774 + _globals['_CONNECTREQUEST']._serialized_start=1939 + _globals['_CONNECTREQUEST']._serialized_end=1987 + _globals['_LISTENREQUEST']._serialized_start=1989 + _globals['_LISTENREQUEST']._serialized_end=2036 + _globals['_CONNEVENT']._serialized_start=2038 + _globals['_CONNEVENT']._serialized_end=2094 + _globals['_SENDREQUEST']._serialized_start=2096 + _globals['_SENDREQUEST']._serialized_end=2140 + _globals['_CLOSEREQUEST']._serialized_start=2142 + _globals['_CLOSEREQUEST']._serialized_end=2173 + _globals['_DATACHUNK']._serialized_start=2175 + _globals['_DATACHUNK']._serialized_end=2200 + _globals['_NETRESPONSE']._serialized_start=2202 + _globals['_NETRESPONSE']._serialized_end=2242 + _globals['_NETAVAILABLERESPONSE']._serialized_start=2244 + _globals['_NETAVAILABLERESPONSE']._serialized_end=2285 + _globals['_SPAWNREQUEST']._serialized_start=2288 + _globals['_SPAWNREQUEST']._serialized_end=2431 + _globals['_SPAWNREQUEST_ENVENTRY']._serialized_start=2389 + _globals['_SPAWNREQUEST_ENVENTRY']._serialized_end=2431 + _globals['_SPAWNRESPONSE']._serialized_start=2433 + _globals['_SPAWNRESPONSE']._serialized_end=2488 + _globals['_PIDREQUEST']._serialized_start=2490 + _globals['_PIDREQUEST']._serialized_end=2515 + _globals['_WAITRESPONSE']._serialized_start=2517 + _globals['_WAITRESPONSE']._serialized_end=2577 + _globals['_STDINREQUEST']._serialized_start=2579 + _globals['_STDINREQUEST']._serialized_end=2620 + _globals['_SIGNALREQUEST']._serialized_start=2622 + _globals['_SIGNALREQUEST']._serialized_end=2666 + _globals['_PROCRESPONSE']._serialized_start=2668 + _globals['_PROCRESPONSE']._serialized_end=2709 + _globals['_PROCLISTRESPONSE']._serialized_start=2711 + _globals['_PROCLISTRESPONSE']._serialized_end=2763 + _globals['_PROCINFO']._serialized_start=2765 + _globals['_PROCINFO']._serialized_end=2817 + _globals['_TIMERONCEREQUEST']._serialized_start=2819 + _globals['_TIMERONCEREQUEST']._serialized_end=2876 + _globals['_TIMERCRONREQUEST']._serialized_start=2878 + _globals['_TIMERCRONREQUEST']._serialized_end=2931 + _globals['_TIMERCANCELREQUEST']._serialized_start=2933 + _globals['_TIMERCANCELREQUEST']._serialized_end=2971 + _globals['_TIMERRESPONSE']._serialized_start=2973 + _globals['_TIMERRESPONSE']._serialized_end=3033 + _globals['_TIMERNOWRESPONSE']._serialized_start=3035 + _globals['_TIMERNOWRESPONSE']._serialized_end=3075 + _globals['_CAPTUREREQUEST']._serialized_start=3077 + _globals['_CAPTUREREQUEST']._serialized_end=3146 + _globals['_CAPTURERESPONSE']._serialized_start=3148 + _globals['_CAPTURERESPONSE']._serialized_end=3223 + _globals['_MOUSEREQUEST']._serialized_start=3225 + _globals['_MOUSEREQUEST']._serialized_end=3293 + _globals['_KEYBOARDREQUEST']._serialized_start=3295 + _globals['_KEYBOARDREQUEST']._serialized_end=3360 + _globals['_HIDRESPONSE']._serialized_start=3362 + _globals['_HIDRESPONSE']._serialized_end=3402 + _globals['_OCRRESPONSE']._serialized_start=3404 + _globals['_OCRRESPONSE']._serialized_end=3458 + _globals['_INPUTTEXTRESPONSE']._serialized_start=3460 + _globals['_INPUTTEXTRESPONSE']._serialized_end=3520 + _globals['_INPUTKEYRESPONSE']._serialized_start=3522 + _globals['_INPUTKEYRESPONSE']._serialized_end=3588 + _globals['_INPUTCLIPBOARDRESPONSE']._serialized_start=3590 + _globals['_INPUTCLIPBOARDRESPONSE']._serialized_end=3669 + _globals['_INPUTEVENT']._serialized_start=3671 + _globals['_INPUTEVENT']._serialized_end=3711 + _globals['_PLUGININFO']._serialized_start=3714 + _globals['_PLUGININFO']._serialized_end=3862 + _globals['_EXPORTDEF']._serialized_start=3864 + _globals['_EXPORTDEF']._serialized_end=3910 + _globals['_REGISTERRESPONSE']._serialized_start=3912 + _globals['_REGISTERRESPONSE']._serialized_end=3977 + _globals['_CALLREQUEST']._serialized_start=3979 + _globals['_CALLREQUEST']._serialized_end=4034 + _globals['_CALLRESPONSE']._serialized_start=4036 + _globals['_CALLRESPONSE']._serialized_end=4093 + _globals['_HEARTBEATREQUEST']._serialized_start=4095 + _globals['_HEARTBEATREQUEST']._serialized_end=4132 + _globals['_HEARTBEATRESPONSE']._serialized_start=4134 + _globals['_HEARTBEATRESPONSE']._serialized_end=4165 + _globals['_EMPTY']._serialized_start=4167 + _globals['_EMPTY']._serialized_end=4174 + _globals['_DISPLAY']._serialized_start=4177 + _globals['_DISPLAY']._serialized_end=4495 + _globals['_AUDIO']._serialized_start=4498 + _globals['_AUDIO']._serialized_end=4852 + _globals['_FS']._serialized_start=4855 + _globals['_FS']._serialized_end=5255 + _globals['_MEMORY']._serialized_start=5258 + _globals['_MEMORY']._serialized_end=5592 + _globals['_NETWORK']._serialized_start=5595 + _globals['_NETWORK']._serialized_end=5941 + _globals['_PROCESS']._serialized_start=5944 + _globals['_PROCESS']._serialized_end=6333 + _globals['_TIMER']._serialized_start=6336 + _globals['_TIMER']._serialized_end=6574 + _globals['_HID']._serialized_start=6577 + _globals['_HID']._serialized_end=6812 + _globals['_INPUT']._serialized_start=6815 + _globals['_INPUT']._serialized_end=7035 + _globals['_PLUGINBRIDGE']._serialized_start=7038 + _globals['_PLUGINBRIDGE']._serialized_end=7294 +# @@protoc_insertion_point(module_scope) diff --git a/plugins/_sdk/agentsd_pb2_grpc.py b/plugins/_sdk/agentsd_pb2_grpc.py new file mode 100644 index 0000000..e2ef646 --- /dev/null +++ b/plugins/_sdk/agentsd_pb2_grpc.py @@ -0,0 +1,2581 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + +import agentsd_pb2 as agentsd__pb2 + +GRPC_GENERATED_VERSION = '1.81.0' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in agentsd_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) + + +class DisplayStub: + """========== Display ========== + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.Text = channel.unary_unary( + '/agentsd.Display/Text', + request_serializer=agentsd__pb2.DisplayTextRequest.SerializeToString, + response_deserializer=agentsd__pb2.DisplayResponse.FromString, + _registered_method=True) + self.Rich = channel.unary_unary( + '/agentsd.Display/Rich', + request_serializer=agentsd__pb2.DisplayRichRequest.SerializeToString, + response_deserializer=agentsd__pb2.DisplayResponse.FromString, + _registered_method=True) + self.Image = channel.unary_unary( + '/agentsd.Display/Image', + request_serializer=agentsd__pb2.DisplayImageRequest.SerializeToString, + response_deserializer=agentsd__pb2.DisplayResponse.FromString, + _registered_method=True) + self.Clear = channel.unary_unary( + '/agentsd.Display/Clear', + request_serializer=agentsd__pb2.Empty.SerializeToString, + response_deserializer=agentsd__pb2.DisplayResponse.FromString, + _registered_method=True) + self.Notify = channel.unary_unary( + '/agentsd.Display/Notify', + request_serializer=agentsd__pb2.DisplayNotifyRequest.SerializeToString, + response_deserializer=agentsd__pb2.DisplayResponse.FromString, + _registered_method=True) + + +class DisplayServicer: + """========== Display ========== + """ + + def Text(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Rich(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Image(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Clear(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Notify(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_DisplayServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Text': grpc.unary_unary_rpc_method_handler( + servicer.Text, + request_deserializer=agentsd__pb2.DisplayTextRequest.FromString, + response_serializer=agentsd__pb2.DisplayResponse.SerializeToString, + ), + 'Rich': grpc.unary_unary_rpc_method_handler( + servicer.Rich, + request_deserializer=agentsd__pb2.DisplayRichRequest.FromString, + response_serializer=agentsd__pb2.DisplayResponse.SerializeToString, + ), + 'Image': grpc.unary_unary_rpc_method_handler( + servicer.Image, + request_deserializer=agentsd__pb2.DisplayImageRequest.FromString, + response_serializer=agentsd__pb2.DisplayResponse.SerializeToString, + ), + 'Clear': grpc.unary_unary_rpc_method_handler( + servicer.Clear, + request_deserializer=agentsd__pb2.Empty.FromString, + response_serializer=agentsd__pb2.DisplayResponse.SerializeToString, + ), + 'Notify': grpc.unary_unary_rpc_method_handler( + servicer.Notify, + request_deserializer=agentsd__pb2.DisplayNotifyRequest.FromString, + response_serializer=agentsd__pb2.DisplayResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'agentsd.Display', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('agentsd.Display', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class Display: + """========== Display ========== + """ + + @staticmethod + def Text(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Display/Text', + agentsd__pb2.DisplayTextRequest.SerializeToString, + agentsd__pb2.DisplayResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Rich(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Display/Rich', + agentsd__pb2.DisplayRichRequest.SerializeToString, + agentsd__pb2.DisplayResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Image(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Display/Image', + agentsd__pb2.DisplayImageRequest.SerializeToString, + agentsd__pb2.DisplayResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Clear(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Display/Clear', + agentsd__pb2.Empty.SerializeToString, + agentsd__pb2.DisplayResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Notify(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Display/Notify', + agentsd__pb2.DisplayNotifyRequest.SerializeToString, + agentsd__pb2.DisplayResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + +class AudioStub: + """========== Audio ========== + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.Play = channel.stream_unary( + '/agentsd.Audio/Play', + request_serializer=agentsd__pb2.AudioChunk.SerializeToString, + response_deserializer=agentsd__pb2.AudioResponse.FromString, + _registered_method=True) + self.Stop = channel.unary_unary( + '/agentsd.Audio/Stop', + request_serializer=agentsd__pb2.Empty.SerializeToString, + response_deserializer=agentsd__pb2.AudioResponse.FromString, + _registered_method=True) + self.Volume = channel.unary_unary( + '/agentsd.Audio/Volume', + request_serializer=agentsd__pb2.VolumeRequest.SerializeToString, + response_deserializer=agentsd__pb2.AudioResponse.FromString, + _registered_method=True) + self.RecordStart = channel.unary_unary( + '/agentsd.Audio/RecordStart', + request_serializer=agentsd__pb2.RecordStartRequest.SerializeToString, + response_deserializer=agentsd__pb2.AudioResponse.FromString, + _registered_method=True) + self.RecordStop = channel.unary_unary( + '/agentsd.Audio/RecordStop', + request_serializer=agentsd__pb2.Empty.SerializeToString, + response_deserializer=agentsd__pb2.AudioData.FromString, + _registered_method=True) + self.RecordStream = channel.unary_stream( + '/agentsd.Audio/RecordStream', + request_serializer=agentsd__pb2.RecordStartRequest.SerializeToString, + response_deserializer=agentsd__pb2.AudioChunk.FromString, + _registered_method=True) + + +class AudioServicer: + """========== Audio ========== + """ + + def Play(self, request_iterator, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Stop(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Volume(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def RecordStart(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def RecordStop(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def RecordStream(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_AudioServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Play': grpc.stream_unary_rpc_method_handler( + servicer.Play, + request_deserializer=agentsd__pb2.AudioChunk.FromString, + response_serializer=agentsd__pb2.AudioResponse.SerializeToString, + ), + 'Stop': grpc.unary_unary_rpc_method_handler( + servicer.Stop, + request_deserializer=agentsd__pb2.Empty.FromString, + response_serializer=agentsd__pb2.AudioResponse.SerializeToString, + ), + 'Volume': grpc.unary_unary_rpc_method_handler( + servicer.Volume, + request_deserializer=agentsd__pb2.VolumeRequest.FromString, + response_serializer=agentsd__pb2.AudioResponse.SerializeToString, + ), + 'RecordStart': grpc.unary_unary_rpc_method_handler( + servicer.RecordStart, + request_deserializer=agentsd__pb2.RecordStartRequest.FromString, + response_serializer=agentsd__pb2.AudioResponse.SerializeToString, + ), + 'RecordStop': grpc.unary_unary_rpc_method_handler( + servicer.RecordStop, + request_deserializer=agentsd__pb2.Empty.FromString, + response_serializer=agentsd__pb2.AudioData.SerializeToString, + ), + 'RecordStream': grpc.unary_stream_rpc_method_handler( + servicer.RecordStream, + request_deserializer=agentsd__pb2.RecordStartRequest.FromString, + response_serializer=agentsd__pb2.AudioChunk.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'agentsd.Audio', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('agentsd.Audio', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class Audio: + """========== Audio ========== + """ + + @staticmethod + def Play(request_iterator, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.stream_unary( + request_iterator, + target, + '/agentsd.Audio/Play', + agentsd__pb2.AudioChunk.SerializeToString, + agentsd__pb2.AudioResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Stop(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Audio/Stop', + agentsd__pb2.Empty.SerializeToString, + agentsd__pb2.AudioResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Volume(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Audio/Volume', + agentsd__pb2.VolumeRequest.SerializeToString, + agentsd__pb2.AudioResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def RecordStart(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Audio/RecordStart', + agentsd__pb2.RecordStartRequest.SerializeToString, + agentsd__pb2.AudioResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def RecordStop(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Audio/RecordStop', + agentsd__pb2.Empty.SerializeToString, + agentsd__pb2.AudioData.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def RecordStream(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_stream( + request, + target, + '/agentsd.Audio/RecordStream', + agentsd__pb2.RecordStartRequest.SerializeToString, + agentsd__pb2.AudioChunk.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + +class FSStub: + """========== FS ========== + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.Read = channel.unary_unary( + '/agentsd.FS/Read', + request_serializer=agentsd__pb2.FsReadRequest.SerializeToString, + response_deserializer=agentsd__pb2.FsReadResponse.FromString, + _registered_method=True) + self.Write = channel.unary_unary( + '/agentsd.FS/Write', + request_serializer=agentsd__pb2.FsWriteRequest.SerializeToString, + response_deserializer=agentsd__pb2.FsResponse.FromString, + _registered_method=True) + self.Delete = channel.unary_unary( + '/agentsd.FS/Delete', + request_serializer=agentsd__pb2.FsPathRequest.SerializeToString, + response_deserializer=agentsd__pb2.FsResponse.FromString, + _registered_method=True) + self.Rename = channel.unary_unary( + '/agentsd.FS/Rename', + request_serializer=agentsd__pb2.FsRenameRequest.SerializeToString, + response_deserializer=agentsd__pb2.FsResponse.FromString, + _registered_method=True) + self.Stat = channel.unary_unary( + '/agentsd.FS/Stat', + request_serializer=agentsd__pb2.FsPathRequest.SerializeToString, + response_deserializer=agentsd__pb2.FsStatResponse.FromString, + _registered_method=True) + self.List = channel.unary_unary( + '/agentsd.FS/List', + request_serializer=agentsd__pb2.FsPathRequest.SerializeToString, + response_deserializer=agentsd__pb2.FsListResponse.FromString, + _registered_method=True) + self.Watch = channel.unary_stream( + '/agentsd.FS/Watch', + request_serializer=agentsd__pb2.FsPathRequest.SerializeToString, + response_deserializer=agentsd__pb2.FsWatchEvent.FromString, + _registered_method=True) + + +class FSServicer: + """========== FS ========== + """ + + def Read(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Write(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Delete(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Rename(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Stat(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def List(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Watch(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_FSServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Read': grpc.unary_unary_rpc_method_handler( + servicer.Read, + request_deserializer=agentsd__pb2.FsReadRequest.FromString, + response_serializer=agentsd__pb2.FsReadResponse.SerializeToString, + ), + 'Write': grpc.unary_unary_rpc_method_handler( + servicer.Write, + request_deserializer=agentsd__pb2.FsWriteRequest.FromString, + response_serializer=agentsd__pb2.FsResponse.SerializeToString, + ), + 'Delete': grpc.unary_unary_rpc_method_handler( + servicer.Delete, + request_deserializer=agentsd__pb2.FsPathRequest.FromString, + response_serializer=agentsd__pb2.FsResponse.SerializeToString, + ), + 'Rename': grpc.unary_unary_rpc_method_handler( + servicer.Rename, + request_deserializer=agentsd__pb2.FsRenameRequest.FromString, + response_serializer=agentsd__pb2.FsResponse.SerializeToString, + ), + 'Stat': grpc.unary_unary_rpc_method_handler( + servicer.Stat, + request_deserializer=agentsd__pb2.FsPathRequest.FromString, + response_serializer=agentsd__pb2.FsStatResponse.SerializeToString, + ), + 'List': grpc.unary_unary_rpc_method_handler( + servicer.List, + request_deserializer=agentsd__pb2.FsPathRequest.FromString, + response_serializer=agentsd__pb2.FsListResponse.SerializeToString, + ), + 'Watch': grpc.unary_stream_rpc_method_handler( + servicer.Watch, + request_deserializer=agentsd__pb2.FsPathRequest.FromString, + response_serializer=agentsd__pb2.FsWatchEvent.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'agentsd.FS', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('agentsd.FS', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class FS: + """========== FS ========== + """ + + @staticmethod + def Read(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.FS/Read', + agentsd__pb2.FsReadRequest.SerializeToString, + agentsd__pb2.FsReadResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Write(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.FS/Write', + agentsd__pb2.FsWriteRequest.SerializeToString, + agentsd__pb2.FsResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Delete(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.FS/Delete', + agentsd__pb2.FsPathRequest.SerializeToString, + agentsd__pb2.FsResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Rename(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.FS/Rename', + agentsd__pb2.FsRenameRequest.SerializeToString, + agentsd__pb2.FsResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Stat(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.FS/Stat', + agentsd__pb2.FsPathRequest.SerializeToString, + agentsd__pb2.FsStatResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def List(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.FS/List', + agentsd__pb2.FsPathRequest.SerializeToString, + agentsd__pb2.FsListResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Watch(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_stream( + request, + target, + '/agentsd.FS/Watch', + agentsd__pb2.FsPathRequest.SerializeToString, + agentsd__pb2.FsWatchEvent.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + +class MemoryStub: + """========== Memory ========== + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.Read = channel.unary_unary( + '/agentsd.Memory/Read', + request_serializer=agentsd__pb2.MemoryReadRequest.SerializeToString, + response_deserializer=agentsd__pb2.MemoryReadResponse.FromString, + _registered_method=True) + self.Write = channel.unary_unary( + '/agentsd.Memory/Write', + request_serializer=agentsd__pb2.MemoryWriteRequest.SerializeToString, + response_deserializer=agentsd__pb2.MemoryResponse.FromString, + _registered_method=True) + self.Delete = channel.unary_unary( + '/agentsd.Memory/Delete', + request_serializer=agentsd__pb2.MemoryKeyRequest.SerializeToString, + response_deserializer=agentsd__pb2.MemoryResponse.FromString, + _registered_method=True) + self.List = channel.unary_unary( + '/agentsd.Memory/List', + request_serializer=agentsd__pb2.MemoryListRequest.SerializeToString, + response_deserializer=agentsd__pb2.MemoryListResponse.FromString, + _registered_method=True) + self.Search = channel.unary_unary( + '/agentsd.Memory/Search', + request_serializer=agentsd__pb2.MemorySearchRequest.SerializeToString, + response_deserializer=agentsd__pb2.MemorySearchResponse.FromString, + _registered_method=True) + + +class MemoryServicer: + """========== Memory ========== + """ + + def Read(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Write(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Delete(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def List(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Search(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_MemoryServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Read': grpc.unary_unary_rpc_method_handler( + servicer.Read, + request_deserializer=agentsd__pb2.MemoryReadRequest.FromString, + response_serializer=agentsd__pb2.MemoryReadResponse.SerializeToString, + ), + 'Write': grpc.unary_unary_rpc_method_handler( + servicer.Write, + request_deserializer=agentsd__pb2.MemoryWriteRequest.FromString, + response_serializer=agentsd__pb2.MemoryResponse.SerializeToString, + ), + 'Delete': grpc.unary_unary_rpc_method_handler( + servicer.Delete, + request_deserializer=agentsd__pb2.MemoryKeyRequest.FromString, + response_serializer=agentsd__pb2.MemoryResponse.SerializeToString, + ), + 'List': grpc.unary_unary_rpc_method_handler( + servicer.List, + request_deserializer=agentsd__pb2.MemoryListRequest.FromString, + response_serializer=agentsd__pb2.MemoryListResponse.SerializeToString, + ), + 'Search': grpc.unary_unary_rpc_method_handler( + servicer.Search, + request_deserializer=agentsd__pb2.MemorySearchRequest.FromString, + response_serializer=agentsd__pb2.MemorySearchResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'agentsd.Memory', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('agentsd.Memory', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class Memory: + """========== Memory ========== + """ + + @staticmethod + def Read(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Memory/Read', + agentsd__pb2.MemoryReadRequest.SerializeToString, + agentsd__pb2.MemoryReadResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Write(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Memory/Write', + agentsd__pb2.MemoryWriteRequest.SerializeToString, + agentsd__pb2.MemoryResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Delete(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Memory/Delete', + agentsd__pb2.MemoryKeyRequest.SerializeToString, + agentsd__pb2.MemoryResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def List(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Memory/List', + agentsd__pb2.MemoryListRequest.SerializeToString, + agentsd__pb2.MemoryListResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Search(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Memory/Search', + agentsd__pb2.MemorySearchRequest.SerializeToString, + agentsd__pb2.MemorySearchResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + +class NetworkStub: + """========== Network ========== + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.Request = channel.unary_unary( + '/agentsd.Network/Request', + request_serializer=agentsd__pb2.HttpRequest.SerializeToString, + response_deserializer=agentsd__pb2.HttpResponse.FromString, + _registered_method=True) + self.OpenConn = channel.unary_stream( + '/agentsd.Network/OpenConn', + request_serializer=agentsd__pb2.ConnectRequest.SerializeToString, + response_deserializer=agentsd__pb2.DataChunk.FromString, + _registered_method=True) + self.Listen = channel.unary_stream( + '/agentsd.Network/Listen', + request_serializer=agentsd__pb2.ListenRequest.SerializeToString, + response_deserializer=agentsd__pb2.ConnEvent.FromString, + _registered_method=True) + self.Send = channel.unary_unary( + '/agentsd.Network/Send', + request_serializer=agentsd__pb2.SendRequest.SerializeToString, + response_deserializer=agentsd__pb2.NetResponse.FromString, + _registered_method=True) + self.Close = channel.unary_unary( + '/agentsd.Network/Close', + request_serializer=agentsd__pb2.CloseRequest.SerializeToString, + response_deserializer=agentsd__pb2.NetResponse.FromString, + _registered_method=True) + self.Available = channel.unary_unary( + '/agentsd.Network/Available', + request_serializer=agentsd__pb2.Empty.SerializeToString, + response_deserializer=agentsd__pb2.NetAvailableResponse.FromString, + _registered_method=True) + + +class NetworkServicer: + """========== Network ========== + """ + + def Request(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def OpenConn(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Listen(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Send(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Close(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Available(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_NetworkServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Request': grpc.unary_unary_rpc_method_handler( + servicer.Request, + request_deserializer=agentsd__pb2.HttpRequest.FromString, + response_serializer=agentsd__pb2.HttpResponse.SerializeToString, + ), + 'OpenConn': grpc.unary_stream_rpc_method_handler( + servicer.OpenConn, + request_deserializer=agentsd__pb2.ConnectRequest.FromString, + response_serializer=agentsd__pb2.DataChunk.SerializeToString, + ), + 'Listen': grpc.unary_stream_rpc_method_handler( + servicer.Listen, + request_deserializer=agentsd__pb2.ListenRequest.FromString, + response_serializer=agentsd__pb2.ConnEvent.SerializeToString, + ), + 'Send': grpc.unary_unary_rpc_method_handler( + servicer.Send, + request_deserializer=agentsd__pb2.SendRequest.FromString, + response_serializer=agentsd__pb2.NetResponse.SerializeToString, + ), + 'Close': grpc.unary_unary_rpc_method_handler( + servicer.Close, + request_deserializer=agentsd__pb2.CloseRequest.FromString, + response_serializer=agentsd__pb2.NetResponse.SerializeToString, + ), + 'Available': grpc.unary_unary_rpc_method_handler( + servicer.Available, + request_deserializer=agentsd__pb2.Empty.FromString, + response_serializer=agentsd__pb2.NetAvailableResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'agentsd.Network', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('agentsd.Network', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class Network: + """========== Network ========== + """ + + @staticmethod + def Request(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Network/Request', + agentsd__pb2.HttpRequest.SerializeToString, + agentsd__pb2.HttpResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def OpenConn(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_stream( + request, + target, + '/agentsd.Network/OpenConn', + agentsd__pb2.ConnectRequest.SerializeToString, + agentsd__pb2.DataChunk.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Listen(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_stream( + request, + target, + '/agentsd.Network/Listen', + agentsd__pb2.ListenRequest.SerializeToString, + agentsd__pb2.ConnEvent.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Send(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Network/Send', + agentsd__pb2.SendRequest.SerializeToString, + agentsd__pb2.NetResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Close(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Network/Close', + agentsd__pb2.CloseRequest.SerializeToString, + agentsd__pb2.NetResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Available(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Network/Available', + agentsd__pb2.Empty.SerializeToString, + agentsd__pb2.NetAvailableResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + +class ProcessStub: + """========== Process ========== + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.Spawn = channel.unary_unary( + '/agentsd.Process/Spawn', + request_serializer=agentsd__pb2.SpawnRequest.SerializeToString, + response_deserializer=agentsd__pb2.SpawnResponse.FromString, + _registered_method=True) + self.Kill = channel.unary_unary( + '/agentsd.Process/Kill', + request_serializer=agentsd__pb2.PidRequest.SerializeToString, + response_deserializer=agentsd__pb2.ProcResponse.FromString, + _registered_method=True) + self.Wait = channel.unary_unary( + '/agentsd.Process/Wait', + request_serializer=agentsd__pb2.PidRequest.SerializeToString, + response_deserializer=agentsd__pb2.WaitResponse.FromString, + _registered_method=True) + self.Stdin = channel.unary_unary( + '/agentsd.Process/Stdin', + request_serializer=agentsd__pb2.StdinRequest.SerializeToString, + response_deserializer=agentsd__pb2.ProcResponse.FromString, + _registered_method=True) + self.Stdout = channel.unary_stream( + '/agentsd.Process/Stdout', + request_serializer=agentsd__pb2.PidRequest.SerializeToString, + response_deserializer=agentsd__pb2.DataChunk.FromString, + _registered_method=True) + self.Signal = channel.unary_unary( + '/agentsd.Process/Signal', + request_serializer=agentsd__pb2.SignalRequest.SerializeToString, + response_deserializer=agentsd__pb2.ProcResponse.FromString, + _registered_method=True) + self.ListProc = channel.unary_unary( + '/agentsd.Process/ListProc', + request_serializer=agentsd__pb2.Empty.SerializeToString, + response_deserializer=agentsd__pb2.ProcListResponse.FromString, + _registered_method=True) + + +class ProcessServicer: + """========== Process ========== + """ + + def Spawn(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Kill(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Wait(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Stdin(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Stdout(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Signal(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListProc(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_ProcessServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Spawn': grpc.unary_unary_rpc_method_handler( + servicer.Spawn, + request_deserializer=agentsd__pb2.SpawnRequest.FromString, + response_serializer=agentsd__pb2.SpawnResponse.SerializeToString, + ), + 'Kill': grpc.unary_unary_rpc_method_handler( + servicer.Kill, + request_deserializer=agentsd__pb2.PidRequest.FromString, + response_serializer=agentsd__pb2.ProcResponse.SerializeToString, + ), + 'Wait': grpc.unary_unary_rpc_method_handler( + servicer.Wait, + request_deserializer=agentsd__pb2.PidRequest.FromString, + response_serializer=agentsd__pb2.WaitResponse.SerializeToString, + ), + 'Stdin': grpc.unary_unary_rpc_method_handler( + servicer.Stdin, + request_deserializer=agentsd__pb2.StdinRequest.FromString, + response_serializer=agentsd__pb2.ProcResponse.SerializeToString, + ), + 'Stdout': grpc.unary_stream_rpc_method_handler( + servicer.Stdout, + request_deserializer=agentsd__pb2.PidRequest.FromString, + response_serializer=agentsd__pb2.DataChunk.SerializeToString, + ), + 'Signal': grpc.unary_unary_rpc_method_handler( + servicer.Signal, + request_deserializer=agentsd__pb2.SignalRequest.FromString, + response_serializer=agentsd__pb2.ProcResponse.SerializeToString, + ), + 'ListProc': grpc.unary_unary_rpc_method_handler( + servicer.ListProc, + request_deserializer=agentsd__pb2.Empty.FromString, + response_serializer=agentsd__pb2.ProcListResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'agentsd.Process', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('agentsd.Process', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class Process: + """========== Process ========== + """ + + @staticmethod + def Spawn(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Process/Spawn', + agentsd__pb2.SpawnRequest.SerializeToString, + agentsd__pb2.SpawnResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Kill(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Process/Kill', + agentsd__pb2.PidRequest.SerializeToString, + agentsd__pb2.ProcResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Wait(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Process/Wait', + agentsd__pb2.PidRequest.SerializeToString, + agentsd__pb2.WaitResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Stdin(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Process/Stdin', + agentsd__pb2.StdinRequest.SerializeToString, + agentsd__pb2.ProcResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Stdout(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_stream( + request, + target, + '/agentsd.Process/Stdout', + agentsd__pb2.PidRequest.SerializeToString, + agentsd__pb2.DataChunk.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Signal(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Process/Signal', + agentsd__pb2.SignalRequest.SerializeToString, + agentsd__pb2.ProcResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ListProc(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Process/ListProc', + agentsd__pb2.Empty.SerializeToString, + agentsd__pb2.ProcListResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + +class TimerStub: + """========== Timer ========== + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.Once = channel.unary_unary( + '/agentsd.Timer/Once', + request_serializer=agentsd__pb2.TimerOnceRequest.SerializeToString, + response_deserializer=agentsd__pb2.TimerResponse.FromString, + _registered_method=True) + self.Cron = channel.unary_unary( + '/agentsd.Timer/Cron', + request_serializer=agentsd__pb2.TimerCronRequest.SerializeToString, + response_deserializer=agentsd__pb2.TimerResponse.FromString, + _registered_method=True) + self.Cancel = channel.unary_unary( + '/agentsd.Timer/Cancel', + request_serializer=agentsd__pb2.TimerCancelRequest.SerializeToString, + response_deserializer=agentsd__pb2.TimerResponse.FromString, + _registered_method=True) + self.Now = channel.unary_unary( + '/agentsd.Timer/Now', + request_serializer=agentsd__pb2.Empty.SerializeToString, + response_deserializer=agentsd__pb2.TimerNowResponse.FromString, + _registered_method=True) + + +class TimerServicer: + """========== Timer ========== + """ + + def Once(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Cron(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Cancel(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Now(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_TimerServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Once': grpc.unary_unary_rpc_method_handler( + servicer.Once, + request_deserializer=agentsd__pb2.TimerOnceRequest.FromString, + response_serializer=agentsd__pb2.TimerResponse.SerializeToString, + ), + 'Cron': grpc.unary_unary_rpc_method_handler( + servicer.Cron, + request_deserializer=agentsd__pb2.TimerCronRequest.FromString, + response_serializer=agentsd__pb2.TimerResponse.SerializeToString, + ), + 'Cancel': grpc.unary_unary_rpc_method_handler( + servicer.Cancel, + request_deserializer=agentsd__pb2.TimerCancelRequest.FromString, + response_serializer=agentsd__pb2.TimerResponse.SerializeToString, + ), + 'Now': grpc.unary_unary_rpc_method_handler( + servicer.Now, + request_deserializer=agentsd__pb2.Empty.FromString, + response_serializer=agentsd__pb2.TimerNowResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'agentsd.Timer', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('agentsd.Timer', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class Timer: + """========== Timer ========== + """ + + @staticmethod + def Once(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Timer/Once', + agentsd__pb2.TimerOnceRequest.SerializeToString, + agentsd__pb2.TimerResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Cron(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Timer/Cron', + agentsd__pb2.TimerCronRequest.SerializeToString, + agentsd__pb2.TimerResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Cancel(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Timer/Cancel', + agentsd__pb2.TimerCancelRequest.SerializeToString, + agentsd__pb2.TimerResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Now(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Timer/Now', + agentsd__pb2.Empty.SerializeToString, + agentsd__pb2.TimerNowResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + +class HIDStub: + """========== HID ========== + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.Capture = channel.unary_unary( + '/agentsd.HID/Capture', + request_serializer=agentsd__pb2.CaptureRequest.SerializeToString, + response_deserializer=agentsd__pb2.CaptureResponse.FromString, + _registered_method=True) + self.Mouse = channel.unary_unary( + '/agentsd.HID/Mouse', + request_serializer=agentsd__pb2.MouseRequest.SerializeToString, + response_deserializer=agentsd__pb2.HidResponse.FromString, + _registered_method=True) + self.Keyboard = channel.unary_unary( + '/agentsd.HID/Keyboard', + request_serializer=agentsd__pb2.KeyboardRequest.SerializeToString, + response_deserializer=agentsd__pb2.HidResponse.FromString, + _registered_method=True) + self.Ocr = channel.unary_unary( + '/agentsd.HID/Ocr', + request_serializer=agentsd__pb2.CaptureRequest.SerializeToString, + response_deserializer=agentsd__pb2.OcrResponse.FromString, + _registered_method=True) + + +class HIDServicer: + """========== HID ========== + """ + + def Capture(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Mouse(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Keyboard(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Ocr(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_HIDServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Capture': grpc.unary_unary_rpc_method_handler( + servicer.Capture, + request_deserializer=agentsd__pb2.CaptureRequest.FromString, + response_serializer=agentsd__pb2.CaptureResponse.SerializeToString, + ), + 'Mouse': grpc.unary_unary_rpc_method_handler( + servicer.Mouse, + request_deserializer=agentsd__pb2.MouseRequest.FromString, + response_serializer=agentsd__pb2.HidResponse.SerializeToString, + ), + 'Keyboard': grpc.unary_unary_rpc_method_handler( + servicer.Keyboard, + request_deserializer=agentsd__pb2.KeyboardRequest.FromString, + response_serializer=agentsd__pb2.HidResponse.SerializeToString, + ), + 'Ocr': grpc.unary_unary_rpc_method_handler( + servicer.Ocr, + request_deserializer=agentsd__pb2.CaptureRequest.FromString, + response_serializer=agentsd__pb2.OcrResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'agentsd.HID', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('agentsd.HID', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class HID: + """========== HID ========== + """ + + @staticmethod + def Capture(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.HID/Capture', + agentsd__pb2.CaptureRequest.SerializeToString, + agentsd__pb2.CaptureResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Mouse(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.HID/Mouse', + agentsd__pb2.MouseRequest.SerializeToString, + agentsd__pb2.HidResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Keyboard(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.HID/Keyboard', + agentsd__pb2.KeyboardRequest.SerializeToString, + agentsd__pb2.HidResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Ocr(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.HID/Ocr', + agentsd__pb2.CaptureRequest.SerializeToString, + agentsd__pb2.OcrResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + +class InputStub: + """========== Input ========== + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.Text = channel.unary_unary( + '/agentsd.Input/Text', + request_serializer=agentsd__pb2.Empty.SerializeToString, + response_deserializer=agentsd__pb2.InputTextResponse.FromString, + _registered_method=True) + self.Key = channel.unary_unary( + '/agentsd.Input/Key', + request_serializer=agentsd__pb2.Empty.SerializeToString, + response_deserializer=agentsd__pb2.InputKeyResponse.FromString, + _registered_method=True) + self.Clipboard = channel.unary_unary( + '/agentsd.Input/Clipboard', + request_serializer=agentsd__pb2.Empty.SerializeToString, + response_deserializer=agentsd__pb2.InputClipboardResponse.FromString, + _registered_method=True) + self.Events = channel.unary_stream( + '/agentsd.Input/Events', + request_serializer=agentsd__pb2.Empty.SerializeToString, + response_deserializer=agentsd__pb2.InputEvent.FromString, + _registered_method=True) + + +class InputServicer: + """========== Input ========== + """ + + def Text(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Key(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Clipboard(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Events(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_InputServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Text': grpc.unary_unary_rpc_method_handler( + servicer.Text, + request_deserializer=agentsd__pb2.Empty.FromString, + response_serializer=agentsd__pb2.InputTextResponse.SerializeToString, + ), + 'Key': grpc.unary_unary_rpc_method_handler( + servicer.Key, + request_deserializer=agentsd__pb2.Empty.FromString, + response_serializer=agentsd__pb2.InputKeyResponse.SerializeToString, + ), + 'Clipboard': grpc.unary_unary_rpc_method_handler( + servicer.Clipboard, + request_deserializer=agentsd__pb2.Empty.FromString, + response_serializer=agentsd__pb2.InputClipboardResponse.SerializeToString, + ), + 'Events': grpc.unary_stream_rpc_method_handler( + servicer.Events, + request_deserializer=agentsd__pb2.Empty.FromString, + response_serializer=agentsd__pb2.InputEvent.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'agentsd.Input', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('agentsd.Input', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class Input: + """========== Input ========== + """ + + @staticmethod + def Text(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Input/Text', + agentsd__pb2.Empty.SerializeToString, + agentsd__pb2.InputTextResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Key(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Input/Key', + agentsd__pb2.Empty.SerializeToString, + agentsd__pb2.InputKeyResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Clipboard(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.Input/Clipboard', + agentsd__pb2.Empty.SerializeToString, + agentsd__pb2.InputClipboardResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Events(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_stream( + request, + target, + '/agentsd.Input/Events', + agentsd__pb2.Empty.SerializeToString, + agentsd__pb2.InputEvent.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + +class PluginBridgeStub: + """========== Plugin Bridge ========== + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.Register = channel.unary_unary( + '/agentsd.PluginBridge/Register', + request_serializer=agentsd__pb2.PluginInfo.SerializeToString, + response_deserializer=agentsd__pb2.RegisterResponse.FromString, + _registered_method=True) + self.Call = channel.unary_unary( + '/agentsd.PluginBridge/Call', + request_serializer=agentsd__pb2.CallRequest.SerializeToString, + response_deserializer=agentsd__pb2.CallResponse.FromString, + _registered_method=True) + self.StreamCall = channel.unary_stream( + '/agentsd.PluginBridge/StreamCall', + request_serializer=agentsd__pb2.CallRequest.SerializeToString, + response_deserializer=agentsd__pb2.CallResponse.FromString, + _registered_method=True) + self.Heartbeat = channel.unary_unary( + '/agentsd.PluginBridge/Heartbeat', + request_serializer=agentsd__pb2.HeartbeatRequest.SerializeToString, + response_deserializer=agentsd__pb2.HeartbeatResponse.FromString, + _registered_method=True) + + +class PluginBridgeServicer: + """========== Plugin Bridge ========== + """ + + def Register(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Call(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def StreamCall(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Heartbeat(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_PluginBridgeServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Register': grpc.unary_unary_rpc_method_handler( + servicer.Register, + request_deserializer=agentsd__pb2.PluginInfo.FromString, + response_serializer=agentsd__pb2.RegisterResponse.SerializeToString, + ), + 'Call': grpc.unary_unary_rpc_method_handler( + servicer.Call, + request_deserializer=agentsd__pb2.CallRequest.FromString, + response_serializer=agentsd__pb2.CallResponse.SerializeToString, + ), + 'StreamCall': grpc.unary_stream_rpc_method_handler( + servicer.StreamCall, + request_deserializer=agentsd__pb2.CallRequest.FromString, + response_serializer=agentsd__pb2.CallResponse.SerializeToString, + ), + 'Heartbeat': grpc.unary_unary_rpc_method_handler( + servicer.Heartbeat, + request_deserializer=agentsd__pb2.HeartbeatRequest.FromString, + response_serializer=agentsd__pb2.HeartbeatResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'agentsd.PluginBridge', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('agentsd.PluginBridge', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class PluginBridge: + """========== Plugin Bridge ========== + """ + + @staticmethod + def Register(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.PluginBridge/Register', + agentsd__pb2.PluginInfo.SerializeToString, + agentsd__pb2.RegisterResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Call(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.PluginBridge/Call', + agentsd__pb2.CallRequest.SerializeToString, + agentsd__pb2.CallResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def StreamCall(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_stream( + request, + target, + '/agentsd.PluginBridge/StreamCall', + agentsd__pb2.CallRequest.SerializeToString, + agentsd__pb2.CallResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Heartbeat(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/agentsd.PluginBridge/Heartbeat', + agentsd__pb2.HeartbeatRequest.SerializeToString, + agentsd__pb2.HeartbeatResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/plugins/ai_test/ai_test_plugin.py b/plugins/ai_test/ai_test_plugin.py new file mode 100644 index 0000000..c717135 --- /dev/null +++ b/plugins/ai_test/ai_test_plugin.py @@ -0,0 +1,222 @@ +""" +AI Test Plugin for Agentsd +========================== + +Manual verification plugin for AI agents and humans. It is intentionally +not autoloaded by default; run it only when testing Agentsd runtime behavior. + +Usage: + # Start agentsd first: + # RUST_LOG=agentsd=info ./target/release/agentsd.exe + python plugins/ai_test/ai_test_plugin.py +""" + +import json +import sys +import time +from concurrent.futures import ThreadPoolExecutor, TimeoutError +from pathlib import Path + +import grpc + +ROOT = Path(__file__).resolve().parents[2] +SDK_DIR = ROOT / "plugins" / "_sdk" +sys.path.insert(0, str(SDK_DIR)) + +import agentsd_pb2 as pb # noqa: E402 +import agentsd_pb2_grpc as rpc # noqa: E402 + +PLUGIN_ID = "ai-test" +METADATA = (("x-plugin-id", PLUGIN_ID),) +ADDR = "localhost:50051" + + +class CheckRunner: + def __init__(self): + self.channel = grpc.insecure_channel(ADDR) + self.bridge = rpc.PluginBridgeStub(self.channel) + self.display = rpc.DisplayStub(self.channel) + self.memory = rpc.MemoryStub(self.channel) + self.fs = rpc.FSStub(self.channel) + self.process = rpc.ProcessStub(self.channel) + self.timer = rpc.TimerStub(self.channel) + self.network = rpc.NetworkStub(self.channel) + self.results = [] + self.run_id = str(int(time.time() * 1000)) + self.scratch_dir = ROOT / "target" / "release" / "data" / "ai_test" / self.run_id + + def close(self): + self.channel.close() + + def record(self, name, ok, details=""): + icon = "PASS" if ok else "FAIL" + print(f"[{icon}] {name}: {details}") + self.results.append({"name": name, "ok": bool(ok), "details": details}) + + def check(self, name, fn): + try: + details = fn() + self.record(name, True, details or "ok") + except Exception as exc: + self.record(name, False, str(exc)) + + def register(self): + resp = self.bridge.Register(pb.PluginInfo( + id=PLUGIN_ID, + name="AI Test Plugin", + version="0.1.0", + plugin_type="basic", + syscalls=[ + "display.text", + "memory.write", "memory.read", "memory.list", "memory.delete", + "fs.write", "fs.read", "fs.stat", "fs.list", "fs.rename", "fs.delete", + "process.spawn", "process.stdout", "process.wait", + "timer.once", + ], + depends=[], + exports=[pb.ExportDef(name="run_checks", description="Run Agentsd verification checks")], + )) + if not resp.ok: + raise RuntimeError(resp.error) + return f"session={resp.session_id}" + + def display_text(self): + resp = self.display.Text( + pb.DisplayTextRequest(content=f"[ai-test] display check run={self.run_id}"), + metadata=METADATA, + ) + if not resp.ok: + raise RuntimeError(resp.error) + return "display.text ok" + + def memory_roundtrip(self): + key = f"ai_test.{self.run_id}.greeting" + value = f"hello-{self.run_id}".encode("utf-8") + resp = self.memory.Write(pb.MemoryWriteRequest(key=key, value=value), metadata=METADATA) + if not resp.ok: + raise RuntimeError(f"write: {resp.error}") + resp = self.memory.Read(pb.MemoryReadRequest(key=key), metadata=METADATA) + if not resp.found or resp.value != value: + raise RuntimeError(f"read mismatch found={resp.found} value={resp.value!r}") + resp = self.memory.List(pb.MemoryListRequest(prefix=f"ai_test.{self.run_id}", limit=10), metadata=METADATA) + if key not in resp.keys: + raise RuntimeError(f"list missing key; keys={list(resp.keys)} error={resp.error!r}") + resp = self.memory.Delete(pb.MemoryKeyRequest(key=key), metadata=METADATA) + if not resp.ok: + raise RuntimeError(f"delete: {resp.error}") + return f"key={key}" + + def fs_roundtrip(self): + base = self.scratch_dir + file_a = base / "sample.txt" + file_b = base / "renamed.txt" + data = f"AI_TEST_FS_OK {self.run_id}".encode("utf-8") + + resp = self.fs.Write(pb.FsWriteRequest(path=str(file_a), data=data, append=False), metadata=METADATA) + if not resp.ok: + raise RuntimeError(f"write: {resp.error}") + + resp = self.fs.Read(pb.FsReadRequest(path=str(file_a), offset=0, length=0), metadata=METADATA) + if not resp.ok or resp.data != data: + raise RuntimeError(f"read mismatch ok={resp.ok} data={resp.data!r} error={resp.error!r}") + + resp = self.fs.Stat(pb.FsPathRequest(path=str(file_a)), metadata=METADATA) + if not resp.ok or not resp.is_file or resp.size != len(data): + raise RuntimeError(f"stat mismatch ok={resp.ok} size={resp.size} error={resp.error!r}") + + resp = self.fs.List(pb.FsPathRequest(path=str(base)), metadata=METADATA) + names = [e.name for e in resp.entries] + if not resp.ok or "sample.txt" not in names: + raise RuntimeError(f"list mismatch ok={resp.ok} names={names} error={resp.error!r}") + + resp = self.fs.Rename(pb.FsRenameRequest(**{"from": str(file_a), "to": str(file_b)}), metadata=METADATA) + if not resp.ok: + raise RuntimeError(f"rename: {resp.error}") + + resp = self.fs.Delete(pb.FsPathRequest(path=str(base)), metadata=METADATA) + if not resp.ok: + raise RuntimeError(f"delete: {resp.error}") + return str(base) + + def process_roundtrip(self): + code = "print('AI_TEST_PROCESS_OK')" + resp = self.process.Spawn(pb.SpawnRequest( + cmd=sys.executable, + args=["-c", code], + env={}, + cwd=str(ROOT), + ), metadata=METADATA) + if not resp.ok: + raise RuntimeError(resp.error) + pid = resp.pid + + chunks = [] + for chunk in self.process.Stdout(pb.PidRequest(pid=pid), metadata=METADATA): + chunks.append(chunk.data) + output = b"".join(chunks).decode("utf-8", errors="replace").strip() + + wait = self.process.Wait(pb.PidRequest(pid=pid), metadata=METADATA) + if not wait.ok or wait.exit_code != 0: + raise RuntimeError(f"wait failed ok={wait.ok} code={wait.exit_code} error={wait.error!r}") + if output != "AI_TEST_PROCESS_OK": + raise RuntimeError(f"stdout mismatch: {output!r}") + return f"pid={pid} stdout={output}" + + def timer_event(self): + stream = self.bridge.StreamCall( + pb.CallRequest(target=PLUGIN_ID, fn="events", args=b""), + metadata=METADATA, + ) + resp = self.timer.Once(pb.TimerOnceRequest(delay_ms=100, callback_id="ai-test-callback"), metadata=METADATA) + if not resp.ok: + raise RuntimeError(resp.error) + + with ThreadPoolExecutor(max_workers=1) as executor: + future = executor.submit(next, stream) + try: + event = future.result(timeout=5) + except TimeoutError: + stream.cancel() + raise RuntimeError("timed out waiting for timer event") + + payload = json.loads(event.result.decode("utf-8")) + if payload.get("event") != "timer" or payload.get("callback_id") != "ai-test-callback": + raise RuntimeError(f"unexpected payload: {payload}") + return json.dumps(payload, ensure_ascii=False) + + def permission_denial(self): + try: + self.network.Available(pb.Empty(), metadata=METADATA) + except grpc.RpcError as exc: + if exc.code() == grpc.StatusCode.PERMISSION_DENIED: + return exc.details() + raise RuntimeError(f"wrong error: {exc.code().name} {exc.details()}") + raise RuntimeError("network.available unexpectedly succeeded") + + def run(self): + print(f"AI Test Plugin connecting to {ADDR}") + print(f"Scratch dir: {self.scratch_dir}") + self.check("register", self.register) + self.check("display.text", self.display_text) + self.check("memory roundtrip", self.memory_roundtrip) + self.check("fs roundtrip", self.fs_roundtrip) + self.check("process roundtrip", self.process_roundtrip) + self.check("timer event", self.timer_event) + self.check("permission denial", self.permission_denial) + + ok = all(r["ok"] for r in self.results) + summary = {"plugin": PLUGIN_ID, "ok": ok, "results": self.results} + print("AI_TEST_SUMMARY " + json.dumps(summary, ensure_ascii=False)) + return 0 if ok else 1 + + +def main(): + runner = CheckRunner() + try: + return runner.run() + finally: + runner.close() + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/plugins/ai_test/plugin.yaml b/plugins/ai_test/plugin.yaml new file mode 100644 index 0000000..bd6cd74 --- /dev/null +++ b/plugins/ai_test/plugin.yaml @@ -0,0 +1,31 @@ +id: ai-test +name: AI Test Plugin +version: 0.1.0 +type: basic + +# Manual verification plugin. Future plugin loaders must skip it by default. +enabled: false +autoload: false + +syscalls: + - display.text + - memory.write + - memory.read + - memory.list + - memory.delete + - fs.write + - fs.read + - fs.stat + - fs.list + - fs.rename + - fs.delete + - process.spawn + - process.stdout + - process.wait + - timer.once + +exports: + - name: run_checks + description: Run end-to-end Agentsd syscall checks for AI verification + +entry: python ai_test_plugin.py diff --git a/plugins/claudecode/claudecode_plugin.py b/plugins/claudecode/claudecode_plugin.py new file mode 100644 index 0000000..78825b9 --- /dev/null +++ b/plugins/claudecode/claudecode_plugin.py @@ -0,0 +1,312 @@ +""" +Claude Code Plugin for Agentsd +============================== + +Bridges locally installed Claude Code CLI into agentsd as a standard plugin. +Enables agentsd to use Claude Code for code generation, editing, and reasoning. + +Architecture: + agentsd (Rust) ←gRPC→ claudecode_plugin.py → claude CLI (subprocess) + +Capabilities: + - chat: Send a prompt to Claude Code, get response + - code: Generate/edit code with full Claude Code capabilities + - session: Resume previous Claude Code sessions + - tools: Let Claude Code use its built-in tools (Bash, Edit, Read, etc.) + +Usage: + # Start agentsd first, then: + python claudecode_plugin.py +""" + +import json +import logging +import os +import subprocess +import sys +import time +from pathlib import Path +from concurrent import futures + +import grpc + +# Add SDK path +SDK_DIR = Path(__file__).resolve().parent.parent / "_sdk" +sys.path.insert(0, str(SDK_DIR)) + +import agentsd_pb2 as pb +import agentsd_pb2_grpc as rpc + +logging.basicConfig(level=logging.INFO, format="[claudecode] %(levelname)s %(message)s") +logger = logging.getLogger(__name__) + +# Find claude CLI +CLAUDE_BIN = os.environ.get("CLAUDE_BIN", None) + + +def find_claude(): + """Verify claude CLI is available and return the path.""" + global CLAUDE_BIN + + if CLAUDE_BIN is None: + # Auto-detect + candidates = [ + "claude", + "claude.cmd", + os.path.expandvars(r"%APPDATA%\npm\claude.cmd"), + os.path.expanduser("~/.local/bin/claude"), + "/usr/local/bin/claude", + ] + for c in candidates: + try: + result = subprocess.run( + [c, "--version"], + capture_output=True, text=True, timeout=10 + ) + if result.returncode == 0: + CLAUDE_BIN = c + break + except (FileNotFoundError, subprocess.TimeoutExpired, OSError): + continue + + if CLAUDE_BIN is None: + logger.error("Claude Code CLI not found") + logger.error("Install: npm install -g @anthropic-ai/claude-code") + return None + + try: + result = subprocess.run( + [CLAUDE_BIN, "--version"], + capture_output=True, text=True, timeout=10 + ) + version = result.stdout.strip() + logger.info("Found Claude Code: %s at %s", version, CLAUDE_BIN) + return version + except Exception as e: + logger.error("Claude Code CLI error: %s", e) + return None + + +class ClaudeCodePlugin: + """Bridges Claude Code CLI into agentsd.""" + + def __init__(self, agentsd_addr: str = "localhost:50051"): + self.addr = agentsd_addr + self.channel = None + self.bridge = None + self.version = None + self.default_cwd = os.getcwd() + + def connect(self): + """Connect to agentsd.""" + self.channel = grpc.insecure_channel(self.addr) + self.bridge = rpc.PluginBridgeStub(self.channel) + logger.info("Connected to agentsd at %s", self.addr) + + def register(self): + """Register with agentsd.""" + resp = self.bridge.Register(pb.PluginInfo( + id="claudecode", + name="Claude Code", + version=self.version or "unknown", + plugin_type="standard", + syscalls=["process.spawn", "display.text", "fs.read", "fs.write", "network.request"], + depends=["hermes"], + exports=[ + pb.ExportDef(name="chat", description="Send prompt to Claude Code, get text response"), + pb.ExportDef(name="code", description="Generate or edit code with Claude Code"), + pb.ExportDef(name="review", description="Review code with Claude Code"), + pb.ExportDef(name="ask", description="Ask Claude Code a question about the codebase"), + pb.ExportDef(name="run", description="Run Claude Code with full tool access"), + ], + )) + if resp.ok: + logger.info("Registered with agentsd: session=%s", resp.session_id) + else: + logger.error("Registration failed: %s", resp.error) + return resp.ok + + def call_claude(self, prompt: str, cwd: str = None, max_turns: int = None, + allowed_tools: list = None, system_prompt: str = None, + output_format: str = "text") -> dict: + """ + Call Claude Code CLI in print mode. + + Args: + prompt: The prompt to send + cwd: Working directory for Claude Code + max_turns: Max agentic turns (default: unlimited) + allowed_tools: List of tools to allow (e.g. ["Bash", "Read", "Edit"]) + system_prompt: Override system prompt + output_format: "text" or "json" or "stream-json" + + Returns: + dict with "result" or "error" + """ + cmd = [CLAUDE_BIN, "-p", "--output-format", output_format] + + if max_turns is not None: + cmd.extend(["--max-turns", str(max_turns)]) + + if allowed_tools: + cmd.extend(["--allowedTools"] + allowed_tools) + + if system_prompt: + cmd.extend(["--system-prompt", system_prompt]) + + work_dir = cwd or self.default_cwd + + logger.info("Calling claude: cwd=%s prompt=%s...", work_dir, prompt[:60]) + + try: + result = subprocess.run( + cmd, + input=prompt, + capture_output=True, + text=True, + cwd=work_dir, + timeout=300, # 5 min max + env={**os.environ, "CLAUDE_CODE_SIMPLE": "1"}, + ) + + if result.returncode == 0: + output = result.stdout.strip() + if output_format == "json": + try: + return json.loads(output) + except json.JSONDecodeError: + return {"result": output} + return {"result": output} + else: + error = result.stderr.strip() or f"exit code {result.returncode}" + return {"error": error} + + except subprocess.TimeoutExpired: + return {"error": "timeout (300s)"} + except Exception as e: + return {"error": str(e)} + + def handle_call(self, fn_name: str, args: dict) -> dict: + """Route a call to the appropriate handler.""" + prompt = args.get("prompt", args.get("text", "")) + cwd = args.get("cwd", self.default_cwd) + + if fn_name == "chat": + return self.call_claude( + prompt=prompt, + cwd=cwd, + max_turns=1, # Single turn, no tools + allowed_tools=[], + output_format="text", + ) + + elif fn_name == "code": + return self.call_claude( + prompt=prompt, + cwd=cwd, + allowed_tools=["Bash", "Read", "Edit", "Write", "Glob", "Grep"], + output_format="text", + ) + + elif fn_name == "review": + review_prompt = f"Review the following code or changes. Be concise and actionable:\n\n{prompt}" + return self.call_claude( + prompt=review_prompt, + cwd=cwd, + max_turns=1, + allowed_tools=["Read", "Glob", "Grep"], + output_format="text", + ) + + elif fn_name == "ask": + return self.call_claude( + prompt=prompt, + cwd=cwd, + max_turns=3, + allowed_tools=["Read", "Glob", "Grep"], + output_format="text", + ) + + elif fn_name == "run": + # Full access mode + return self.call_claude( + prompt=prompt, + cwd=cwd, + output_format="text", + ) + + else: + return {"error": f"unknown function: {fn_name}"} + + def run_server(self, port: int = 50053): + """Run callback gRPC server for agentsd.""" + server = grpc.server(futures.ThreadPoolExecutor(max_workers=4)) + rpc.add_PluginBridgeServicer_to_server(ClaudeCodeServicer(self), server) + server.add_insecure_port(f"[::]:{port}") + server.start() + logger.info("Claude Code plugin server listening on port %d", port) + return server + + +class ClaudeCodeServicer(rpc.PluginBridgeServicer): + """gRPC servicer for Claude Code plugin.""" + + def __init__(self, plugin: ClaudeCodePlugin): + self.plugin = plugin + + def Call(self, request, context): + fn_name = request.fn + try: + args = json.loads(request.args) if request.args else {} + except json.JSONDecodeError: + args = {} + + logger.info("Call: %s(%s)", fn_name, list(args.keys())) + result = self.plugin.handle_call(fn_name, args) + result_bytes = json.dumps(result, ensure_ascii=False, default=str).encode("utf-8") + + if "error" in result and not result.get("result"): + return pb.CallResponse(result=result_bytes, ok=False, error=result["error"]) + return pb.CallResponse(result=result_bytes, ok=True, error="") + + def Register(self, request, context): + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + return pb.RegisterResponse(ok=False, error="not a bridge") + + def Heartbeat(self, request, context): + return pb.HeartbeatResponse(ok=True) + + def StreamCall(self, request, context): + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + return pb.CallResponse(ok=False, error="not implemented") + + +def main(): + # Verify claude CLI + version = find_claude() + if not version: + sys.exit(1) + + plugin = ClaudeCodePlugin() + plugin.version = version + + # Connect and register + plugin.connect() + if not plugin.register(): + sys.exit(1) + + # Start callback server + server = plugin.run_server(port=50053) + + logger.info("Claude Code plugin running (v%s). Exports: chat, code, review, ask, run", version) + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + logger.info("Shutting down...") + server.stop(grace=5) + plugin.channel.close() + + +if __name__ == "__main__": + main() diff --git a/plugins/claudecode/plugin.yaml b/plugins/claudecode/plugin.yaml new file mode 100644 index 0000000..7689c23 --- /dev/null +++ b/plugins/claudecode/plugin.yaml @@ -0,0 +1,29 @@ +id: claudecode +name: Claude Code +version: 2.1.169 +type: standard + +syscalls: + - process.spawn + - display.text + - fs.read + - fs.write + - network.request + +depends: + - hermes # 可选,用于 agent 编排 + +exports: + - name: chat + description: Send prompt to Claude Code, get text response (no tools) + - name: code + description: Generate or edit code with Claude Code (Bash/Read/Edit/Write) + - name: review + description: Review code changes (Read/Glob/Grep only) + - name: ask + description: Ask about the codebase (Read/Glob/Grep, max 3 turns) + - name: run + description: Full Claude Code access (all tools, unlimited turns) + +entry: python claudecode_plugin.py +port: 50053 diff --git a/plugins/claudecode/test_chat.py b/plugins/claudecode/test_chat.py new file mode 100644 index 0000000..7b6eecf --- /dev/null +++ b/plugins/claudecode/test_chat.py @@ -0,0 +1,29 @@ +"""Quick test: call Claude Code plugin's chat export.""" +import json +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent.parent / "_sdk")) + +import grpc +import agentsd_pb2 as pb +import agentsd_pb2_grpc as rpc + + +def main(): + # Connect to claudecode plugin's callback server + channel = grpc.insecure_channel("localhost:50053") + bridge = rpc.PluginBridgeStub(channel) + + # Call "chat" + args = json.dumps({"prompt": "What is 2+2? Reply with just the number."}).encode() + resp = bridge.Call(pb.CallRequest(target="claudecode", fn="chat", args=args)) + print(f"ok={resp.ok}") + if resp.result: + result = json.loads(resp.result) + print(f"result: {result.get('result', result.get('error', ''))}") + channel.close() + + +if __name__ == "__main__": + main() diff --git a/plugins/echo/echo_plugin.py b/plugins/echo/echo_plugin.py new file mode 100644 index 0000000..6e4eae8 --- /dev/null +++ b/plugins/echo/echo_plugin.py @@ -0,0 +1,67 @@ +""" +Echo plugin for agentsd - verifies the gRPC syscall chain works. +Registers as a basic plugin, calls Display.Text and FS.Read. +""" +import grpc +import sys +import os + +# Add SDK path +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "_sdk")) + +from agentsd_pb2 import ( + DisplayTextRequest, FsReadRequest, FsPathRequest, + MemoryWriteRequest, MemoryReadRequest, + PluginInfo, ExportDef, Empty +) +from agentsd_pb2_grpc import ( + DisplayStub, FSStub, MemoryStub, PluginBridgeStub +) + + +PLUGIN_ID = "echo-test" +METADATA = (("x-plugin-id", PLUGIN_ID),) + + +def main(): + channel = grpc.insecure_channel('localhost:50051') + + # Register plugin + bridge = PluginBridgeStub(channel) + resp = bridge.Register(PluginInfo( + id=PLUGIN_ID, + name="Echo Test Plugin", + version="0.1.0", + plugin_type="basic", + syscalls=["display.text", "fs.list", "memory.read", "memory.write"], + depends=[], + exports=[ExportDef(name="echo", description="Echo test")] + )) + print(f"Register: ok={resp.ok} session={resp.session_id}") + + # Test Display + display = DisplayStub(channel) + resp = display.Text(DisplayTextRequest(content="Hello from echo plugin!"), metadata=METADATA) + print(f"Display.Text: ok={resp.ok}") + + # Test Memory write + read + memory = MemoryStub(channel) + resp = memory.Write(MemoryWriteRequest(key="test.greeting", value=b"Hello World"), metadata=METADATA) + print(f"Memory.Write: ok={resp.ok}") + + resp = memory.Read(MemoryReadRequest(key="test.greeting"), metadata=METADATA) + print(f"Memory.Read: found={resp.found} value={resp.value.decode()}") + + # Test FS list + fs = FSStub(channel) + resp = fs.List(FsPathRequest(path="."), metadata=METADATA) + print(f"FS.List: ok={resp.ok} entries={len(resp.entries)}") + for entry in resp.entries[:5]: + print(f" {entry.name} {'[dir]' if entry.is_dir else ''}") + + print("\nAll syscalls verified OK!") + channel.close() + + +if __name__ == "__main__": + main() diff --git a/plugins/echo/plugin.yaml b/plugins/echo/plugin.yaml new file mode 100644 index 0000000..b1379ed --- /dev/null +++ b/plugins/echo/plugin.yaml @@ -0,0 +1,16 @@ +id: echo-test +name: Echo Test Plugin +version: 0.1.0 +type: basic + +syscalls: + - display.text + - fs.list + - memory.read + - memory.write + +exports: + - name: echo + description: Echo test + +entry: python echo_plugin.py diff --git a/plugins/hermes/hermes_plugin.py b/plugins/hermes/hermes_plugin.py new file mode 100644 index 0000000..4538033 --- /dev/null +++ b/plugins/hermes/hermes_plugin.py @@ -0,0 +1,215 @@ +""" +Hermes Plugin for Agentsd +========================= + +Bridges Hermes Agent's tool registry into agentsd as a standard plugin. +All Hermes tools become callable via agentsd's gRPC PluginBridge. + +Architecture: + agentsd (Rust) ←gRPC→ hermes-plugin.py ← Hermes tools/registry.py + +This plugin: + 1. Imports Hermes tool registry and discovers all built-in tools + 2. Registers with agentsd as a "basic" plugin, exporting all Hermes tools + 3. Listens for Call requests from agentsd and dispatches to Hermes handlers +""" + +import asyncio +import json +import logging +import os +import sys +import threading +import time +from concurrent import futures +from pathlib import Path + +import grpc +from grpc import StatusCode + +# Add hermes-agent to path +HERMES_DIR = Path(__file__).resolve().parent.parent.parent.parent / "hermes-agent" +sys.path.insert(0, str(HERMES_DIR)) + +# Import generated proto +SDK_DIR = Path(__file__).resolve().parent.parent / "_sdk" +sys.path.insert(0, str(SDK_DIR)) + +import agentsd_pb2 as pb +import agentsd_pb2_grpc as rpc + +logging.basicConfig(level=logging.INFO, format="[hermes-plugin] %(levelname)s %(message)s") +logger = logging.getLogger(__name__) + + +class HermesPlugin: + """Bridges Hermes tools into agentsd.""" + + def __init__(self, agentsd_addr: str = "localhost:50051"): + self.addr = agentsd_addr + self.channel = None + self.bridge = None + self.registry = None + self.tool_handlers = {} + + def load_hermes_tools(self): + """Import and discover all Hermes built-in tools.""" + logger.info("Loading Hermes tools from: %s", HERMES_DIR) + + # Import registry + from tools.registry import registry, discover_builtin_tools + + # Discover and import all tool modules + imported = discover_builtin_tools(HERMES_DIR / "tools") + logger.info("Imported %d tool modules", len(imported)) + + self.registry = registry + + # Collect all tool names and handlers + all_names = registry.get_all_tool_names() + for name in all_names: + entry = registry.get_entry(name) + if entry: + self.tool_handlers[name] = entry + logger.info("Loaded %d tools: %s", len(self.tool_handlers), list(self.tool_handlers.keys())[:10]) + return all_names + + def connect(self): + """Connect to agentsd gRPC server.""" + self.channel = grpc.insecure_channel(self.addr) + self.bridge = rpc.PluginBridgeStub(self.channel) + logger.info("Connected to agentsd at %s", self.addr) + + def register(self, tool_names): + """Register this plugin with agentsd, exporting all Hermes tools.""" + exports = [] + for name in tool_names: + entry = self.tool_handlers.get(name) + desc = entry.description if entry else "" + exports.append(pb.ExportDef(name=name, description=desc[:200])) + + resp = self.bridge.Register(pb.PluginInfo( + id="hermes", + name="Hermes Agent Tools", + version="0.16.0", + plugin_type="basic", + syscalls=["network.request", "fs.read", "fs.write", "process.spawn", "memory.read", "memory.write", "display.text", "timer.once"], + depends=[], + exports=exports, + )) + + if resp.ok: + logger.info("Registered with agentsd: session=%s, %d exports", resp.session_id, len(exports)) + else: + logger.error("Registration failed: %s", resp.error) + return resp.ok + + def call_tool(self, tool_name: str, args: dict) -> dict: + """Execute a Hermes tool by name with given args.""" + entry = self.tool_handlers.get(tool_name) + if not entry: + return {"error": f"tool not found: {tool_name}"} + + try: + if entry.is_async: + # Run async handler in event loop + loop = asyncio.new_event_loop() + try: + result = loop.run_until_complete(entry.handler(args)) + finally: + loop.close() + else: + result = entry.handler(args) + + # Hermes tools return strings or dicts + if isinstance(result, str): + return {"result": result} + elif isinstance(result, dict): + return result + else: + return {"result": str(result)} + + except Exception as e: + logger.error("Tool %s failed: %s", tool_name, e) + return {"error": str(e)} + + def run_server(self, port: int = 50052): + """ + Run a gRPC server that agentsd can call back into. + Agentsd routes Call requests here for Hermes tools. + """ + from concurrent import futures + + server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) + rpc.add_PluginBridgeServicer_to_server(HermesServicer(self), server) + server.add_insecure_port(f"[::]:{port}") + server.start() + logger.info("Hermes plugin server listening on port %d", port) + return server + + +class HermesServicer(rpc.PluginBridgeServicer): + """gRPC servicer that handles tool call requests from agentsd.""" + + def __init__(self, plugin: HermesPlugin): + self.plugin = plugin + + def Call(self, request, context): + """Handle a tool call from agentsd.""" + fn_name = request.fn + try: + args = json.loads(request.args) if request.args else {} + except json.JSONDecodeError: + args = {} + + logger.info("Call: %s(%s)", fn_name, list(args.keys())) + result = self.plugin.call_tool(fn_name, args) + result_bytes = json.dumps(result, ensure_ascii=False, default=str).encode("utf-8") + + if "error" in result and not result.get("result"): + return pb.CallResponse(result=result_bytes, ok=False, error=result["error"]) + return pb.CallResponse(result=result_bytes, ok=True, error="") + + def Register(self, request, context): + context.set_code(StatusCode.UNIMPLEMENTED) + return pb.RegisterResponse(ok=False, error="not a bridge") + + def Heartbeat(self, request, context): + return pb.HeartbeatResponse(ok=True) + + def StreamCall(self, request, context): + context.set_code(StatusCode.UNIMPLEMENTED) + return pb.CallResponse(ok=False, error="not implemented") + + +def main(): + plugin = HermesPlugin() + + # Load all Hermes tools + try: + tool_names = plugin.load_hermes_tools() + except Exception as e: + logger.error("Failed to load Hermes tools: %s", e) + logger.info("Ensure hermes-agent is at: %s", HERMES_DIR) + sys.exit(1) + + # Connect and register with agentsd + plugin.connect() + if not plugin.register(tool_names): + sys.exit(1) + + # Start callback server for tool invocations + server = plugin.run_server(port=50052) + + logger.info("Hermes plugin running. %d tools available. Ctrl+C to stop.", len(tool_names)) + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + logger.info("Shutting down...") + server.stop(grace=5) + plugin.channel.close() + + +if __name__ == "__main__": + main() diff --git a/plugins/hermes/plugin.yaml b/plugins/hermes/plugin.yaml new file mode 100644 index 0000000..b0313ee --- /dev/null +++ b/plugins/hermes/plugin.yaml @@ -0,0 +1,19 @@ +id: hermes +name: Hermes Agent Tools +version: 0.16.0 +type: basic + +syscalls: + - network.request + - fs.read + - fs.write + - process.spawn + - memory.read + - memory.write + - display.text + - timer.once + +exports: auto # 自动从 Hermes registry 发现 + +entry: python hermes_plugin.py +port: 50052 diff --git a/proto/agentsd.proto b/proto/agentsd.proto new file mode 100644 index 0000000..40f1558 --- /dev/null +++ b/proto/agentsd.proto @@ -0,0 +1,217 @@ +syntax = "proto3"; +package agentsd; + +// ========== Display ========== +service Display { + rpc Text(DisplayTextRequest) returns (DisplayResponse); + rpc Rich(DisplayRichRequest) returns (DisplayResponse); + rpc Image(DisplayImageRequest) returns (DisplayResponse); + rpc Clear(Empty) returns (DisplayResponse); + rpc Notify(DisplayNotifyRequest) returns (DisplayResponse); +} + +message DisplayTextRequest { string content = 1; } +message DisplayRichRequest { string markup = 1; string format = 2; } +message DisplayImageRequest { bytes data = 1; string format = 2; } +message DisplayNotifyRequest { string message = 1; } +message DisplayResponse { bool ok = 1; string error = 2; } + +// ========== Audio ========== +service Audio { + rpc Play(stream AudioChunk) returns (AudioResponse); + rpc Stop(Empty) returns (AudioResponse); + rpc Volume(VolumeRequest) returns (AudioResponse); + rpc RecordStart(RecordStartRequest) returns (AudioResponse); + rpc RecordStop(Empty) returns (AudioData); + rpc RecordStream(RecordStartRequest) returns (stream AudioChunk); +} + +message AudioChunk { bytes data = 1; string format = 2; } +message VolumeRequest { float level = 1; } +message RecordStartRequest { string format = 1; } +message AudioData { bytes data = 1; string format = 2; } +message AudioResponse { bool ok = 1; string error = 2; } + +// ========== FS ========== +service FS { + rpc Read(FsReadRequest) returns (FsReadResponse); + rpc Write(FsWriteRequest) returns (FsResponse); + rpc Delete(FsPathRequest) returns (FsResponse); + rpc Rename(FsRenameRequest) returns (FsResponse); + rpc Stat(FsPathRequest) returns (FsStatResponse); + rpc List(FsPathRequest) returns (FsListResponse); + rpc Watch(FsPathRequest) returns (stream FsWatchEvent); +} + +message FsPathRequest { string path = 1; } +message FsReadRequest { string path = 1; int64 offset = 2; int64 length = 3; } +message FsReadResponse { bytes data = 1; bool ok = 2; string error = 3; } +message FsWriteRequest { string path = 1; bytes data = 2; bool append = 3; } +message FsRenameRequest { string from = 1; string to = 2; } +message FsStatResponse { + bool ok = 1; + string error = 2; + int64 size = 3; + bool is_dir = 4; + bool is_file = 5; + int64 modified = 6; + int64 created = 7; + uint32 permissions = 8; +} +message FsListResponse { + bool ok = 1; + string error = 2; + repeated FsEntry entries = 3; +} +message FsEntry { string name = 1; bool is_dir = 2; int64 size = 3; } +message FsWatchEvent { string path = 1; string kind = 2; } +message FsResponse { bool ok = 1; string error = 2; } + +// ========== Memory ========== +service Memory { + rpc Read(MemoryReadRequest) returns (MemoryReadResponse); + rpc Write(MemoryWriteRequest) returns (MemoryResponse); + rpc Delete(MemoryKeyRequest) returns (MemoryResponse); + rpc List(MemoryListRequest) returns (MemoryListResponse); + rpc Search(MemorySearchRequest) returns (MemorySearchResponse); +} + +message MemoryKeyRequest { string key = 1; } +message MemoryReadRequest { string key = 1; } +message MemoryReadResponse { bytes value = 1; bool found = 2; string error = 3; } +message MemoryWriteRequest { string key = 1; bytes value = 2; } +message MemoryListRequest { string prefix = 1; int32 limit = 2; } +message MemoryListResponse { repeated string keys = 1; string error = 2; } +message MemorySearchRequest { string query = 1; int32 limit = 2; } +message MemorySearchResponse { + repeated MemorySearchHit hits = 1; + string error = 2; +} +message MemorySearchHit { string key = 1; string snippet = 2; float score = 3; } +message MemoryResponse { bool ok = 1; string error = 2; } + +// ========== Network ========== +service Network { + rpc Request(HttpRequest) returns (HttpResponse); + rpc OpenConn(ConnectRequest) returns (stream DataChunk); + rpc Listen(ListenRequest) returns (stream ConnEvent); + rpc Send(SendRequest) returns (NetResponse); + rpc Close(CloseRequest) returns (NetResponse); + rpc Available(Empty) returns (NetAvailableResponse); +} + +message HttpRequest { + string url = 1; + string method = 2; + map headers = 3; + bytes body = 4; +} +message HttpResponse { + int32 status = 1; + map headers = 2; + bytes body = 3; + string error = 4; +} +message ConnectRequest { string addr = 1; string protocol = 2; } +message ListenRequest { uint32 port = 1; string protocol = 2; } +message ConnEvent { string conn_id = 1; bytes data = 2; string kind = 3; } +message SendRequest { string conn_id = 1; bytes data = 2; } +message CloseRequest { string conn_id = 1; } +message DataChunk { bytes data = 1; } +message NetResponse { bool ok = 1; string error = 2; } +message NetAvailableResponse { bool available = 1; } + +// ========== Process ========== +service Process { + rpc Spawn(SpawnRequest) returns (SpawnResponse); + rpc Kill(PidRequest) returns (ProcResponse); + rpc Wait(PidRequest) returns (WaitResponse); + rpc Stdin(StdinRequest) returns (ProcResponse); + rpc Stdout(PidRequest) returns (stream DataChunk); + rpc Signal(SignalRequest) returns (ProcResponse); + rpc ListProc(Empty) returns (ProcListResponse); +} + +message SpawnRequest { + string cmd = 1; + repeated string args = 2; + map env = 3; + string cwd = 4; +} +message SpawnResponse { uint64 pid = 1; bool ok = 2; string error = 3; } +message PidRequest { uint64 pid = 1; } +message WaitResponse { int32 exit_code = 1; bool ok = 2; string error = 3; } +message StdinRequest { uint64 pid = 1; bytes data = 2; } +message SignalRequest { uint64 pid = 1; int32 signal = 2; } +message ProcResponse { bool ok = 1; string error = 2; } +message ProcListResponse { repeated ProcInfo procs = 1; } +message ProcInfo { uint64 pid = 1; string cmd = 2; string status = 3; } + +// ========== Timer ========== +service Timer { + rpc Once(TimerOnceRequest) returns (TimerResponse); + rpc Cron(TimerCronRequest) returns (TimerResponse); + rpc Cancel(TimerCancelRequest) returns (TimerResponse); + rpc Now(Empty) returns (TimerNowResponse); +} + +message TimerOnceRequest { uint64 delay_ms = 1; string callback_id = 2; } +message TimerCronRequest { string expr = 1; string callback_id = 2; } +message TimerCancelRequest { string timer_id = 1; } +message TimerResponse { string timer_id = 1; bool ok = 2; string error = 3; } +message TimerNowResponse { int64 timestamp_ms = 1; } + +// ========== HID ========== +service HID { + rpc Capture(CaptureRequest) returns (CaptureResponse); + rpc Mouse(MouseRequest) returns (HidResponse); + rpc Keyboard(KeyboardRequest) returns (HidResponse); + rpc Ocr(CaptureRequest) returns (OcrResponse); +} + +message CaptureRequest { int32 x = 1; int32 y = 2; int32 width = 3; int32 height = 4; } +message CaptureResponse { bytes image = 1; string format = 2; bool ok = 3; string error = 4; } +message MouseRequest { int32 x = 1; int32 y = 2; string action = 3; string button = 4; } +message KeyboardRequest { string key = 1; string action = 2; repeated string modifiers = 3; } +message HidResponse { bool ok = 1; string error = 2; } +message OcrResponse { string text = 1; bool ok = 2; string error = 3; } + +// ========== Input ========== +service Input { + rpc Text(Empty) returns (InputTextResponse); + rpc Key(Empty) returns (InputKeyResponse); + rpc Clipboard(Empty) returns (InputClipboardResponse); + rpc Events(Empty) returns (stream InputEvent); +} + +message InputTextResponse { string text = 1; bool ok = 2; string error = 3; } +message InputKeyResponse { string key = 1; string action = 2; repeated string modifiers = 3; } +message InputClipboardResponse { bytes data = 1; string mime = 2; bool ok = 3; string error = 4; } +message InputEvent { string type = 1; bytes data = 2; } + +// ========== Plugin Bridge ========== +service PluginBridge { + rpc Register(PluginInfo) returns (RegisterResponse); + rpc Call(CallRequest) returns (CallResponse); + rpc StreamCall(CallRequest) returns (stream CallResponse); + rpc Heartbeat(HeartbeatRequest) returns (HeartbeatResponse); +} + +message PluginInfo { + string id = 1; + string name = 2; + string version = 3; + string plugin_type = 4; + repeated string syscalls = 5; + repeated string depends = 6; + repeated ExportDef exports = 7; +} +message ExportDef { string name = 1; string description = 2; } +message RegisterResponse { bool ok = 1; string error = 2; string session_id = 3; } +message CallRequest { string target = 1; string fn = 2; bytes args = 3; } +message CallResponse { bytes result = 1; bool ok = 2; string error = 3; } +message HeartbeatRequest { string plugin_id = 1; } +message HeartbeatResponse { bool ok = 1; } + +// ========== Common ========== +message Empty {}