diff --git a/Cargo.lock b/Cargo.lock index 393fc48..1ede92f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -66,12 +66,15 @@ dependencies = [ "base64 0.22.1", "chacha20poly1305", "clap", + "cranibox-agents", "cranibox-contracts", "data-encoding", "ed25519-dalek 2.2.0", "fs2", "futures-util", + "hpke", "libc", + "rand 0.8.8", "rand 0.9.5", "reqwest 0.12.28", "serde", @@ -85,6 +88,7 @@ dependencies = [ "tower", "url", "uuid", + "zeroize", ] [[package]] @@ -109,6 +113,7 @@ dependencies = [ "axum", "base64 0.22.1", "clap", + "cranibox-agents", "cranibox-net", "cranibox-node", "futures-util", @@ -134,9 +139,11 @@ dependencies = [ "chrono", "chrono-tz", "clap", + "cranibox-agents", "fs2", "futures-util", "ipnet", + "json5", "libc", "md-5", "regex", @@ -150,6 +157,18 @@ dependencies = [ "toml 0.8.2", ] +[[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.5" @@ -1105,6 +1124,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -1128,7 +1157,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" dependencies = [ "bitflags 2.13.2", - "core-foundation", + "core-foundation 0.10.1", "core-graphics-types", "foreign-types", "libc", @@ -1141,7 +1170,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ "bitflags 2.13.2", - "core-foundation", + "core-foundation 0.10.1", "libc", ] @@ -1172,6 +1201,7 @@ dependencies = [ "agent-workspace-web", "agent-workspacectl", "anyhow", + "cranibox-node", "tokio", ] @@ -1180,6 +1210,15 @@ name = "cranibox-agents" version = "0.1.0" dependencies = [ "cranibox-contracts", + "fs2", + "json5", + "rusqlite", + "serde", + "serde_json", + "serde_yaml", + "sha2 0.10.9", + "tempfile", + "toml_edit 0.22.27", ] [[package]] @@ -1208,14 +1247,22 @@ dependencies = [ "axum", "base64 0.22.1", "bytes", + "chacha20poly1305", "cranibox-contracts", + "ed25519-dalek 2.2.0", + "fs2", "futures-util", + "hex", + "hpke", "http", "http-body-util", "hyper", "hyper-util", + "keyring", + "rand 0.8.8", "rand 0.9.5", "reqwest 0.12.28", + "rusqlite", "serde", "serde_json", "sha2 0.10.9", @@ -1230,6 +1277,7 @@ dependencies = [ "url", "uuid", "windows-sys 0.61.2", + "zeroize", ] [[package]] @@ -1278,7 +1326,9 @@ dependencies = [ "axum", "base64 0.22.1", "clap", + "cranibox-agents", "cranibox-contracts", + "cranibox-llm", "cranibox-net", "fs2", "futures-util", @@ -1313,6 +1363,7 @@ dependencies = [ "tauri", "tauri-build", "tauri-plugin-autostart", + "tauri-plugin-notification", "tauri-plugin-single-instance", "tokio", "url", @@ -1978,6 +2029,18 @@ dependencies = [ "pin-project-lite", ] +[[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.5.0" @@ -2578,6 +2641,9 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", +] [[package]] name = "hashbrown" @@ -2588,6 +2654,15 @@ dependencies = [ "foldhash", ] +[[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.4.1" @@ -2612,6 +2687,44 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "hpke" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4917627a14198c3603282c5158b815ad5534795451d3c074b53cf3cee0960b11" +dependencies = [ + "aead", + "aes-gcm", + "chacha20poly1305", + "digest 0.10.7", + "generic-array", + "hkdf", + "hmac", + "rand_core 0.6.4", + "sha2 0.10.9", + "subtle", + "x25519-dalek", + "zeroize", +] + [[package]] name = "html5ever" version = "0.39.0" @@ -3351,6 +3464,17 @@ dependencies = [ "thiserror 2.0.21", ] +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + [[package]] name = "jsonptr" version = "0.7.1" @@ -3371,6 +3495,20 @@ dependencies = [ "serde", ] +[[package]] +name = "keyring" +version = "3.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" +dependencies = [ + "byteorder", + "log", + "security-framework 2.11.1", + "security-framework 3.7.0", + "windows-sys 0.60.2", + "zeroize", +] + [[package]] name = "lazy_static" version = "1.5.1" @@ -3435,6 +3573,17 @@ dependencies = [ "libc", ] +[[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" @@ -3496,6 +3645,20 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3d25b0e0b648a86960ac23b7ad4abb9717601dec6f66c165f5b037f3f03065f" +[[package]] +name = "mac-notification-sys" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd604973958ddcc11b561193c0fb96ba146506ef2f231ef2e7c35fd2cbc9beca" +dependencies = [ + "cc", + "log", + "objc2", + "objc2-foundation", + "time", + "uuid", +] + [[package]] name = "markup5ever" version = "0.39.0" @@ -4000,6 +4163,20 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "notify-rust" +version = "4.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4587364a9a0074333429b3df75a30a205340c56a536ca3eb6ca0e59b87bbf8af" +dependencies = [ + "futures-lite", + "log", + "mac-notification-sys", + "serde", + "tauri-winrt-notification", + "zbus", +] + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -4495,6 +4672,50 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pest" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd47a460a9cbc3b3429c7e22bf0d76653e779319bae45a499ca010c9a990bb23" +dependencies = [ + "memchr", + "psm", + "stacker", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "203e46697708f7519ab1608393cf89239cafb46b8e6d08a8ffe359359b11ef44" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e84ae6340a458d3d6cfc73cf328cf3cfe90b2484cd0de25eb66aa954615ed4d5" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 3.0.7", +] + +[[package]] +name = "pest_meta" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b57f92f32b3dbce6598fd8f5cdf1f06a6072570b8103fa21728fa9036923e710" +dependencies = [ + "pest", +] + [[package]] name = "pharos" version = "0.5.3" @@ -4898,6 +5119,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "psm" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810" +dependencies = [ + "cc", +] + [[package]] name = "pxfm" version = "0.1.30" @@ -4996,13 +5226,24 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "rand" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + [[package]] name = "rand" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ - "rand_chacha", + "rand_chacha 0.9.0", "rand_core 0.9.5", ] @@ -5017,6 +5258,16 @@ dependencies = [ "rand_core 0.10.1", ] +[[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 0.6.4", +] + [[package]] name = "rand_chacha" version = "0.9.0" @@ -5259,6 +5510,20 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rusqlite" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" +dependencies = [ + "bitflags 2.13.2", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + [[package]] name = "rustc-hash" version = "2.1.3" @@ -5354,7 +5619,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 3.7.0", ] [[package]] @@ -5373,7 +5638,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1167586491e2b18b8bfbb293e8180ec17c201c4f076d7cb3070ca964e7598f98" dependencies = [ - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "jni 0.22.4", "log", @@ -5382,7 +5647,7 @@ dependencies = [ "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki", - "security-framework", + "security-framework 3.7.0", "security-framework-sys", "webpki-root-certs", "windows-sys 0.61.2", @@ -5498,6 +5763,19 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.13.2", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + [[package]] name = "security-framework" version = "3.7.0" @@ -5505,7 +5783,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags 2.13.2", - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -6088,6 +6366,19 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "stacker" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707f49d46706bacf8a2b00d51dace3f9de527c13eec3778f570c411f89e69967" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.61.2", +] + [[package]] name = "string_cache" version = "0.9.0" @@ -6226,7 +6517,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "501336eb7ba9e417300a6a0fa985721065467aa83a6dcf0422a8e43e4c0328fa" dependencies = [ "bitflags 2.13.2", - "core-foundation", + "core-foundation 0.10.1", "system-configuration-sys", ] @@ -6261,7 +6552,7 @@ checksum = "f37f381f4e048e6cdf038b5705f8cf14ad108279d46eb968140a7b291aba9400" dependencies = [ "bitflags 2.13.2", "block2", - "core-foundation", + "core-foundation 0.10.1", "core-graphics", "crossbeam-channel", "dbus", @@ -6454,6 +6745,26 @@ dependencies = [ "thiserror 2.0.21", ] +[[package]] +name = "tauri-plugin-notification" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b939d7b19c8af1381a72ef8474b3aba2b56c1ddac2c23f0c8e96abbcf86b198b" +dependencies = [ + "log", + "notify-rust", + "rand 0.9.5", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "thiserror 2.0.21", + "time", + "url", + "zbus", +] + [[package]] name = "tauri-plugin-single-instance" version = "2.5.3" @@ -6570,6 +6881,17 @@ dependencies = [ "toml 1.1.8+spec-1.1.0", ] +[[package]] +name = "tauri-winrt-notification" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f37a6c354fd28fc9e322ed9bd47e3959576dad28c9d58ea1cf888cce1c7ccb36" +dependencies = [ + "thiserror 2.0.21", + "windows", + "windows-version", +] + [[package]] name = "tempfile" version = "3.27.0" @@ -6889,6 +7211,20 @@ dependencies = [ "winnow 0.5.40", ] +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap 2.14.2", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.15", +] + [[package]] name = "toml_edit" version = "0.25.17+spec-1.1.0" @@ -6910,6 +7246,12 @@ dependencies = [ "winnow 1.0.4", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "toml_writer" version = "1.1.3+spec-1.1.0" @@ -7082,6 +7424,12 @@ version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + [[package]] name = "uds_windows" version = "1.2.1" @@ -7188,6 +7536,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version-compare" version = "0.2.1" @@ -7656,6 +8010,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -7689,13 +8052,30 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + [[package]] name = "windows-threading" version = "0.2.1" @@ -7726,6 +8106,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -7738,6 +8124,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -7750,12 +8142,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -7768,6 +8172,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -7780,6 +8190,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -7792,6 +8208,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -7804,6 +8226,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winnow" version = "0.5.40" @@ -7813,6 +8241,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + [[package]] name = "winnow" version = "1.0.4" @@ -7952,6 +8389,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek 4.1.3", + "rand_core 0.6.4", +] + [[package]] name = "x509-parser" version = "0.18.1" diff --git a/NOTICE b/NOTICE index fed5d4a..3dae8cb 100644 --- a/NOTICE +++ b/NOTICE @@ -4,3 +4,17 @@ Copyright 2026 fliaping This product is licensed under the Apache License, Version 2.0 (see LICENSE). Third-party components bundled in container images and desktop builds keep their own licenses; see docs/licenses/. + +Model protocol translation includes code ported or adapted from: + +- pleaseai/shunt, commit 9d42ba251dd1f0651e9d150767e3ff98a1ae042b, + MIT OR Apache-2.0; crates/cranibox-llm/src/proto/vendor/shunt/. +- link-assistant/router, commit 07901ad24576e7f045270fe62ac56f8a72446c53, + Unlicense; crates/cranibox-llm/src/proto/chat.rs, with license in + crates/cranibox-llm/src/proto/vendor/link_assistant/. +- yetone/magpie, commit 865b61808690bc9fd716257949414ec97618a5a5, + MIT; crates/cranibox-llm/src/proto/port/magpie/ and provider adaptations. + +Original copyright and license texts are retained alongside the vendored +code. See crates/cranibox-llm/tests/THIRD-PARTY.md for source paths, +adaptations and provider protocol attribution. diff --git a/README.md b/README.md index 9211d64..9c293bf 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,8 @@ Cranibox 是你自己部署的 AI Agent “脑壳”:一个跑在你的服务 ## 架构 +Cranibox 新里程碑的目标规格与逐项完成情况见[设计文档](docs/design/README.md)及[实现核对与缺口](docs/design/IMPLEMENTATION-STATUS.md)。 +

Agent Workspace 架构图
点击图片查看可缩放的 SVG 版本 @@ -257,7 +259,7 @@ docker run -d --name agent-workspace \ -e SELKIES_CONGESTION_CONTROL=false \ -e SELKIES_ENABLE_RESIZE=true \ -e AGENT_WORKSPACE_BOOTSTRAP=remote \ - -e AGENT_WORKSPACE_AGENT=codex \ + -e AGENT_WORKSPACE_AGENT=deepseek-harness \ -p 3001:3001 -p 8443:8443 \ -v ~/agent-workspace-data:/config \ xuping/agent-workspace:ubuntu-xfce @@ -322,7 +324,7 @@ code-server 默认监听 `0.0.0.0:8443`,因此需要在创建容器时映射 ` | `START_DOCKER` | `false` | 启用容器内 Docker(需 `--privileged`) | | `USE_CHINA_MIRROR` | `false` | 运行时切换国内镜像源 | | `AGENT_WORKSPACE_BOOTSTRAP` | 不设置 | 设为 `remote`(或 `foundation` / `base`)时,首次启动自动安装 code-server、Control Center 和桌面集成;`remote-up.sh` 会设置为 `remote` | -| `AGENT_WORKSPACE_AGENT` | `none`(`remote-up.sh` 默认 `codex`) | 首次引导时安装 `codex`、`claude-code`、`hermes`、`deepseek-harness`、`openclaw` 或 `none`;仅在设置 `AGENT_WORKSPACE_BOOTSTRAP` 时生效 | +| `AGENT_WORKSPACE_AGENT` | `deepseek-harness` | 首次引导时安装 `codex`、`claude-code`、`pi`、`hermes`、`deepseek-harness`、`openclaw` 或 `none`;仅在设置 `AGENT_WORKSPACE_BOOTSTRAP` 时生效 | | `CODE_SERVER_BIND` / `CODE_SERVER_AUTH` / `CODE_SERVER_CERT` | `0.0.0.0:8443` / `password` / `true` | code-server 监听地址、认证方式和自签名证书;密码默认取 `PASSWORD` | | `SSH_PASSWORD` | 不设置 | 设置后启用 SSH 服务(容器端口 22),值为 abc 用户密码;compose 文件默认不映射该端口,需要时添加如 `-p 2222:22` | | `NODE_OPTIONS` | - | Node.js 选项(如 `--max-old-space-size=2048`) | diff --git a/README_en.md b/README_en.md index b5803db..f6f226e 100644 --- a/README_en.md +++ b/README_en.md @@ -264,7 +264,7 @@ docker run -d --name agent-workspace \ -e SELKIES_CONGESTION_CONTROL=false \ -e SELKIES_ENABLE_RESIZE=true \ -e AGENT_WORKSPACE_BOOTSTRAP=remote \ - -e AGENT_WORKSPACE_AGENT=codex \ + -e AGENT_WORKSPACE_AGENT=deepseek-harness \ -p 3001:3001 -p 8443:8443 \ -v ~/agent-workspace-data:/config \ xuping/agent-workspace:ubuntu-xfce @@ -332,7 +332,7 @@ Each release also publishes pinned tags such as `ubuntu-xfce-1.0.35`; see | `START_DOCKER` | `false` | Enable Docker inside container (requires `--privileged`) | | `USE_CHINA_MIRROR` | `false` | Switch to China mirrors at runtime | | `AGENT_WORKSPACE_BOOTSTRAP` | unset | `remote` (or `foundation` / `base`) installs code-server, Control Center, and desktop integration on first boot; `remote-up.sh` sets `remote` | -| `AGENT_WORKSPACE_AGENT` | `none` (`codex` with `remote-up.sh`) | First-boot Agent: `codex`, `claude-code`, `hermes`, `deepseek-harness`, `openclaw`, or `none`; only used when `AGENT_WORKSPACE_BOOTSTRAP` is set | +| `AGENT_WORKSPACE_AGENT` | `deepseek-harness` | First-boot Agent: `codex`, `claude-code`, `pi`, `hermes`, `deepseek-harness`, `openclaw`, or `none`; only used when `AGENT_WORKSPACE_BOOTSTRAP` is set | | `CODE_SERVER_BIND` / `CODE_SERVER_AUTH` / `CODE_SERVER_CERT` | `0.0.0.0:8443` / `password` / `true` | code-server listen address, authentication, and self-signed certificate; the password defaults to `PASSWORD` | | `SSH_PASSWORD` | unset | Set to enable SSH service (container port 22), value is abc user password; the compose files do not publish it, add e.g. `-p 2222:22` | | `NODE_OPTIONS` | - | Node.js options (e.g., `--max-old-space-size=2048`) | diff --git a/addons/control-center/index.en.html b/addons/control-center/index.en.html index b2f8d09..c08849f 100644 --- a/addons/control-center/index.en.html +++ b/addons/control-center/index.en.html @@ -1,7 +1,7 @@ - + Cranibox · Agent Workspace diff --git a/addons/control-center/index.zh-cn.html b/addons/control-center/index.zh-cn.html index 98d300b..eaadaf6 100644 --- a/addons/control-center/index.zh-cn.html +++ b/addons/control-center/index.zh-cn.html @@ -1,7 +1,7 @@ - + Cranibox · Agent Workspace diff --git a/addons/control-center/ui/src/main.tsx b/addons/control-center/ui/src/main.tsx index f67dd41..78be571 100644 --- a/addons/control-center/ui/src/main.tsx +++ b/addons/control-center/ui/src/main.tsx @@ -16,6 +16,8 @@ import { WebTerminalEnvironment } from './terminal/WebTerminalEnvironment'; import { Nodes } from './pages/Nodes'; import { Performance } from './pages/Performance'; import './style.css'; +import { PersonalApp } from './personal/PersonalApp'; +import { httpApi } from './personal/api'; import './terminal/terminal.css'; import './terminal/window.css'; import { openUrl,t,useWorkspace,WorkspaceProvider } from './workspace'; @@ -84,4 +86,16 @@ function LinkBrand() { return item.id === legacy)) history.replaceState(null, '', '#/' + legacy); -createRoot(document.getElementById('root')!).render(); +const personalApi = httpApi(window.workspaceWeb.csrf); +function PersonalShell() { + const navigate = useNavigate(); + const location = useLocation(); + useEffect(() => { + if (location.pathname === '/app/advanced') navigate('/overview', { replace: true }); + }, [location.pathname, navigate]); + // HashRouter commits navigation in a transition. Wait for its location before + // mounting the legacy routes, whose fallback would redirect the previous URL. + const personalRoute = location.pathname === '/' || location.pathname.startsWith('/app/'); + return } onNavigate={navigate} />; +} +createRoot(document.getElementById('root')!).render(); diff --git a/addons/control-center/ui/src/personal/PersonalApp.tsx b/addons/control-center/ui/src/personal/PersonalApp.tsx new file mode 100644 index 0000000..b8f3b3a --- /dev/null +++ b/addons/control-center/ui/src/personal/PersonalApp.tsx @@ -0,0 +1,1711 @@ +import { + useCallback, + useEffect, + useState, + useRef, + type ReactNode, +} from "react"; +import { + Bell, + Bot, + ChevronRight, + CircleCheck, + House, + Inbox, + Laptop, + ListTodo, + LoaderCircle, + Menu, + Monitor, + Moon, + Plus, + Settings, + ShieldCheck, + Sparkles, + Sun, + X, +} from "lucide-react"; +import { + type Api, + type Account, + type Accepted, + type Agent, + type Approval, + type Device, + type Invite, + type Model, + type Pill, + type Preset, + type SignIn, + type TaskSummary, + agentNames, +} from "./api"; +import { + ApprovalCard, + Card, + Pill as Tag, + QrPanel, + Sheet, + StatusPill, +} from "./components"; +import "./personal.css"; +type Page = "home" | "inbox" | "devices" | "models" | "settings" | "advanced"; +const pages: Page[] = ["home", "inbox", "devices", "models", "settings"]; +function stored(key: string, fallback: string) { + try { + return localStorage.getItem(key) || fallback; + } catch { + return fallback; + } +} +function pageFromHash(): Page { + const path = location.hash.replace(/^#\/?/, ""); + return path === "app/advanced" + ? "advanced" + : path.startsWith("app/") && pages.includes(path.slice(4) as Page) + ? (path.slice(4) as Page) + : path && !path.startsWith("app/") + ? "advanced" + : "home"; +} +export function PersonalApp({ + api, + language = "en", + advanced, + openExternal, + onNavigate, + chatUrl = "/proxy/3080/", +}: { + api: Api; + language?: string; + advanced?: ReactNode; + openExternal?: (url: string) => Promise; + onNavigate?: (path: string) => void; + chatUrl?: string; +}) { + const [locale, setLocale] = useState(() => + stored("cranibox.language", language), + ), + [theme, setTheme] = useState(() => stored("cranibox.theme", "light")); + const t = useCallback( + (zh: string, en: string) => (locale === "zh-cn" ? zh : en), + [locale], + ); + const [page, setPage] = useState(pageFromHash), + [navOpen, setNavOpen] = useState(false), + [taskOpen, setTaskOpen] = useState(false), + [statusOpen, setStatusOpen] = useState(false); + const [status, setStatus] = useState(null), + [devices, setDevices] = useState([]), + [approvals, setApprovals] = useState([]), + [agents, setAgents] = useState([]), + [models, setModels] = useState([]), + [accounts, setAccounts] = useState([]), + [presets, setPresets] = useState([]); + const [loading, setLoading] = useState(true), + [busy, setBusy] = useState(false), + [error, setError] = useState(""), + [notice, setNotice] = useState(""); + const [sheet, setSheet] = useState<"pair" | "subscription" | null>(null), + [purpose, setPurpose] = useState("owner_device"), + [invite, setInvite] = useState(null), + [pairLink, setPairLink] = useState(""), + [accepted, setAccepted] = useState(null), + [pairError, setPairError] = useState(""); + const [onboarding, setOnboarding] = useState( + () => stored("cranibox.onboarded", "") !== "1", + ), + [onboardStep, setOnboardStep] = useState(0); + const [tab, setTab] = useState("signin"), + [provider, setProvider] = useState("codex"), + [region, setRegion] = useState(""), + [key, setKey] = useState(""), + [label, setLabel] = useState(""), + [endpoint, setEndpoint] = useState(""), + [customModel, setCustomModel] = useState(""), + [signIn, setSignIn] = useState(null), + [callback, setCallback] = useState(""), + [subscriptionError, setSubscriptionError] = useState(""); + const [selectedDevice, setSelectedDevice] = useState(""); + const [tasks, setTasks] = useState([]), + [tasksLoading, setTasksLoading] = useState(false), + [tasksError, setTasksError] = useState(""); + useEffect(() => { + if (!taskOpen) return; + let active = true; + let pending = false; + const load = async () => { + if (pending) return; + pending = true; + try { + const items = await api.call("GET", "/tasks"); + if (active) { setTasks(items); setTasksError(""); } + } catch (error) { + if (active) setTasksError(error instanceof Error ? error.message : String(error)); + } finally { + pending = false; + if (active) setTasksLoading(false); + } + }; + setTasksLoading(true); + void load(); + const timer = setInterval(() => void load(), 5000); + return () => { active = false; clearInterval(timer); }; + }, [taskOpen, api]); + const labels: Record = { + home: t("首页", "Home"), + inbox: t("收件箱", "Inbox"), + devices: t("设备", "Devices"), + models: t("模型", "Models"), + settings: t("设置", "Settings"), + advanced: t("高级", "Advanced"), + }; + const icons = { + home: House, + inbox: Inbox, + devices: Laptop, + models: Sparkles, + settings: Settings, + }; + const refreshing = useRef | null>(null); + const refresh = useCallback(() => { + if (refreshing.current) return refreshing.current; + const pending = (async () => { + const results = await Promise.allSettled([ + api.call("GET", "/status").then(setStatus), + api.call("GET", "/devices").then(setDevices), + api.call("GET", "/approvals").then(setApprovals), + api.call("GET", "/agents").then(setAgents), + api.call("GET", "/models/models").then(setModels), + api.call("GET", "/models/accounts").then(setAccounts), + api.call("GET", "/models/presets").then(setPresets), + ]); + const failed = results.find((r) => r.status === "rejected"); + setError( + failed?.status === "rejected" + ? String( + failed.reason instanceof Error + ? failed.reason.message + : failed.reason, + ) + : "", + ); + setLoading(false); + })().finally(() => { + refreshing.current = null; + }); + refreshing.current = pending; + return pending; + }, [api]); + useEffect(() => { + void refresh(); + const timer = setInterval(() => { + if (!document.hidden) void refresh(); + }, 10000); + return () => clearInterval(timer); + }, [refresh]); + useEffect( + () => + api.subscribe?.(() => { + if (!document.hidden) void refresh(); + }), + [api, refresh], + ); + useEffect(() => { + const change = () => { + setPage(pageFromHash()); + setNavOpen(false); + }; + window.addEventListener("hashchange", change); + return () => window.removeEventListener("hashchange", change); + }, []); + useEffect(() => { + document.documentElement.dataset.theme = theme; + try { + localStorage.setItem("cranibox.theme", theme); + localStorage.setItem("cranibox.language", locale); + } catch {} + }, [theme, locale]); + useEffect(() => { + document.title = labels[page] + " · Cranibox"; + }, [page, locale]); + function navigate(target: Page, path = target === "advanced" ? "/overview" : "/app/" + target) { + setPage(target); + setNavOpen(false); + if (onNavigate) onNavigate(path); + else location.hash = path; + } + async function run(action: () => Promise) { + if (busy) return; + setBusy(true); + setError(""); + try { + await action(); + await refresh(); + } catch (error) { + setError(error instanceof Error ? error.message : String(error)); + } finally { + setBusy(false); + } + } + function finishOnboarding() { + try { + localStorage.setItem("cranibox.onboarded", "1"); + } catch {} + setOnboarding(false); + navigate("home"); + } + function openPair() { + setInvite(null); + setAccepted(null); + setPairError(""); + setSheet("pair"); + } + async function createInvite() { + setBusy(true); + setPairError(""); + try { + setInvite(await api.call("POST", "/pair/invite", { purpose })); + } catch (error) { + setPairError(String(error instanceof Error ? error.message : error)); + } finally { + setBusy(false); + } + } + async function acceptPair() { + setBusy(true); + setPairError(""); + try { + setAccepted( + await api.call("POST", "/pair/accept", { + link: pairLink.trim(), + }), + ); + } catch (error) { + setPairError(String(error instanceof Error ? error.message : error)); + } finally { + setBusy(false); + } + } + async function confirmPair(matches: boolean) { + if (!accepted) return; + setBusy(true); + setPairError(""); + try { + await api.call( + "POST", + "/pair/" + encodeURIComponent(accepted.flow_id) + "/confirm", + { match: matches }, + ); + if (matches) { + setNotice(t("设备已配对", "Device paired")); + setSheet(null); + setOnboardStep(2); + await refresh(); + } else { + setAccepted(null); + setPairError( + t( + "配对已取消。请重新生成邀请。", + "Pairing cancelled. Create a new invitation.", + ), + ); + } + } catch (error) { + setPairError(String(error instanceof Error ? error.message : error)); + } finally { + setBusy(false); + } + } + function addSubscription() { + setRegion(""); + setKey(""); + setSignIn(null); + setCallback(""); + setSubscriptionError(""); + setSheet("subscription"); + } + async function external(url: string) { + if (!/^https?:\/\//i.test(url)) + throw Error(t("无效的登录地址", "Invalid sign-in URL")); + if (openExternal) await openExternal(url); + else window.open(url, "_blank", "noopener,noreferrer"); + } + async function submitSubscription(event: React.FormEvent) { + event.preventDefault(); + setBusy(true); + setSubscriptionError(""); + try { + if (tab === "signin") { + const flow = await api.call("POST", "/models/signin/start", { + provider, + region: region || null, + }); + setSignIn(flow); + await external(flow.url); + } else { + const body = + tab === "custom" + ? { + kind: "custom", + label, + key, + endpoints: { chat: endpoint }, + models: customModel + .split(",") + .map((s) => s.trim()) + .filter(Boolean), + } + : { + kind: "api_key", + preset: provider, + key, + label: label || null, + region: region || null, + }; + await api.call("POST", "/models/accounts", body); + setKey(""); + setSheet(null); + await refresh(); + } + } catch (error) { + setSubscriptionError( + String(error instanceof Error ? error.message : error), + ); + } finally { + setBusy(false); + } + } + const currentDevice = devices.find((d) => d.badges.includes("this")); + const available = models.filter((m) => m.availability.kind !== "unavailable"); + const dsh = agents.find((a) => a.id === "deepseek-harness"); + const agentPicker = ( +

+ {agents.map((agent) => ( +
+ + +
+ {agentNames[agent.id] || agent.id} +
+ {agent.recommended && ( + + {t("推荐 · 默认", "Recommended · Default")} + + )} + + {agent.installed + ? t("已安装", "Installed") + : t("未安装", "Not installed")} + +
+
+ +
+ ))} +
+ ); + const pairContent = ( + <> + {!accepted ? ( + <> +
+ {[ + ["owner_device", t("我的设备", "My device")], + ["guest", t("访客设备", "Guest device")], + ].map(([value, label]) => ( + + ))} +
+

+ {purpose === "guest" + ? t( + "访客只能使用你允许的功能,无法访问模型订阅。", + "Guests can use only the capabilities you allow. Model subscriptions stay private.", + ) + : t( + "你的设备共享设备名单,并接收加密的模型订阅。", + "Your devices share a device list and receive encrypted model subscriptions.", + )} +

+ {purpose === "guest" && ( +

+ {t( + "配对完成后,在访客设备卡片上选择允许的功能。", + "After pairing, choose allowed capabilities on the guest device card.", + )} +

+ )} + {invite ? ( + + ) : ( + + )} +
+ {t("或打开收到的邀请", "Or open an invitation")} +
+
{ + e.preventDefault(); + void acceptPair(); + }} + > + + +
+ + ) : ( +
+
+ )} + {pairError && ( +

+ {pairError} +

+ )} + + ); + return ( +
+ {page !== "advanced" && ( + { + e.preventDefault(); + document.getElementById("pa-main")?.focus(); + }} + > + {t("跳至主要内容", "Skip to main content")} + + )} + {page === "advanced" ? ( +
+
+ +

+ {t( + "高级功能 · 面向熟悉网络和命令行的用户", + "Advanced · For users familiar with networks and the command line", + )} +

+
+ {advanced || ( + + {t( + "此环境未提供高级工具。", + "Advanced tools are not available in this environment.", + )} + + )} +
+ ) : ( + <> + + {navOpen && ( + + +
+
+ setStatusOpen(true)} + /> + +
+ +
+ {error && ( +
+ {error} + +
+ )} + {notice && ( +
+ {notice} + +
+ )} + {loading ? ( +
+
+ ) : ( + <> + {page === "home" && ( + <> +
+
+

+ {t( + "一点想法,无限可能", + "A LITTLE IDEA. A WORLD OF POSSIBILITY.", + )} +

+

+ {t( + "今天,一起做些什么?", + "What shall we do today?", + )} +

+

+ {t( + "交给 Agent。需要你决定时,我会来问你。", + "Hand it to your agent. You decide when it matters.", + )} +

+
+ +
+ {dsh?.installed ? ( +
+