diff --git a/.cargo/config.toml b/.cargo/config.toml index d363b83..03e45ea 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1,2 @@ [env] -MTP_TYPE_MAPS = { value = "type-maps.yaml", relative = true } +MTP_TYPE_MAPS = { value = "../type-maps.yaml", relative = true } diff --git a/Cargo.lock b/Cargo.lock index 3ad4b62..b8b8ebf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,16 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common 0.1.7", + "generic-array", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -23,7 +33,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror", + "thiserror 2.0.18", "time", ] @@ -97,6 +107,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + [[package]] name = "bit-vec" version = "0.9.1" @@ -112,6 +128,15 @@ version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "block-buffer" version = "0.12.1" @@ -141,9 +166,9 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "cc" -version = "1.2.66" +version = "1.2.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996" +checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38" dependencies = [ "find-msvc-tools", "jobserver", @@ -163,6 +188,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + [[package]] name = "chacha20" version = "0.10.1" @@ -170,10 +206,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.3.0", "rand_core 0.10.1", ] +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20 0.9.1", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.45" @@ -188,6 +237,17 @@ dependencies = [ "windows-link", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout", + "zeroize", +] + [[package]] name = "cmake" version = "0.1.58" @@ -197,6 +257,18 @@ dependencies = [ "cc", ] +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "const-oid" version = "0.10.2" @@ -219,6 +291,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + [[package]] name = "cpufeatures" version = "0.3.0" @@ -228,13 +309,62 @@ dependencies = [ "libc", ] +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + [[package]] name = "crypto-common" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ + "getrandom 0.4.3", "hybrid-array", + "rand_core 0.10.1", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -243,6 +373,27 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid 0.9.6", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "der" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" +dependencies = [ + "const-oid 0.10.2", + "zeroize", +] + [[package]] name = "der-parser" version = "10.0.0" @@ -263,15 +414,26 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common 0.1.7", +] + [[package]] name = "digest" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", + "block-buffer 0.12.1", + "const-oid 0.10.2", + "crypto-common 0.2.2", + "ctutils", ] [[package]] @@ -291,6 +453,30 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8 0.10.2", + "signature 2.2.0", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2 0.10.9", + "subtle", + "zeroize", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -313,6 +499,12 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -358,6 +550,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -391,6 +593,24 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +[[package]] +name = "hkdf" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", +] + [[package]] name = "httlib-huffman" version = "0.3.4" @@ -403,6 +623,7 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" dependencies = [ + "ctutils", "typenum", ] @@ -543,6 +764,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "itoa" version = "1.0.18" @@ -570,6 +800,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures 0.2.17", +] + +[[package]] +name = "keccak" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -629,52 +878,171 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "wasi", "windows-sys 0.61.2", ] +[[package]] +name = "ml-dsa" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add6b9d92e496f16f4526d68ff29da1483aba4b119baeab8bed3b9e3544a6f3d" +dependencies = [ + "const-oid 0.10.2", + "crypto-common 0.2.2", + "ctutils", + "hybrid-array", + "module-lattice", + "pkcs8 0.11.0", + "shake", + "signature 3.0.0", +] + +[[package]] +name = "mlkem-rs" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b0965b8b081668ff0398dc5e9dc3f2ebb9e833393f4ab5b9f725ddce11acef8" +dependencies = [ + "rand_core 0.6.4", + "serde", + "sha3", + "subtle", + "zeroize", +] + +[[package]] +name = "mlkem-tls" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77b205d031298adf904d88efd6a57862d8650a4ab754aade19a9b5e87040bf4e" +dependencies = [ + "mlkem-rs", + "rand_core 0.6.4", + "subtle", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "module-lattice" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe" +dependencies = [ + "ctutils", + "hybrid-array", + "num-traits", +] + [[package]] name = "mtp" version = "0.1.0" -source = "git+https://git@git.methanium.net/methanium/mtp.git#2126a142f44634ccfdcaa297633fdb63b1d9b46d" +source = "git+https://git@git.methanium.net/methanium/mtp.git#6a65e43ca9f7d128b691c8946adaeecccc2cb2ec" +dependencies = [ + "mtp-client", + "mtp-codec", + "mtp-common", + "mtp-crypto", + "mtp-host", + "mtp-type-map", +] + +[[package]] +name = "mtp-client" +version = "0.1.0" +source = "git+https://git@git.methanium.net/methanium/mtp.git#6a65e43ca9f7d128b691c8946adaeecccc2cb2ec" dependencies = [ "mtp-codec", "mtp-common", - "mtp-type-map", + "mtp-crypto", + "mtp-transport", + "rand 0.8.7", + "tokio", + "tracing", ] [[package]] name = "mtp-codec" version = "0.1.0" -source = "git+https://git@git.methanium.net/methanium/mtp.git#2126a142f44634ccfdcaa297633fdb63b1d9b46d" +source = "git+https://git@git.methanium.net/methanium/mtp.git#6a65e43ca9f7d128b691c8946adaeecccc2cb2ec" dependencies = [ "base64", "byteorder", "mtp-common", + "mtp-crypto", "mtp-type-map", - "rand 0.8.6", + "rand 0.8.7", ] [[package]] name = "mtp-common" version = "0.1.0" -source = "git+https://git@git.methanium.net/methanium/mtp.git#2126a142f44634ccfdcaa297633fdb63b1d9b46d" +source = "git+https://git@git.methanium.net/methanium/mtp.git#6a65e43ca9f7d128b691c8946adaeecccc2cb2ec" dependencies = [ "quinn", "rustls", - "thiserror", + "thiserror 2.0.18", + "wtransport", +] + +[[package]] +name = "mtp-crypto" +version = "0.1.0" +source = "git+https://git@git.methanium.net/methanium/mtp.git#6a65e43ca9f7d128b691c8946adaeecccc2cb2ec" +dependencies = [ + "base64", + "chacha20poly1305", + "ed25519-dalek", + "getrandom 0.4.3", + "hkdf", + "ml-dsa", + "mlkem-tls", + "rand_core 0.6.4", + "serde", + "sha2 0.11.0", + "thiserror 1.0.69", + "zeroize", +] + +[[package]] +name = "mtp-host" +version = "0.1.0" +source = "git+https://git@git.methanium.net/methanium/mtp.git#6a65e43ca9f7d128b691c8946adaeecccc2cb2ec" +dependencies = [ + "mtp-codec", + "mtp-common", + "mtp-crypto", + "mtp-transport", + "rand 0.8.7", + "tokio", +] + +[[package]] +name = "mtp-transport" +version = "0.1.0" +source = "git+https://git@git.methanium.net/methanium/mtp.git#6a65e43ca9f7d128b691c8946adaeecccc2cb2ec" +dependencies = [ + "log", + "mtp-codec", + "mtp-common", + "rcgen", + "rustls", + "rustls-native-certs", + "tokio", + "tracing", "wtransport", ] [[package]] name = "mtp-type-map" version = "0.1.0" -source = "git+https://git@git.methanium.net/methanium/mtp.git#2126a142f44634ccfdcaa297633fdb63b1d9b46d" +source = "git+https://git@git.methanium.net/methanium/mtp.git#6a65e43ca9f7d128b691c8946adaeecccc2cb2ec" dependencies = [ "serde", "serde_yaml", @@ -726,9 +1094,9 @@ dependencies = [ [[package]] name = "octets" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8311fa8ab7a57759b4ff1f851a3048d9ef0effaa0130726426b742d26d8a88e7" +checksum = "866cb5af6f3aa3c1b44c3c2d79d22165fbb1b102e1b3fb499864bfe34736ec4b" [[package]] name = "oid-registry" @@ -745,6 +1113,12 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "openssl-probe" version = "0.2.1" @@ -784,6 +1158,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -796,12 +1179,43 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der 0.7.10", + "spki 0.7.3", +] + +[[package]] +name = "pkcs8" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" +dependencies = [ + "der 0.8.1", + "spki 0.8.0", +] + [[package]] name = "pkg-config" version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + [[package]] name = "potential_utf" version = "0.1.5" @@ -849,7 +1263,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -872,7 +1286,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -909,9 +1323,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" dependencies = [ "libc", "rand_chacha", @@ -924,7 +1338,7 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" dependencies = [ - "chacha20", + "chacha20 0.10.1", "getrandom 0.4.3", "rand_core 0.10.1", ] @@ -970,6 +1384,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57f6d249aad744e274e682777a50283a225a32705394ee6d5fcc01efa25e4055" dependencies = [ "aws-lc-rs", + "pem", + "ring", "rustls-pki-types", "time", "x509-parser", @@ -1005,6 +1421,15 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rusticata-macros" version = "4.1.0" @@ -1029,9 +1454,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.41" +version = "0.23.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" +checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" dependencies = [ "aws-lc-rs", "log", @@ -1127,6 +1552,12 @@ dependencies = [ "libc", ] +[[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" @@ -1183,6 +1614,17 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + [[package]] name = "sha2" version = "0.11.0" @@ -1190,8 +1632,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.3.0", + "digest 0.11.3", +] + +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest 0.10.7", + "keccak 0.1.6", +] + +[[package]] +name = "shake" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09057cb2149ad4cbd2da1e26b351f9a4c354219421229c69c3063e6f61947c4a" +dependencies = [ + "digest 0.11.3", + "keccak 0.2.0", + "sponge-cursor", ] [[package]] @@ -1210,8 +1673,9 @@ dependencies = [ "serde", "serde_json", "tempfile", - "thiserror", + "thiserror 2.0.18", "tokio", + "url", "uuid", ] @@ -1225,6 +1689,25 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "signature" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" +dependencies = [ + "digest 0.11.3", + "rand_core 0.10.1", +] + [[package]] name = "slab" version = "0.4.12" @@ -1239,14 +1722,40 @@ checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "socket2" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", "windows-sys 0.61.2", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der 0.7.10", +] + +[[package]] +name = "spki" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" +dependencies = [ + "base64ct", + "der 0.8.1", +] + +[[package]] +name = "sponge-cursor" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620" + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -1261,9 +1770,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.118" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" dependencies = [ "proc-macro2", "quote", @@ -1294,13 +1803,33 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1356,9 +1885,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" dependencies = [ "tinyvec_macros", ] @@ -1440,6 +1969,16 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.7", + "subtle", +] + [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -1478,9 +2017,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.23.4" +version = "1.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" +checksum = "ea5fab0d6c3c01ae70085a09cb03d4c7a1d6314e2b3e075392783396d724ca0a" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -1488,6 +2027,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -1709,9 +2254,9 @@ dependencies = [ "rustls", "rustls-native-certs", "rustls-pki-types", - "sha2", + "sha2 0.11.0", "socket2", - "thiserror", + "thiserror 2.0.18", "time", "tokio", "tracing", @@ -1728,10 +2273,22 @@ checksum = "d5867c629e4252f7439d82315923daaf27f4fa442410d51b78ab93ef4c432a11" dependencies = [ "httlib-huffman", "octets", - "thiserror", + "thiserror 2.0.18", "url", ] +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "serde", + "zeroize", +] + [[package]] name = "x509-parser" version = "0.18.1" @@ -1745,8 +2302,9 @@ dependencies = [ "lazy_static", "nom", "oid-registry", + "ring", "rusticata-macros", - "thiserror", + "thiserror 2.0.18", "time", ] @@ -1829,6 +2387,20 @@ name = "zeroize" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "zerotrie" @@ -1865,6 +2437,6 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml index c99c10c..d393b94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,12 @@ chrono = { version = "0.4", features = ["serde"] } thiserror = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" -mtp = { git = "https://git@git.methanium.net/methanium/mtp.git", features = [] } +mtp = { git = "https://git@git.methanium.net/methanium/mtp.git", features = [ + "crypto", +] } tokio = "1" async-trait = "0.1" +url = "2" [dev-dependencies] tokio = { version = "1", features = ["full"] } diff --git a/src/agents.rs b/src/agents.rs index 5936e34..b4d6d33 100644 --- a/src/agents.rs +++ b/src/agents.rs @@ -1,4 +1,5 @@ use serde::{Deserialize, Serialize}; +use crate::task::SessionBudget; /* ExploreAgent is intentionally limited to read-only tools so it can be dispatched without approval for safe, unrestricted codebase exploration. */ @@ -37,6 +38,35 @@ impl AgentType { _ => None, } } + + pub fn default_session_budget(&self) -> SessionBudget { + match self { + AgentType::BuildAgent => SessionBudget { + max_tool_calls: 50, + max_tokens: 100_000, + }, + AgentType::Planner => SessionBudget { + max_tool_calls: 30, + max_tokens: 50_000, + }, + AgentType::ExploreAgent => SessionBudget { + max_tool_calls: 10, + max_tokens: 5_000, + }, + AgentType::DocAgent => SessionBudget { + max_tool_calls: 20, + max_tokens: 20_000, + }, + AgentType::TestJudge => SessionBudget { + max_tool_calls: 10, + max_tokens: 10_000, + }, + AgentType::ChatAgent => SessionBudget { + max_tool_calls: 5, + max_tokens: 5_000, + }, + } + } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -46,6 +76,24 @@ pub enum ApprovalMode { PerTool, } +/// Controls what happens when a tool call requires approval and the user defers it. +/// Orthogonal to `ApprovalMode` (which controls *how* approvals are collected). +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[serde(rename_all = "snake_case")] +pub enum ApprovalTurnMode { + /// Block the agent turn; resume by injecting the approval result into the same turn. + Wait, + /// Return a placeholder result to the agent immediately ("Approval pending"); + /// the actual approval result is injected in a subsequent turn. + Defer, +} + +impl Default for ApprovalTurnMode { + fn default() -> Self { + ApprovalTurnMode::Wait + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PlannedAction { pub tool_name: String, @@ -70,6 +118,10 @@ pub struct AgentPolicy { pub max_tokens: u32, pub allowed_tools: Vec, pub forbidden_mutations: Vec, + /// Controls what happens when a tool call is deferred by the user. + /// `Wait` pauses the turn; `Defer` returns a placeholder immediately. + #[serde(default)] + pub turn_mode: ApprovalTurnMode, } impl AgentPolicy { @@ -84,6 +136,7 @@ impl AgentPolicy { max_tokens: 4096, allowed_tools: vec!["read_file".into(), "write_file".into(), "edit_file".into()], forbidden_mutations: vec!["Cargo.toml".into(), "package.json".into()], + turn_mode: ApprovalTurnMode::Defer, }, AgentType::TestJudge => Self { agent_type: agent_type.clone(), @@ -94,6 +147,7 @@ impl AgentPolicy { max_tokens: 4096, allowed_tools: vec![], forbidden_mutations: vec![], + turn_mode: ApprovalTurnMode::Defer, }, AgentType::BuildAgent => Self { agent_type: agent_type.clone(), @@ -104,6 +158,7 @@ impl AgentPolicy { max_tokens: 131072, allowed_tools: vec![], forbidden_mutations: vec!["Cargo.toml".into(), "package.json".into()], + turn_mode: ApprovalTurnMode::Wait, }, AgentType::Planner => Self { agent_type: agent_type.clone(), @@ -114,6 +169,7 @@ impl AgentPolicy { max_tokens: 131072, allowed_tools: vec![], forbidden_mutations: vec![], + turn_mode: ApprovalTurnMode::Defer, }, AgentType::ChatAgent => Self { agent_type: agent_type.clone(), @@ -124,6 +180,7 @@ impl AgentPolicy { max_tokens: 4096, allowed_tools: vec![], forbidden_mutations: vec![], + turn_mode: ApprovalTurnMode::Defer, }, /* ExploreAgent: auto-approved, read-only tools only, short timeout. Forbidden mutations set to wildcard to block all writes. */ @@ -143,6 +200,7 @@ impl AgentPolicy { "web_fetch".into(), ], forbidden_mutations: vec!["*".into()], + turn_mode: ApprovalTurnMode::Defer, }, } } diff --git a/src/ai_response.rs b/src/ai_response.rs index 95bdb93..9d52aff 100644 --- a/src/ai_response.rs +++ b/src/ai_response.rs @@ -21,53 +21,144 @@ pub struct AiConversation { } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct StreamEvent { - #[serde(rename = "type")] - pub event_type: String, - pub content: Option, - pub thinking: Option, - pub error: Option, - pub message_id: Option, +#[serde(tag = "type")] +pub enum StreamEvent { + #[serde(rename = "content")] + Content { content: String }, + + #[serde(rename = "thinking")] + Thinking { content: String }, + + #[serde(rename = "error")] + Error { error: String }, + + #[serde(rename = "done")] + Done { message_id: String }, + + #[serde(rename = "tool_call")] + ToolCall { + tool_name: String, + tool_id: String, + arguments: String, + }, + + #[serde(rename = "tool_executing")] + ToolExecuting { tool_name: String, tool_id: String }, + + #[serde(rename = "tool_result")] + ToolResult { + tool_name: String, + tool_id: String, + status: String, + result: String, + }, + + #[serde(rename = "checkpoint")] + Checkpoint { + checkpoint_id: String, + data: serde_json::Value, + }, + + #[serde(rename = "simulation_result")] + SimulationResult { simulation: serde_json::Value }, + + #[serde(rename = "debug")] + Debug { content: String }, + + #[serde(rename = "handshake")] + Handshake { + protocol_version: String, + #[serde(skip_serializing_if = "Option::is_none")] + warmup: Option, + }, + + #[serde(rename = "task_complete")] + TaskComplete, } impl StreamEvent { pub fn content(content: String) -> Self { - Self { - event_type: "content".to_string(), - content: Some(content), - thinking: None, - error: None, - message_id: None, - } + Self::Content { content } } - pub fn thinking(thinking: String) -> Self { - Self { - event_type: "thinking".to_string(), - content: None, - thinking: Some(thinking), - error: None, - message_id: None, - } + pub fn thinking(content: String) -> Self { + Self::Thinking { content } } pub fn error(error: String) -> Self { - Self { - event_type: "error".to_string(), - content: None, - thinking: None, - error: Some(error), - message_id: None, - } + Self::Error { error } } pub fn done(message_id: String) -> Self { - Self { - event_type: "done".to_string(), - content: None, - thinking: None, - error: None, - message_id: Some(message_id), + Self::Done { message_id } + } + + pub fn tool_call(tool_name: String, tool_id: String, arguments: String) -> Self { + Self::ToolCall { + tool_name, + tool_id, + arguments, + } + } + + pub fn tool_executing(tool_name: String, tool_id: String) -> Self { + Self::ToolExecuting { tool_name, tool_id } + } + + pub fn tool_result( + tool_name: String, + tool_id: String, + status: String, + result: String, + ) -> Self { + Self::ToolResult { + tool_name, + tool_id, + status, + result, + } + } + + pub fn checkpoint(checkpoint_id: String, data: serde_json::Value) -> Self { + Self::Checkpoint { + checkpoint_id, + data, + } + } + + pub fn simulation_result(simulation: serde_json::Value) -> Self { + Self::SimulationResult { simulation } + } + + pub fn debug(content: String) -> Self { + Self::Debug { content } + } + + pub fn handshake(protocol_version: String, warmup: Option) -> Self { + Self::Handshake { + protocol_version, + warmup, + } + } + + pub fn task_complete() -> Self { + Self::TaskComplete + } + + pub fn event_type(&self) -> &str { + match self { + Self::Content { .. } => "content", + Self::Thinking { .. } => "thinking", + Self::Error { .. } => "error", + Self::Done { .. } => "done", + Self::ToolCall { .. } => "tool_call", + Self::ToolExecuting { .. } => "tool_executing", + Self::ToolResult { .. } => "tool_result", + Self::Checkpoint { .. } => "checkpoint", + Self::SimulationResult { .. } => "simulation_result", + Self::Debug { .. } => "debug", + Self::Handshake { .. } => "handshake", + Self::TaskComplete => "task_complete", } } } diff --git a/src/enums.rs b/src/enums.rs index 750892a..e4befa6 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -1,6 +1,38 @@ use serde::{Deserialize, Serialize}; use std::fmt; +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum ToDoMode { + Discussion, + Finalized, +} + +impl Default for ToDoMode { + fn default() -> Self { + ToDoMode::Discussion + } +} + +impl fmt::Display for ToDoMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ToDoMode::Discussion => write!(f, "discussion"), + ToDoMode::Finalized => write!(f, "finalized"), + } + } +} + +impl std::str::FromStr for ToDoMode { + type Err = String; + fn from_str(s: &str) -> Result { + match s { + "discussion" => Ok(ToDoMode::Discussion), + "finalized" => Ok(ToDoMode::Finalized), + _ => Err(format!("unknown mode: {}", s)), + } + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum ToDoStatus { Pending, diff --git a/src/errors.rs b/src/errors.rs index 769c629..08ab112 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -20,6 +20,21 @@ pub enum ValidationError { InvalidField(String), } +impl ValidationError { + pub fn http_status(&self) -> u16 { + match self { + ValidationError::EmptyTitle + | ValidationError::TitleTooLong + | ValidationError::DescriptionTooLong + | ValidationError::InvalidPriority + | ValidationError::InvalidStatusTransition { .. } + | ValidationError::InvalidField(_) => 400, + ValidationError::CircularDependency(_) + | ValidationError::DependencyNotFound(_) => 409, + } + } +} + #[derive(Debug, Clone, Error)] pub enum ShoalError { #[error("storage error: {0}")] @@ -38,6 +53,20 @@ pub enum ShoalError { InternalError(String), } +impl ShoalError { + pub fn http_status(&self) -> u16 { + match self { + ShoalError::ValidationError(_) => 400, + ShoalError::NotFound(_) => 404, + ShoalError::Unauthorized(_) => 401, + ShoalError::StorageError(_) + | ShoalError::NetworkError(_) + | ShoalError::DatabaseError(_) + | ShoalError::InternalError(_) => 500, + } + } +} + impl From for ShoalError { fn from(e: ValidationError) -> Self { ShoalError::ValidationError(e.to_string()) diff --git a/src/krill.rs b/src/krill.rs index 3a8294e..b8316e4 100644 --- a/src/krill.rs +++ b/src/krill.rs @@ -3,6 +3,15 @@ use uuid::Uuid; use crate::enums::ModelType; +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub enum PodProtocolVersion { + V1, +} + +impl Default for PodProtocolVersion { + fn default() -> Self { Self::V1 } +} + pub type KrillId = Uuid; #[derive(Debug, Clone)] @@ -76,12 +85,14 @@ impl KrillDescriptor { #[derive(Debug, Clone, Default)] pub struct KrillConfig { pub settings: serde_json::Value, + pub pod_protocol_version: PodProtocolVersion, } impl KrillConfig { pub fn new() -> Self { Self { settings: serde_json::json!({}), + pod_protocol_version: PodProtocolVersion::default(), } } diff --git a/src/lib.rs b/src/lib.rs index 8c295c9..183cd88 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,17 +28,44 @@ pub use conclusion::{ pub use coral::{Coral, CoralId}; pub use enums::*; pub use errors::{ShoalError, ValidationError}; -pub use krill::{KrillConfig, KrillDescriptor, KrillId}; +pub use krill::{KrillConfig, KrillDescriptor, KrillId, PodProtocolVersion}; pub use mutations::FileMutation; pub use project::{Project, ProjectFile, ProjectId, ProjectSettings}; pub use task::{Task, TaskResult}; pub use todo::{Dependency, ToDo}; pub use tools::{ - format_tool_error, format_tool_result, format_tools_json, is_global_tool, is_reef_proxy_tool, + compute_tools_hash, format_tool_error, format_tool_result, format_tools_json, + is_global_tool, is_reef_proxy_tool, parse_tool_call_blocks, parse_tool_call_stream, tool_definitions, tool_ids_for_agent_type, ApprovalRequirement, CompositionStep, ContextVisibility, ExecutionContext, MarketplaceSearchResults, ParsedToolCall, TestAction, ToolComposition, ToolDefinition, ToolDependency, ToolDocumentation, ToolExample, ToolExecutor, ToolListing, ToolParser, - ToolPlugin, ToolRegistry, ToolTest, ToolTestResult, ToolTestSuite, ToolTestSuiteResult, - ToolVersion, GLOBAL_TOOLS, REEF_PROXY_TOOLS, + ToolPlugin, ToolRegistry, ToolState, ToolTest, ToolTestResult, ToolTestSuite, + ToolTestSuiteResult, ToolVersion, GLOBAL_TOOLS, REEF_PROXY_TOOLS, }; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn typemap_contains_expected_types() { + let tm = mtp::type_map::TypeMap::latest(); + assert!( + tm.data_id_enum(DataType::ProjectId).is_some(), + "TypeMap missing ProjectId" + ); + assert!( + tm.comm_id_enum(CommunicationType::AuthRequest).is_some(), + "TypeMap missing AuthRequest" + ); + assert!( + tm.comm_id_enum(CommunicationType::PasswordAuthRequest).is_some(), + "TypeMap missing PasswordAuthRequest" + ); + assert!( + tm.data_id_enum(DataType::DockId).is_some(), + "TypeMap missing DockId" + ); + } +} diff --git a/src/project.rs b/src/project.rs index 6e1f4ac..bcb6c6b 100644 --- a/src/project.rs +++ b/src/project.rs @@ -110,6 +110,8 @@ pub struct ProjectSettings { pub language: Option, pub build_command: Option, pub run_command: Option, + /// Conventional subject/body template used for generated git commits. + pub git_commit_template: Option, #[serde(flatten)] pub extra: serde_json::Value, } @@ -120,6 +122,7 @@ impl ProjectSettings { language: None, build_command: None, run_command: None, + git_commit_template: None, extra: serde_json::json!({}), } } @@ -139,6 +142,11 @@ impl ProjectSettings { self } + pub fn with_git_commit_template(mut self, template: String) -> Self { + self.git_commit_template = Some(template); + self + } + pub fn set_extra(&mut self, key: &str, value: serde_json::Value) { if let Some(obj) = self.extra.as_object_mut() { obj.insert(key.to_string(), value); diff --git a/src/task.rs b/src/task.rs index aaeece5..8ab94c5 100644 --- a/src/task.rs +++ b/src/task.rs @@ -183,6 +183,61 @@ pub struct ConnectorConfig { pub temperature: Option, } +impl ConnectorConfig { + pub const MIN_API_KEY_LEN: usize = 20; + + /// Validate the connector configuration fields. Returns a list of human-readable + /// error messages; an empty vec means the config is valid. + pub fn validate(&self) -> Vec { + let mut errors = Vec::new(); + + if self.api_key.trim().len() < Self::MIN_API_KEY_LEN { + errors.push(format!( + "api_key must be non-empty and at least {} characters", + Self::MIN_API_KEY_LEN + )); + } + + match url::Url::parse(&self.base_url) { + Ok(parsed) => { + if parsed.scheme() != "https" && parsed.scheme() != "http" { + errors.push(format!( + "base_url must use https or http scheme, got '{}'", + parsed.scheme() + )); + } + } + Err(e) => { + errors.push(format!("base_url is not a valid URL: {}", e)); + } + } + + if self.model.trim().is_empty() { + errors.push("model must be non-empty".to_string()); + } + + if let Some(tokens) = self.max_tokens { + if tokens <= 0 { + errors.push(format!( + "max_tokens must be > 0, got {}", + tokens + )); + } + } + + if let Some(temp) = self.temperature { + if !(0.0..=2.0).contains(&temp) { + errors.push(format!( + "temperature must be in [0.0, 2.0], got {}", + temp + )); + } + } + + errors + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AgentTaskSpec { pub task_id: Uuid, @@ -200,6 +255,10 @@ pub struct AgentTaskSpec { pub pre_approved_plan_id: Option, pub execution_token: Option, pub session_budget: Option, + /// Maximum number of checklist tasks this agent run may add. `None` leaves + /// task creation unconstrained for backwards-compatible callers. + #[serde(default)] + pub task_budget: Option, pub agent_type: Option, #[serde(default)] pub suggested_test_files: Vec, @@ -209,10 +268,19 @@ pub struct AgentTaskSpec { pub connector_config: Option, #[serde(default)] pub tools_json: Option, + /// blake3-style hash of `tools_json` for deduplication across dispatches. + /// When present, the receiver may skip the full payload if the hash is cached. + #[serde(default)] + pub tools_hash: Option, /* Overrides the agent-type default system prompt when set. Takes highest precedence in the resolution chain (above DB overrides and defaults). */ #[serde(default)] pub system_prompt: Option, + /// When set, the Pod reads the latest checkpoint from SQLite and injects the + /// completed tool calls into the system message so the LLM resumes instead + /// of restarting from scratch on retry. + #[serde(default)] + pub resume_from_checkpoint: Option, } impl AgentTaskSpec { @@ -238,12 +306,15 @@ impl AgentTaskSpec { pre_approved_plan_id: None, execution_token: None, session_budget: None, + task_budget: None, agent_type: None, suggested_test_files: Vec::new(), project_id: None, connector_config: None, tools_json: None, + tools_hash: None, system_prompt: None, + resume_from_checkpoint: None, } } } diff --git a/src/todo.rs b/src/todo.rs index 9147757..0551472 100644 --- a/src/todo.rs +++ b/src/todo.rs @@ -2,7 +2,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::enums::{ToDoSource, ToDoStatus}; +use crate::enums::{ToDoMode, ToDoSource, ToDoStatus}; use crate::errors::ValidationError; use std::collections::HashSet; @@ -36,6 +36,7 @@ pub struct ToDo { pub subtask_ids: Vec, pub affected_files: Vec, pub source: ToDoSource, + pub mode: ToDoMode, pub automation_chain_id: Option, pub tags: Vec, pub estimated_tokens: u32, @@ -62,6 +63,7 @@ impl Default for ToDo { subtask_ids: Vec::new(), affected_files: Vec::new(), source: ToDoSource::User, + mode: ToDoMode::default(), automation_chain_id: None, tags: Vec::new(), estimated_tokens: 0, @@ -90,6 +92,7 @@ impl ToDo { subtask_ids: Vec::new(), affected_files: Vec::new(), source: ToDoSource::User, + mode: ToDoMode::default(), automation_chain_id: None, tags: Vec::new(), estimated_tokens: 0, @@ -199,6 +202,8 @@ impl ToDo { (ToDoStatus::Failed, ToDoStatus::ReadyForAgent) => true, (ToDoStatus::PendingApproval, ToDoStatus::InProgress) => true, (ToDoStatus::PendingApproval, ToDoStatus::Blocked) => true, + (ToDoStatus::Draft, ToDoStatus::Pending) => true, + (ToDoStatus::Pending, ToDoStatus::Draft) => true, _ => self.status == status, }; diff --git a/src/tools.rs b/src/tools.rs index 838ef0f..7bfb86c 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -516,6 +516,48 @@ impl Default for ToolVersion { } } +/* Dynamic runtime state of a tool, managed per-project by `ToolStateManager`. +This replaces the static `deprecated` bool on `ToolDefinition` with a richer +taxonomy that operators can toggle without redeploying. */ +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum ToolState { + Enabled, + Disabled, + Deprecated, + Experimental, +} + +impl Default for ToolState { + fn default() -> Self { + Self::Enabled + } +} + +impl ToolState { + pub fn as_str(&self) -> &'static str { + match self { + Self::Enabled => "enabled", + Self::Disabled => "disabled", + Self::Deprecated => "deprecated", + Self::Experimental => "experimental", + } + } + + pub fn from_str(s: &str) -> Self { + match s { + "disabled" => Self::Disabled, + "deprecated" => Self::Deprecated, + "experimental" => Self::Experimental, + _ => Self::Enabled, + } + } + + pub fn is_available(&self) -> bool { + matches!(self, Self::Enabled | Self::Experimental) + } +} + /* Tool category classification for UI grouping and filtering. */ #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] @@ -849,7 +891,13 @@ pub const PLANNER_ONLY_TOOLS: &[&str] = &[ Mutating tools (write_file, edit_file, bash, delete_workspace) are intentionally NOT here: they stay Everywhere so direct chat can call them under approval routing (Phase 4). Only genuinely agent-internal collaboration tools are agent-only. */ -pub const AGENT_ONLY_TOOLS: &[&str] = &["submit_batch_plan", "report_completion"]; +pub const AGENT_ONLY_TOOLS: &[&str] = &[ + "submit_batch_plan", + "report_completion", + "kanban_add_task", + "kanban_complete_task", + "kanban_remove_task", +]; /* Tools that proxy to the Reef API when a `REEF_URL` / `REEF_API_URL` is configured. These tools have both a local implementation and a Reef-backed @@ -899,7 +947,9 @@ pub fn tool_definitions() -> Vec { "read_file", "Read the content of a file with optional pagination", "filesystem", - false, false, false, + false, + false, + false, serde_json::json!({ "type": "object", "properties": { @@ -914,7 +964,9 @@ pub fn tool_definitions() -> Vec { "write_file", "Create or overwrite a file with content", "filesystem", - true, true, false, + true, + true, + false, serde_json::json!({ "type": "object", "properties": { @@ -929,7 +981,9 @@ pub fn tool_definitions() -> Vec { "list_directory", "List directory contents with optional filtering", "filesystem", - false, false, false, + false, + false, + false, serde_json::json!({ "type": "object", "properties": { @@ -944,7 +998,9 @@ pub fn tool_definitions() -> Vec { "edit_file", "Edit file contents with line-based operations (replace, insert, delete)", "filesystem", - true, true, false, + true, + true, + false, serde_json::json!({ "type": "object", "properties": { @@ -971,7 +1027,9 @@ pub fn tool_definitions() -> Vec { "search_files", "Search for text patterns in files (grep-like)", "filesystem", - false, false, false, + false, + false, + false, serde_json::json!({ "type": "object", "properties": { @@ -982,7 +1040,6 @@ pub fn tool_definitions() -> Vec { "required": ["path", "pattern"] }) ), - // ======================== // Execution Tools // ======================== @@ -990,7 +1047,9 @@ pub fn tool_definitions() -> Vec { "bash", "Execute a shell command and capture output", "execution", - true, true, false, + true, + true, + false, serde_json::json!({ "type": "object", "properties": { @@ -1001,7 +1060,6 @@ pub fn tool_definitions() -> Vec { "required": ["command"] }) ), - // ======================== // Git Tools // ======================== @@ -1009,7 +1067,9 @@ pub fn tool_definitions() -> Vec { "git_status", "Show working tree status in a git repository", "git", - false, false, false, + false, + false, + false, serde_json::json!({ "type": "object", "properties": { @@ -1022,7 +1082,9 @@ pub fn tool_definitions() -> Vec { "git_diff", "Show changes between commits, commit and working tree, etc.", "git", - false, false, false, + false, + false, + false, serde_json::json!({ "type": "object", "properties": { @@ -1036,7 +1098,9 @@ pub fn tool_definitions() -> Vec { "git_log", "Show commit history in a git repository", "git", - false, false, false, + false, + false, + false, serde_json::json!({ "type": "object", "properties": { @@ -1049,7 +1113,9 @@ pub fn tool_definitions() -> Vec { "git_branch", "List or manage git branches", "git", - false, false, false, + false, + false, + false, serde_json::json!({ "type": "object", "properties": { @@ -1058,7 +1124,6 @@ pub fn tool_definitions() -> Vec { } }) ), - // ======================== // Build & Test Tools // ======================== @@ -1066,7 +1131,9 @@ pub fn tool_definitions() -> Vec { "cargo_check", "Run cargo check on a Rust project to verify compilation", "build", - false, false, false, + false, + false, + false, serde_json::json!({ "type": "object", "properties": { @@ -1079,7 +1146,9 @@ pub fn tool_definitions() -> Vec { "npm_build", "Run npm build script in a JavaScript/TypeScript project", "build", - false, false, false, + false, + false, + false, serde_json::json!({ "type": "object", "properties": { @@ -1092,7 +1161,9 @@ pub fn tool_definitions() -> Vec { "python_check", "Check Python syntax or run linting on a Python file", "build", - false, false, false, + false, + false, + false, serde_json::json!({ "type": "object", "properties": { @@ -1105,7 +1176,9 @@ pub fn tool_definitions() -> Vec { "test_runner", "Run project tests with a test runner like pytest", "build", - false, false, false, + false, + false, + false, serde_json::json!({ "type": "object", "properties": { @@ -1115,7 +1188,6 @@ pub fn tool_definitions() -> Vec { } }) ), - // ======================== // Network Tools // ======================== @@ -1123,7 +1195,9 @@ pub fn tool_definitions() -> Vec { "web_search", "Search the web for information using a search provider", "network", - false, false, false, + false, + false, + false, serde_json::json!({ "type": "object", "properties": { @@ -1138,7 +1212,9 @@ pub fn tool_definitions() -> Vec { "web_fetch", "Fetch and parse web pages from URLs", "network", - false, false, false, + false, + false, + false, serde_json::json!({ "type": "object", "properties": { @@ -1152,7 +1228,9 @@ pub fn tool_definitions() -> Vec { "web_api", "Make HTTP API requests to external services", "network", - false, false, false, + false, + false, + false, serde_json::json!({ "type": "object", "properties": { @@ -1163,7 +1241,6 @@ pub fn tool_definitions() -> Vec { "required": ["url", "method"] }) ), - // ======================== // Workspace Tools // ======================== @@ -1171,7 +1248,9 @@ pub fn tool_definitions() -> Vec { "create_workspace", "Create an ephemeral workspace directory for isolated operations", "workspace", - false, false, false, + false, + false, + false, serde_json::json!({ "type": "object", "properties": { @@ -1184,7 +1263,9 @@ pub fn tool_definitions() -> Vec { "delete_workspace", "Delete an ephemeral workspace and all its contents", "workspace", - true, true, false, + true, + true, + false, serde_json::json!({ "type": "object", "properties": { @@ -1198,7 +1279,9 @@ pub fn tool_definitions() -> Vec { "create_venv", "Create a Python virtual environment", "workspace", - false, false, false, + false, + false, + false, serde_json::json!({ "type": "object", "properties": { @@ -1212,7 +1295,9 @@ pub fn tool_definitions() -> Vec { "install_dependencies", "Install Python dependencies from requirements.txt or specified packages", "workspace", - false, false, false, + false, + false, + false, serde_json::json!({ "type": "object", "properties": { @@ -1226,7 +1311,9 @@ pub fn tool_definitions() -> Vec { "workspace_info", "Get information and statistics about a workspace directory", "workspace", - false, false, false, + false, + false, + false, serde_json::json!({ "type": "object", "properties": { @@ -1235,7 +1322,6 @@ pub fn tool_definitions() -> Vec { "required": ["path"] }) ), - // ======================== // Kanban Tools // ======================== @@ -1243,7 +1329,9 @@ pub fn tool_definitions() -> Vec { "kanban_list_board", "List all columns and todos in the Kanban board", "kanban", - false, false, true, + false, + false, + true, serde_json::json!({ "type": "object", "properties": { @@ -1255,10 +1343,12 @@ pub fn tool_definitions() -> Vec { ), tool!( "kanban_create_todo", - "Create a new todo item in a Kanban board", - "kanban", - false, false, true, - serde_json::json!({ + "Create a new todo item in a Kanban board", + "kanban", + false, + false, + true, + serde_json::json!({ "type": "object", "properties": { "project_id": { "type": "string", "description": "Project ID" }, @@ -1280,7 +1370,9 @@ pub fn tool_definitions() -> Vec { "kanban_update_todo", "Update a todo item's title, description, status, or priority", "kanban", - false, false, true, + false, + false, + true, serde_json::json!({ "type": "object", "properties": { @@ -1298,7 +1390,9 @@ pub fn tool_definitions() -> Vec { "kanban_delete_todo", "Delete a todo item from the Kanban board", "kanban", - false, false, true, + false, + false, + true, serde_json::json!({ "type": "object", "properties": { @@ -1310,25 +1404,29 @@ pub fn tool_definitions() -> Vec { ), tool!( "kanban_move_todo", - "Move a todo item to a different status", - "kanban", - false, false, true, - serde_json::json!({ - "type": "object", - "properties": { - "project_id": { "type": "string", "description": "Project ID" }, - "todo_id": { "type": "string", "description": "ID of the todo to move" }, - "status": { "type": "string", "enum": ["pending","in_progress","completed","blocked","ready_for_agent","delegated","failed","pending_approval"], "description": "Target status" }, - "task_order": { "type": "number", "description": "New order position" } - }, - "required": ["project_id", "todo_id"] - }) + "Move a todo item to a different status", + "kanban", + false, + false, + true, + serde_json::json!({ + "type": "object", + "properties": { + "project_id": { "type": "string", "description": "Project ID" }, + "todo_id": { "type": "string", "description": "ID of the todo to move" }, + "status": { "type": "string", "enum": ["pending","in_progress","completed","blocked","ready_for_agent","delegated","failed","pending_approval"], "description": "Target status" }, + "task_order": { "type": "number", "description": "New order position" } + }, + "required": ["project_id", "todo_id"] + }) ), tool!( "kanban_create_task", "Create a sub-task within a todo", "kanban", - false, false, true, + false, + false, + true, serde_json::json!({ "type": "object", "properties": { @@ -1344,7 +1442,9 @@ pub fn tool_definitions() -> Vec { "kanban_update_task", "Update a sub-task's title, description, or status", "kanban", - false, false, true, + false, + false, + true, serde_json::json!({ "type": "object", "properties": { @@ -1357,11 +1457,64 @@ pub fn tool_definitions() -> Vec { "required": ["project_id", "task_id"] }) ), + tool!( + "kanban_add_task", + "Add a checklist task to the todo owned by this agent run", + "kanban", + false, + false, + true, + serde_json::json!({ + "type": "object", + "properties": { + "project_id": { "type": "string", "description": "Project ID" }, + "todo_id": { "type": "string", "description": "ID of the parent todo" }, + "title": { "type": "string", "description": "Checklist task title" }, + "description": { "type": "string", "description": "Optional description" } + }, + "required": ["project_id", "todo_id", "title"] + }) + ), + tool!( + "kanban_complete_task", + "Mark a checklist task complete or incomplete on the todo owned by this agent run", + "kanban", + false, + false, + true, + serde_json::json!({ + "type": "object", + "properties": { + "project_id": { "type": "string", "description": "Project ID" }, + "task_id": { "type": "string", "description": "Checklist task ID" }, + "status": { "type": "string", "enum": ["completed", "pending"], "description": "Target status (defaults to completed)" } + }, + "required": ["project_id", "task_id"] + }) + ), + tool!( + "kanban_remove_task", + "Remove a checklist task from the todo owned by this agent run", + "kanban", + false, + false, + true, + serde_json::json!({ + "type": "object", + "properties": { + "project_id": { "type": "string", "description": "Project ID" }, + "task_id": { "type": "string", "description": "Checklist task ID" } + }, + "required": ["project_id", "task_id"] + }) + ), tool!( "kanban_add_tag", "Add a tag to a todo item", "kanban", - false, false, true, + false, + false, + true, serde_json::json!({ "type": "object", "properties": { @@ -1380,7 +1533,9 @@ pub fn tool_definitions() -> Vec { "document_file", "Analyze and generate documentation for a file", "documentation", - false, false, true, + false, + false, + true, serde_json::json!({ "type": "object", "properties": { @@ -1394,7 +1549,9 @@ pub fn tool_definitions() -> Vec { "document_project", "Generate a comprehensive summary of all project files", "documentation", - false, false, true, + false, + false, + true, serde_json::json!({ "type": "object", "properties": { @@ -1407,7 +1564,9 @@ pub fn tool_definitions() -> Vec { "find_references", "Find all references to a symbol across the project", "documentation", - false, false, true, + false, + false, + true, serde_json::json!({ "type": "object", "properties": { @@ -1423,7 +1582,9 @@ pub fn tool_definitions() -> Vec { "file_dependencies", "Get incoming or outgoing dependencies for a file", "documentation", - false, false, true, + false, + false, + true, serde_json::json!({ "type": "object", "properties": { @@ -1438,7 +1599,9 @@ pub fn tool_definitions() -> Vec { "documentation_tree", "Build a directory tree showing documentation coverage", "documentation", - false, false, true, + false, + false, + true, serde_json::json!({ "type": "object", "properties": { @@ -1452,7 +1615,9 @@ pub fn tool_definitions() -> Vec { "verify_documentation", "Verify documentation freshness and compute coverage stats", "documentation", - false, false, true, + false, + false, + true, serde_json::json!({ "type": "object", "properties": { @@ -1465,7 +1630,9 @@ pub fn tool_definitions() -> Vec { "document_folder", "Document all files within a folder", "documentation", - false, false, true, + false, + false, + true, serde_json::json!({ "type": "object", "properties": { @@ -1479,7 +1646,9 @@ pub fn tool_definitions() -> Vec { "store_file_doc", "Store documentation for a file", "documentation", - false, false, true, + false, + false, + true, serde_json::json!({ "type": "object", "properties": { @@ -1502,7 +1671,9 @@ pub fn tool_definitions() -> Vec { "get_documentation_context", "Get the documentation context for a file", "documentation", - false, false, true, + false, + false, + true, serde_json::json!({ "type": "object", "properties": { @@ -1512,7 +1683,6 @@ pub fn tool_definitions() -> Vec { "required": ["project_id", "file_path"] }) ), - // ======================== // File Tools // ======================== @@ -1520,7 +1690,9 @@ pub fn tool_definitions() -> Vec { "list_files", "List all files in a project", "files", - false, false, true, + false, + false, + true, serde_json::json!({ "type": "object", "properties": { @@ -1530,7 +1702,6 @@ pub fn tool_definitions() -> Vec { "required": ["project_id"] }) ), - // ======================== // Strategic Tools // ======================== @@ -1538,7 +1709,9 @@ pub fn tool_definitions() -> Vec { "propose_strategic_item", "Propose a new high-level strategic goal or todo", "strategic", - false, true, true, + false, + true, + true, serde_json::json!({ "type": "object", "properties": { @@ -1555,7 +1728,9 @@ pub fn tool_definitions() -> Vec { "split_task", "Split a Pending todo into multiple subtasks with proper dependency tracking, depth validation, and cycle detection. Each subtask should be independently deployable to an agent.", "strategic", - false, false, true, + false, + false, + true, serde_json::json!({ "type": "object", "properties": { @@ -1583,7 +1758,9 @@ pub fn tool_definitions() -> Vec { "change_planner_mode", "Switch the Planner's working mode. Modes: cooperative (fast shipping), critical (audit), red_team (pre-mortem), socratic (discovery), execution (task decomposition).", "strategic", - false, true, true, + false, + true, + true, serde_json::json!({ "type": "object", "properties": { @@ -1598,7 +1775,9 @@ pub fn tool_definitions() -> Vec { "audit_assumptions", "List the user's current assumptions about the project and identify the weakest ones", "strategic", - false, false, true, + false, + false, + true, serde_json::json!({ "type": "object", "properties": { @@ -1611,7 +1790,9 @@ pub fn tool_definitions() -> Vec { "identify_blind_spots", "Query project data for missing dependency chains, undocumented risks, and recurring blockers", "strategic", - false, false, true, + false, + false, + true, serde_json::json!({ "type": "object", "properties": { @@ -1624,7 +1805,9 @@ pub fn tool_definitions() -> Vec { "check_dependencies", "Analyze todo dependency chains for circular or missing dependencies", "strategic", - false, false, true, + false, + false, + true, serde_json::json!({ "type": "object", "properties": { @@ -1637,7 +1820,9 @@ pub fn tool_definitions() -> Vec { "evaluate_plan_risk", "Score the current plan for feasibility, edge-case coverage, and alignment", "strategic", - false, false, true, + false, + false, + true, serde_json::json!({ "type": "object", "properties": { @@ -1650,7 +1835,9 @@ pub fn tool_definitions() -> Vec { "compare_project_patterns", "Compare kanban structures across projects to detect inconsistencies", "strategic", - false, false, false, + false, + false, + false, serde_json::json!({ "type": "object", "properties": { @@ -1662,7 +1849,9 @@ pub fn tool_definitions() -> Vec { "find_similar_risks", "Search across projects for similar risk patterns", "strategic", - false, false, false, + false, + false, + false, serde_json::json!({ "type": "object", "properties": { @@ -1671,7 +1860,6 @@ pub fn tool_definitions() -> Vec { } }) ), - // ======================== // Agent Tools // ======================== @@ -1679,7 +1867,9 @@ pub fn tool_definitions() -> Vec { "report_completion", "Report completion status of a todo back to the Bridge", "agent", - false, false, false, + false, + false, + false, serde_json::json!({ "type": "object", "properties": { @@ -1697,7 +1887,9 @@ pub fn tool_definitions() -> Vec { "submit_batch_plan", "Submit a batch plan of mutations for pre-approval before execution", "agent", - false, false, false, + false, + false, + false, serde_json::json!({ "type": "object", "properties": { @@ -1798,6 +1990,9 @@ pub fn tool_definitions() -> Vec { ("kanban_move_todo", 2), ("kanban_create_task", 2), ("kanban_update_task", 2), + ("kanban_add_task", 1), + ("kanban_complete_task", 1), + ("kanban_remove_task", 1), ("create_workspace", 2), ("split_task", 2), // Depth 3 — needs deep reasoning @@ -1933,11 +2128,37 @@ pub fn format_tools_json(tools: &[ToolDefinition]) -> Option { serde_json::to_string(&formatted).ok() } +/// Deterministic hash of a tools JSON string using std DefaultHasher. +/// The hex-encoded result is used as a deduplication key across dispatch/pod. +pub fn compute_tools_hash(tools_json: &str) -> String { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + let mut hasher = DefaultHasher::new(); + tools_json.hash(&mut hasher); + format!("{:016x}", hasher.finish()) +} + #[cfg(test)] mod tests { use super::*; use serde_json::json; + #[test] + fn test_compute_tools_hash_deterministic() { + let input = r#"[{"type":"function","function":{"name":"foo","description":"bar","parameters":{}}}]"#; + let h1 = compute_tools_hash(input); + let h2 = compute_tools_hash(input); + assert_eq!(h1, h2); + assert_eq!(h1.len(), 16); + } + + #[test] + fn test_compute_tools_hash_different_inputs() { + let a = compute_tools_hash(r#"aaa"#); + let b = compute_tools_hash(r#"bbb"#); + assert_ne!(a, b); + } + #[test] fn test_parse_single_block() { let text = r#" @@ -2213,10 +2434,12 @@ Done with tools. let results = parse_tool_call_blocks(text); assert_eq!(results.len(), 1); assert_eq!(results[0].call_id, "call_01"); - assert!(results[0].payload["content"] - .as_str() - .unwrap_or("") - .contains("🎉")); + assert!( + results[0].payload["content"] + .as_str() + .unwrap_or("") + .contains("🎉") + ); } #[test] @@ -2465,6 +2688,22 @@ Done with tools. } } + #[test] + fn checklist_task_tools_are_agent_only_at_depth_one() { + for id in [ + "kanban_add_task", + "kanban_complete_task", + "kanban_remove_task", + ] { + let tool = tool_definitions() + .into_iter() + .find(|tool| tool.id == id) + .expect("checklist task tool must be registered"); + assert_eq!(tool.context_visibility, ContextVisibility::AgentOnly); + assert_eq!(tool.required_depth, 1); + } + } + #[test] fn test_category_backstop_keeps_sensitive_tools_fail_closed() { /* Invariant enforced by the category backstop: no strategic tool is ever diff --git a/type-maps.yaml b/type-maps.yaml deleted file mode 100644 index f3558b4..0000000 --- a/type-maps.yaml +++ /dev/null @@ -1,118 +0,0 @@ -protocol_version: "1.0" - -type_maps: - "1.0": - CommunicationTypes: - ReservoirHeartbeat: 32 - ReservoirRequest: 33 - ReservoirStore: 34 - AuthRequest: 35 - AuthResponse: 36 - ProjectRequest: 37 - ProjectResponse: 38 - KanbanRequest: 39 - KanbanResponse: 40 - DiscussRequest: 41 - DiscussResponse: 42 - DiscussStreamChunk: 43 - DiscussStreamDone: 44 - ToolRequest: 45 - ToolResponse: 46 - AgentRequest: 47 - AgentResponse: 48 - AdminRequest: 49 - AdminResponse: 50 - SyncRequest: 51 - SyncResponse: 52 - PodRequest: 53 - PodResponse: 54 - LookoutEvent: 55 - EventBroadcast: 56 - ErrorResponse: 57 - KrillIoInitialize: 58 - KrillIoInitialized: 59 - KrillIoAdvertiseSkills: 60 - KrillIoSkillsAdvertised: 61 - KrillIoQuerySkills: 62 - KrillIoQuerySkillsResponse: 63 - KrillIoMessage: 64 - KrillIoMessageResponse: 65 - KrillIoToolCallRequest: 66 - KrillIoToolCallResponse: 67 - KrillIoListTools: 68 - KrillIoListToolsResponse: 69 - KrillIoPauseSession: 70 - KrillIoResumeSession: 71 - KrillIoTerminateSession: 72 - KrillIoSessionStateChanged: 73 - KrillIoHeartbeat: 74 - KrillIoHeartbeatResponse: 75 - KrillIoLoadModel: 76 - KrillIoModelLoaded: 77 - KrillIoUnloadModel: 78 - KrillIoMemoryReport: 79 - KrillIoImmediateKill: 80 - KrillIoKilled: 81 - PodTaskAvailable: 82 - PodDiscussAbort: 83 - DataTypes: - ProjectId: 32 - RequestType: 33 - ResponseData: 34 - Data: 35 - FilePath: 36 - FileData: 37 - SessionId: 38 - KrillId: 39 - KrillIoVersion: 40 - MaxHistoryLength: 41 - DefaultTimeoutMs: 42 - Enabled: 43 - ToolError: 44 - SkillsJson: 45 - Content: 46 - MessageRole: 47 - Response: 48 - TokensUsed: 49 - ToolId: 50 - ToolCallId: 51 - ToolArguments: 52 - Timeout: 53 - ToolSuccess: 54 - ExecutionTimeMs: 55 - ToolResult: 56 - ToolDefinitionsList: 57 - Graceful: 58 - SessionState: 59 - ModelPath: 60 - LoadIntoVram: 61 - WeightsPath: 62 - Path: 63 - RamUsageMb: 64 - VramMb: 65 - KillReason: 66 - SaveCheckpoint: 67 - AuthToken: 68 - UserId: 69 - Username: 70 - Email: 71 - Password: 72 - BoardId: 73 - TodoId: 74 - TaskId: 75 - TagId: 76 - ChatId: 77 - MessageId: 78 - AgentRunId: 79 - Payload: 80 - EventType: 81 - EventPayload: 82 - StreamSessionId: 83 - ContentChunk: 84 - StreamComplete: 85 - FileHash: 86 - FileSize: 87 - FileContent: 88 - ErrorCode: 89 - PageOffset: 90 - PageLimit: 91