Compare commits

...
3 Commits
Author SHA1 Message Date
jaberjaber23 acf2587e46 bump v0.6.9 2026-05-12 21:42:08 +03:00
jaberjaber23 4583157b49 audit fixes 2026-05-12 21:13:41 +03:00
jaberjaber23 7185ea8808 cargo fmt 2026-05-12 20:57:57 +03:00
23 changed files with 510 additions and 368 deletions
Generated
+90 -90
View File
@@ -139,7 +139,7 @@ version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -150,7 +150,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -893,7 +893,7 @@ version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -1029,27 +1029,27 @@ dependencies = [
[[package]]
name = "cranelift-assembler-x64"
version = "0.130.1"
version = "0.130.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "046d4b584c3bb9b5eb500c8f29549bec36be11000f1ba2a927cef3d1a9875691"
checksum = "adc822414b18d1f5b1b33ce1441534e311e62fef86ebb5b9d382af857d0272c9"
dependencies = [
"cranelift-assembler-x64-meta",
]
[[package]]
name = "cranelift-assembler-x64-meta"
version = "0.130.1"
version = "0.130.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9b194a7870becb1490366fc0ae392ccd188065ff35f8391e77ac659db6fb977"
checksum = "8c646808b06f4532478d8d6057d74f15c3322f10d995d9486e7dcea405bf521a"
dependencies = [
"cranelift-srcgen",
]
[[package]]
name = "cranelift-bforest"
version = "0.130.1"
version = "0.130.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb6a4ab44c6b371e661846b97dab687387a60ac4e2f864e2d4257284aad9e889"
checksum = "7b5996f01a686b2349cdb379083ec5ad3e8cb8767fb2d495d3a4f2ee4163a18d"
dependencies = [
"cranelift-entity",
"wasmtime-internal-core",
@@ -1057,9 +1057,9 @@ dependencies = [
[[package]]
name = "cranelift-bitset"
version = "0.130.1"
version = "0.130.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8b7a44150c2f471a94023482bda1902710746e4bed9f9973d60c5a94319b06d"
checksum = "523fea83273f6a985520f57788809a4de2165794d9ab00fb1254fceb4f5aa00c"
dependencies = [
"serde",
"serde_derive",
@@ -1068,9 +1068,9 @@ dependencies = [
[[package]]
name = "cranelift-codegen"
version = "0.130.1"
version = "0.130.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01b06598133b1dd76758b8b95f8d6747c124124aade50cea96a3d88b962da9fa"
checksum = "d73d1e372730b5f64ed1a2bd9f01fe4686c8ec14a28034e3084e530c8d951878"
dependencies = [
"bumpalo",
"cranelift-assembler-x64",
@@ -1096,9 +1096,9 @@ dependencies = [
[[package]]
name = "cranelift-codegen-meta"
version = "0.130.1"
version = "0.130.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6190e2e7bcf0a678da2f715363d34ed530fedf7a2f0ab75edaefef72a70465ff"
checksum = "b0319c18165e93dc1ebf78946a8da0b1c341c95b4a39729a69574671639bdb5f"
dependencies = [
"cranelift-assembler-x64-meta",
"cranelift-codegen-shared",
@@ -1109,24 +1109,24 @@ dependencies = [
[[package]]
name = "cranelift-codegen-shared"
version = "0.130.1"
version = "0.130.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f583cf203d1aa8b79560e3b01f929bdacf9070b015eec4ea9c46e22a3f83e4a0"
checksum = "9195cd8aeecb55e401aa96b2eaa55921636e8246c127ed7908f7ef7e0d40f270"
[[package]]
name = "cranelift-control"
version = "0.130.1"
version = "0.130.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "803159df35cc398ae54473c150b16d6c77e92ab2948be638488de126a3328fbc"
checksum = "8976c2154b74136322befc74222ab5c7249edd7e2604f8cbef2b94975541ffb9"
dependencies = [
"arbitrary",
]
[[package]]
name = "cranelift-entity"
version = "0.130.1"
version = "0.130.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3109e417257082d88087f5bcce677525bdaa8322b88dd7f175ed1a1fd41d546c"
checksum = "6038b3147c7982f4951150d5f96c7c06c1e7214b99d4b4a98607aadf8ded89d1"
dependencies = [
"cranelift-bitset",
"serde",
@@ -1136,9 +1136,9 @@ dependencies = [
[[package]]
name = "cranelift-frontend"
version = "0.130.1"
version = "0.130.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14db6b0e0e4994c581092df78d837be2072578f7cb2528f96a6cf895e56dee63"
checksum = "4cbd294abe236e23cc3d907b0936226b6a8342db7636daa9c7c72be1e323420e"
dependencies = [
"cranelift-codegen",
"log",
@@ -1148,15 +1148,15 @@ dependencies = [
[[package]]
name = "cranelift-isle"
version = "0.130.1"
version = "0.130.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec66ea5025c7317383699778282ac98741d68444f956e3b1d7b62f12b7216e67"
checksum = "b5a90b6ed3aba84189352a87badeb93b2126d3724225a42dc67fdce53d1b139c"
[[package]]
name = "cranelift-native"
version = "0.130.1"
version = "0.130.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373ade56438e6232619d85678477d0a88a31b3581936e0503e61e96b546b0800"
checksum = "c3ec0cc1a54e22925eacf4fc3dc815f907734d3b377899d19d52bec04863e853"
dependencies = [
"cranelift-codegen",
"libc",
@@ -1165,9 +1165,9 @@ dependencies = [
[[package]]
name = "cranelift-srcgen"
version = "0.130.1"
version = "0.130.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef53619d3cd5c78fd998c6d9420547af26b72e6456f94c2a8a2334cb76b42baa"
checksum = "948865622f87f30907bb46fbb081b235ae63c1896a99a83c26a003305c1fa82d"
[[package]]
name = "crc32fast"
@@ -1555,7 +1555,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users 0.5.2",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -1805,7 +1805,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -3739,7 +3739,7 @@ version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -3959,7 +3959,7 @@ dependencies = [
[[package]]
name = "openfang-api"
version = "0.6.8"
version = "0.6.9"
dependencies = [
"argon2",
"async-trait",
@@ -4001,7 +4001,7 @@ dependencies = [
[[package]]
name = "openfang-channels"
version = "0.6.8"
version = "0.6.9"
dependencies = [
"aes",
"async-trait",
@@ -4040,7 +4040,7 @@ dependencies = [
[[package]]
name = "openfang-cli"
version = "0.6.8"
version = "0.6.9"
dependencies = [
"clap",
"clap_complete",
@@ -4068,7 +4068,7 @@ dependencies = [
[[package]]
name = "openfang-desktop"
version = "0.6.8"
version = "0.6.9"
dependencies = [
"axum",
"open",
@@ -4094,7 +4094,7 @@ dependencies = [
[[package]]
name = "openfang-extensions"
version = "0.6.8"
version = "0.6.9"
dependencies = [
"aes-gcm",
"argon2",
@@ -4122,7 +4122,7 @@ dependencies = [
[[package]]
name = "openfang-hands"
version = "0.6.8"
version = "0.6.9"
dependencies = [
"chrono",
"dashmap",
@@ -4142,7 +4142,7 @@ dependencies = [
[[package]]
name = "openfang-kernel"
version = "0.6.8"
version = "0.6.9"
dependencies = [
"async-trait",
"chrono",
@@ -4182,7 +4182,7 @@ dependencies = [
[[package]]
name = "openfang-memory"
version = "0.6.8"
version = "0.6.9"
dependencies = [
"async-trait",
"chrono",
@@ -4202,7 +4202,7 @@ dependencies = [
[[package]]
name = "openfang-migrate"
version = "0.6.8"
version = "0.6.9"
dependencies = [
"chrono",
"dirs 6.0.0",
@@ -4221,7 +4221,7 @@ dependencies = [
[[package]]
name = "openfang-runtime"
version = "0.6.8"
version = "0.6.9"
dependencies = [
"anyhow",
"async-trait",
@@ -4257,7 +4257,7 @@ dependencies = [
[[package]]
name = "openfang-skills"
version = "0.6.8"
version = "0.6.9"
dependencies = [
"chrono",
"ed25519-dalek",
@@ -4282,7 +4282,7 @@ dependencies = [
[[package]]
name = "openfang-types"
version = "0.6.8"
version = "0.6.9"
dependencies = [
"async-trait",
"bitflags 2.11.0",
@@ -4302,7 +4302,7 @@ dependencies = [
[[package]]
name = "openfang-wire"
version = "0.6.8"
version = "0.6.9"
dependencies = [
"async-trait",
"chrono",
@@ -4399,7 +4399,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
dependencies = [
"libc",
"windows-sys 0.61.2",
"windows-sys 0.45.0",
]
[[package]]
@@ -5024,7 +5024,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b"
dependencies = [
"anyhow",
"itertools 0.14.0",
"itertools 0.13.0",
"proc-macro2",
"quote",
"syn 2.0.117",
@@ -5032,9 +5032,9 @@ dependencies = [
[[package]]
name = "pulley-interpreter"
version = "43.0.1"
version = "43.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "010dec3755eb61b2f1051ecb3611b718460b7a74c131e474de2af20a845938af"
checksum = "7ec12fe19a9588315a49fe5704502a9c02d6a198303314b0c7c86123b06d29e5"
dependencies = [
"cranelift-bitset",
"log",
@@ -5044,9 +5044,9 @@ dependencies = [
[[package]]
name = "pulley-macros"
version = "43.0.1"
version = "43.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad360c32e85ca4b083ac0e2b6856e8f11c3d5060dafa7d5dc57b370857fa3018"
checksum = "36f7d5ef31ebf1b46cd7e722ffef934e670d7e462f49aa01cde07b9b76dca580"
dependencies = [
"proc-macro2",
"quote",
@@ -5709,7 +5709,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.12.1",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -5768,7 +5768,7 @@ dependencies = [
"security-framework",
"security-framework-sys",
"webpki-root-certs",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -5779,9 +5779,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
[[package]]
name = "rustls-webpki"
version = "0.103.10"
version = "0.103.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef"
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
dependencies = [
"aws-lc-rs",
"ring",
@@ -6314,7 +6314,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
dependencies = [
"libc",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -7050,7 +7050,7 @@ dependencies = [
"getrandom 0.4.2",
"once_cell",
"rustix 1.1.4",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -7622,7 +7622,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
dependencies = [
"memoffset",
"tempfile",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -8066,9 +8066,9 @@ dependencies = [
[[package]]
name = "wasmtime"
version = "43.0.1"
version = "43.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce205cd643d661b5ba5ba4717e13730262e8cdbc8f2eacbc7b906d45c1a74026"
checksum = "efb1ed5899dde98357cfdcf647a4614498798719793898245b4b34e663addabf"
dependencies = [
"addr2line",
"async-trait",
@@ -8119,9 +8119,9 @@ dependencies = [
[[package]]
name = "wasmtime-environ"
version = "43.0.1"
version = "43.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b8b78abf3677d4a0a5db82e5015b4d085ff3a1b8b472cbb8c70d4b769f019ce"
checksum = "4172382dcc785c31d0e862c6780a18f5dd437914d22c4691351f965ef751c821"
dependencies = [
"anyhow",
"cpp_demangle",
@@ -8150,9 +8150,9 @@ dependencies = [
[[package]]
name = "wasmtime-internal-cache"
version = "43.0.1"
version = "43.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e4fd4103ba413c0da2e636f73490c6c8e446d708cbde7573703941bc3d6a448"
checksum = "4ed398988226d7aa0505ac6bb576e09532ad722d702ec4e66365d78ed695c95f"
dependencies = [
"base64 0.22.1",
"directories-next",
@@ -8170,9 +8170,9 @@ dependencies = [
[[package]]
name = "wasmtime-internal-component-macro"
version = "43.0.1"
version = "43.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d3d6914f34be2f9d78d8ee9f422e834dfc204e71ccce697205fae95fed87892"
checksum = "ae5ec9fff073ff13b81732d56a9515d761c245750bcda09093827f84130ebc25"
dependencies = [
"anyhow",
"proc-macro2",
@@ -8185,15 +8185,15 @@ dependencies = [
[[package]]
name = "wasmtime-internal-component-util"
version = "43.0.1"
version = "43.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3751b0616b914fdd87fe1bf804694a078f321b000338e6476bc48a4d6e454f21"
checksum = "935d9ab293ba27d1ec9aa7bc1b3a43993dbe961af2a8f23f90a11e1331b4c13f"
[[package]]
name = "wasmtime-internal-core"
version = "43.0.1"
version = "43.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22632b187e1b0716f1b9ac57ad29013bed33175fcb19e10bb6896126f82fac67"
checksum = "9a3820b174f477d2a7083209d1ad5353fcdb11eaea434b2137b8681029460dd3"
dependencies = [
"anyhow",
"hashbrown 0.16.1",
@@ -8203,9 +8203,9 @@ dependencies = [
[[package]]
name = "wasmtime-internal-cranelift"
version = "43.0.1"
version = "43.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b3ca07b3e0bb3429674b173b5800577719d600774dd81bff58f775c0aaa64ee"
checksum = "d1679d205caf9766c6aa309d45bb3e7c634d7725e3164404df33824b9f7c4fb7"
dependencies = [
"cfg-if",
"cranelift-codegen",
@@ -8230,9 +8230,9 @@ dependencies = [
[[package]]
name = "wasmtime-internal-fiber"
version = "43.0.1"
version = "43.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20c8b2c9704eb1f33ead025ec16038277ccb63d0a14c31e99d5b765d7c36da55"
checksum = "f1e505254058be5b0df458d670ee42d9eafe2349d04c1296e9dc01071dc20a85"
dependencies = [
"cc",
"cfg-if",
@@ -8245,9 +8245,9 @@ dependencies = [
[[package]]
name = "wasmtime-internal-jit-debug"
version = "43.0.1"
version = "43.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d950310d07391d34369f62c48336ebb14eacbd4d6f772bb5f349c24e838e0664"
checksum = "1c2e05b345f1773e59c20e6ad7298fd6857cdea245023d88bb659c96d8f0ea72"
dependencies = [
"cc",
"object",
@@ -8257,9 +8257,9 @@ dependencies = [
[[package]]
name = "wasmtime-internal-jit-icache-coherence"
version = "43.0.1"
version = "43.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3606662c156962d096be3127b8b8ae8ee2f8be3f896dad29259ff01ddb64abfd"
checksum = "b86701b234a4643e3f111869aa792b3a05a06e02d486ee9cb6c04dae16b52dab"
dependencies = [
"cfg-if",
"libc",
@@ -8269,9 +8269,9 @@ dependencies = [
[[package]]
name = "wasmtime-internal-unwinder"
version = "43.0.1"
version = "43.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75eef0747e52dc545b075f64fd0e0cc237ae738e641266b1970e07e2d744bc32"
checksum = "f63558d801beb83dde9b336eb4ae049019aee26627926edb32cd119d7e4c83cd"
dependencies = [
"cfg-if",
"cranelift-codegen",
@@ -8282,9 +8282,9 @@ dependencies = [
[[package]]
name = "wasmtime-internal-versioned-export-macros"
version = "43.0.1"
version = "43.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b0a5dab02a8fb527f547855ecc0e05f9fdc3d5bd57b8b080349408f9a6cece"
checksum = "737c4d956fc3a848541a064afb683dd2771132a6b125be5baaf95c4379aa47df"
dependencies = [
"proc-macro2",
"quote",
@@ -8293,9 +8293,9 @@ dependencies = [
[[package]]
name = "wasmtime-internal-winch"
version = "43.0.1"
version = "43.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8007342bd12ff400293a817973f7ecd6f1d9a8549a53369a9c1af357166f1f1e"
checksum = "f599b79545e3bba0b7913406055ebede5bb0dabee9ba2015ef25a9f4c9f47807"
dependencies = [
"cranelift-codegen",
"gimli",
@@ -8310,9 +8310,9 @@ dependencies = [
[[package]]
name = "wasmtime-internal-wit-bindgen"
version = "43.0.1"
version = "43.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7900c3e3c1d6e475bc225d73b02d6d5484815f260022e6964dca9558e50dd01a"
checksum = "2192a77a00b9a67800c2b4e1c70fb6abca79d6b529e53a2ef9dcdcc36090330d"
dependencies = [
"anyhow",
"bitflags 2.11.0",
@@ -8495,7 +8495,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -8506,9 +8506,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "winch-codegen"
version = "43.0.1"
version = "43.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f45f7172a2628c8317766e427babc0a400f9d10b1c0f0b0617c5ed5b79de6"
checksum = "52dbb0cf07b0dfe7b7a1ca8efb8f94ba98bd0fb144c411ea1665c78f0449e958"
dependencies = [
"cranelift-assembler-x64",
"cranelift-codegen",
@@ -9236,7 +9236,7 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
[[package]]
name = "xtask"
version = "0.6.8"
version = "0.6.9"
[[package]]
name = "yoke"
+1 -1
View File
@@ -18,7 +18,7 @@ members = [
]
[workspace.package]
version = "0.6.8"
version = "0.6.9"
edition = "2021"
license = "Apache-2.0 OR MIT"
repository = "https://github.com/RightNow-AI/openfang"
+2 -2
View File
@@ -19,8 +19,8 @@
<p align="center">
<img src="https://img.shields.io/badge/language-Rust-orange?style=flat-square" alt="Rust" />
<img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="MIT" />
<img src="https://img.shields.io/badge/version-0.6.8-green?style=flat-square" alt="v0.6.8" />
<img src="https://img.shields.io/badge/tests-2,669%2B%20passing-brightgreen?style=flat-square" alt="Tests" />
<img src="https://img.shields.io/badge/version-0.6.9-green?style=flat-square" alt="v0.6.9" />
<img src="https://img.shields.io/badge/tests-2,696%2B%20passing-brightgreen?style=flat-square" alt="Tests" />
<img src="https://img.shields.io/badge/clippy-0%20warnings-brightgreen?style=flat-square" alt="Clippy" />
<a href="https://www.buymeacoffee.com/openfang" target="_blank"><img src="https://img.shields.io/badge/Buy%20Me%20a%20Coffee-FFDD00?style=flat-square&logo=buy-me-a-coffee&logoColor=black" alt="Buy Me A Coffee" /></a>
</p>
+13 -3
View File
@@ -3856,7 +3856,10 @@ pub async fn audit_append(
};
let outcome = req.outcome.clone().unwrap_or_else(|| "ok".to_string());
let hash = state.kernel.audit_log.record(agent_id, action, detail, outcome);
let hash = state
.kernel
.audit_log
.record(agent_id, action, detail, outcome);
let seq = state.kernel.audit_log.len().saturating_sub(1) as u64;
(
@@ -10063,7 +10066,9 @@ pub async fn clone_agent(
Err(e) => {
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({"error": format!("Failed to serialize manifest: {e}")})),
Json(
serde_json::json!({"error": format!("Failed to serialize manifest: {e}")}),
),
);
}
};
@@ -10240,7 +10245,12 @@ pub async fn list_agent_files(
// Identity files live in the agent's private state directory (see #1097).
// Fall back to the legacy workspace location for agents created before the
// split so existing on-disk files remain reachable.
let workspace = match entry.manifest.state_dir.as_ref().or(entry.manifest.workspace.as_ref()) {
let workspace = match entry
.manifest
.state_dir
.as_ref()
.or(entry.manifest.workspace.as_ref())
{
Some(ws) => ws.clone(),
None => {
return (
+1 -2
View File
@@ -167,8 +167,7 @@ mod tests {
fn skill_install_request_defaults_back_compat() {
// Existing callers send `{"name": "..."}` only. New optional fields
// must default cleanly (issue #1170).
let req: SkillInstallRequest =
serde_json::from_str(r#"{"name":"github-helper"}"#).unwrap();
let req: SkillInstallRequest = serde_json::from_str(r#"{"name":"github-helper"}"#).unwrap();
assert_eq!(req.name, "github-helper");
assert!(!req.require_signed);
assert!(req.allowed_signer_keys.is_empty());
+7 -6
View File
@@ -1048,7 +1048,9 @@ async fn handle_command(
"command": cmd,
"message": format!("Hand '{}' deactivated.", instance.hand_id),
}),
Err(e) => serde_json::json!({"type": "error", "content": format!("Stop failed: {e}")}),
Err(e) => {
serde_json::json!({"type": "error", "content": format!("Stop failed: {e}")})
}
}
} else {
match state.kernel.stop_agent_run(agent_id) {
@@ -1058,7 +1060,9 @@ async fn handle_command(
Ok(false) => {
serde_json::json!({"type": "command_result", "command": cmd, "message": "No active run to cancel."})
}
Err(e) => serde_json::json!({"type": "error", "content": format!("Stop failed: {e}")}),
Err(e) => {
serde_json::json!({"type": "error", "content": format!("Stop failed: {e}")})
}
}
}
}
@@ -1812,10 +1816,7 @@ mod tests {
// Cookie signed with the wrong secret must fail.
let bad = crate::session_auth::create_session_token("alice", "other-secret", 1);
let mut headers = axum::http::HeaderMap::new();
headers.insert(
"cookie",
format!("openfang_session={bad}").parse().unwrap(),
);
headers.insert("cookie", format!("openfang_session={bad}").parse().unwrap());
let uri = empty_uri();
let ctx = WsAuthCtx {
api_key: "secret",
@@ -1660,10 +1660,7 @@ async fn test_clone_agent_happy_path() {
assert_eq!(manifest["description"], "Cloned for user 1");
assert_eq!(
manifest["tags"].as_array().unwrap(),
&vec![
serde_json::json!("clone"),
serde_json::json!("user-1"),
]
&vec![serde_json::json!("clone"), serde_json::json!("user-1"),]
);
// Inherited from template — the system_prompt should match.
assert_eq!(
@@ -1678,10 +1675,7 @@ async fn test_clone_agent_happy_path() {
.await
.unwrap();
let agents: Vec<serde_json::Value> = resp.json().await.unwrap();
let names: Vec<&str> = agents
.iter()
.map(|a| a["name"].as_str().unwrap())
.collect();
let names: Vec<&str> = agents.iter().map(|a| a["name"].as_str().unwrap()).collect();
assert!(names.contains(&"test-agent"));
assert!(names.contains(&"cloned-user-1"));
}
@@ -1731,10 +1725,7 @@ async fn test_clone_agent_name_collision() {
"duplicate name must return 409 Conflict"
);
let body: serde_json::Value = resp.json().await.unwrap();
assert!(body["error"]
.as_str()
.unwrap()
.contains("already exists"));
assert!(body["error"].as_str().unwrap().contains("already exists"));
// Cloning into the template's own name must also be rejected.
let resp = client
+15 -20
View File
@@ -144,8 +144,7 @@ impl MatrixAdapter {
&& status == reqwest::StatusCode::UNAUTHORIZED
&& is_unknown_token_body(&body_text)
{
match try_refresh_tokens(&self.client, &self.homeserver_url, &self.tokens)
.await
match try_refresh_tokens(&self.client, &self.homeserver_url, &self.tokens).await
{
Ok(()) => {
info!("Matrix: access token refreshed via MSC2918, retrying send");
@@ -717,7 +716,8 @@ mod tests {
#[test]
fn test_is_unknown_token_body() {
// Real matrix.org body for M_UNKNOWN_TOKEN under MAS.
let body = r#"{"errcode":"M_UNKNOWN_TOKEN","error":"Token is not active","soft_logout":true}"#;
let body =
r#"{"errcode":"M_UNKNOWN_TOKEN","error":"Token is not active","soft_logout":true}"#;
assert!(is_unknown_token_body(body));
assert!(!is_hard_logout(body));
@@ -740,19 +740,17 @@ mod tests {
// access and refresh tokens and returns the new pair.
use axum::{routing::post, Json, Router};
async fn refresh_handler(
Json(body): Json<serde_json::Value>,
) -> Json<serde_json::Value> {
let incoming = body
.get("refresh_token")
.and_then(|v| v.as_str())
.unwrap_or("");
assert_eq!(incoming, "old_refresh");
Json(serde_json::json!({
"access_token": "new_access",
"refresh_token": "new_refresh",
"expires_in_ms": 3_600_000u64,
}))
async fn refresh_handler(Json(body): Json<serde_json::Value>) -> Json<serde_json::Value> {
let incoming = body
.get("refresh_token")
.and_then(|v| v.as_str())
.unwrap_or("");
assert_eq!(incoming, "old_refresh");
Json(serde_json::json!({
"access_token": "new_access",
"refresh_token": "new_refresh",
"expires_in_ms": 3_600_000u64,
}))
}
let app = Router::new().route("/_matrix/client/v3/refresh", post(refresh_handler));
@@ -779,10 +777,7 @@ mod tests {
drop(guard);
// Refresh with no refresh token configured must fail cleanly.
let no_refresh: TokenPair = Arc::new(RwLock::new((
Zeroizing::new("a".to_string()),
None,
)));
let no_refresh: TokenPair = Arc::new(RwLock::new((Zeroizing::new("a".to_string()), None)));
let err = try_refresh_tokens(&client, &homeserver, &no_refresh)
.await
.unwrap_err();
+2 -1
View File
@@ -722,7 +722,8 @@ mod tests {
assert_eq!(resolved, None);
// Missing channel_id on the wire — no match (the binding is restrictive).
let resolved = router.resolve_with_channel_id(&ChannelType::Discord, "any-user", None, None);
let resolved =
router.resolve_with_channel_id(&ChannelType::Discord, "any-user", None, None);
assert_eq!(resolved, None);
}
+1 -3
View File
@@ -329,9 +329,7 @@ impl ChannelAdapter for SlackAdapter {
// connection during the rotation overlap. Ack on
// both, but only forward to the agent once.
if is_duplicate_envelope(&seen_envelopes, envelope_id) {
debug!(
"Slack: skipping duplicate envelope_id {envelope_id}"
);
debug!("Slack: skipping duplicate envelope_id {envelope_id}");
continue;
}
+229 -61
View File
@@ -1234,9 +1234,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
assert_eq!(msg.channel, ChannelType::Telegram);
assert_eq!(msg.sender.display_name, "Alice Smith");
assert_eq!(msg.sender.platform_id, "111222333");
@@ -1267,9 +1275,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
// The chat_id (used for replies) stays on sender.platform_id.
assert_eq!(msg.sender.platform_id, "-1009876543210");
@@ -1308,9 +1324,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
let tg_id = msg
.metadata
@@ -1345,9 +1369,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
match &msg.content {
ChannelContent::Command { name, args } => {
assert_eq!(name, "agent");
@@ -1379,8 +1411,16 @@ mod tests {
let client = test_client();
// Empty allowed_users = allow all
let msg =
parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new()).await;
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await;
assert!(msg.is_some());
// Non-matching allowed_users = filter out
@@ -1434,9 +1474,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
assert_eq!(msg.channel, ChannelType::Telegram);
assert_eq!(msg.sender.display_name, "Alice Smith");
assert!(matches!(msg.content, ChannelContent::Text(ref t) if t == "Edited message!"));
@@ -1472,9 +1520,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
match &msg.content {
ChannelContent::Command { name, args } => {
assert_eq!(name, "agents");
@@ -1498,9 +1554,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
assert!(matches!(msg.content, ChannelContent::Location { .. }));
}
@@ -1524,9 +1588,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
// With a fake token, getFile will fail, so we get a text fallback
match &msg.content {
ChannelContent::Text(t) => {
@@ -1561,9 +1633,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
match &msg.content {
ChannelContent::Text(t) => {
assert!(t.contains("Document received"));
@@ -1594,9 +1674,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
match &msg.content {
ChannelContent::Text(t) => {
assert!(t.contains("Voice message"));
@@ -1627,9 +1715,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
assert_eq!(msg.thread_id, Some("42".to_string()));
assert!(msg.is_group);
}
@@ -1649,9 +1745,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
assert_eq!(msg.thread_id, None);
assert!(!msg.is_group);
}
@@ -1673,9 +1777,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
assert_eq!(msg.thread_id, Some("99".to_string()));
}
@@ -1871,9 +1983,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
assert_eq!(msg.sender.display_name, "My Channel");
assert_eq!(msg.sender.platform_id, "-1001234567890");
assert!(
@@ -1895,8 +2015,16 @@ mod tests {
});
let client = test_client();
let msg =
parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new()).await;
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await;
assert!(msg.is_none());
}
@@ -2162,9 +2290,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
match &msg.content {
ChannelContent::Text(t) => {
assert!(t.starts_with("[Replying to Bob: We should use Rust]\n\n"));
@@ -2204,9 +2340,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
match &msg.content {
ChannelContent::Text(t) => {
assert!(t.starts_with("[Replying to Carol: Sunset view]\n\n"));
@@ -2245,9 +2389,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
match &msg.content {
ChannelContent::Text(t) => {
assert_eq!(t, "What was that?");
@@ -2283,9 +2435,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
match &msg.content {
ChannelContent::Text(t) => {
assert!(t.starts_with("[Replying to Unknown: Anonymous message]\n\n"));
@@ -2310,9 +2470,17 @@ mod tests {
});
let client = test_client();
let msg = parse_telegram_update(&update, &[], "fake:token", &client, DEFAULT_API_URL, None, &HashMap::new())
.await
.unwrap();
let msg = parse_telegram_update(
&update,
&[],
"fake:token",
&client,
DEFAULT_API_URL,
None,
&HashMap::new(),
)
.await
.unwrap();
match &msg.content {
ChannelContent::Text(t) => {
assert_eq!(t, "Just a normal message");
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "OpenFang",
"version": "0.6.8",
"version": "0.6.9",
"identifier": "ai.openfang.desktop",
"build": {},
"app": {
+28 -30
View File
@@ -1086,11 +1086,10 @@ impl OpenFangKernel {
openfang_runtime::media_understanding::MediaEngine::new(config.media.clone());
// Closes #1051: thread MediaConfig URL overrides into the TTS engine
// so local OpenAI/ElevenLabs-compatible services can be targeted.
let tts_engine = openfang_runtime::tts::TtsEngine::new(config.tts.clone())
.with_base_urls(
config.media.tts_openai_base_url.clone(),
config.media.tts_elevenlabs_base_url.clone(),
);
let tts_engine = openfang_runtime::tts::TtsEngine::new(config.tts.clone()).with_base_urls(
config.media.tts_openai_base_url.clone(),
config.media.tts_elevenlabs_base_url.clone(),
);
let mut pairing = crate::pairing::PairingManager::new(config.pairing.clone());
// Load paired devices from database and set up persistence callback
@@ -1246,16 +1245,16 @@ impl OpenFangKernel {
}
let audit_log_for_cb = Arc::clone(&kernel.audit_log);
kernel.hand_registry.set_audit_callback(Arc::new(
move |hand_id: &str, hash: &str| {
kernel
.hand_registry
.set_audit_callback(Arc::new(move |hand_id: &str, hash: &str| {
audit_log_for_cb.record(
"kernel",
openfang_runtime::audit::AuditAction::ConfigChange,
format!("HAND.toml reload hand={hand_id} sha256={hash}"),
"ok",
);
},
));
}));
}
// Restore persisted agents from SQLite
@@ -1541,9 +1540,7 @@ impl OpenFangKernel {
}
}
if auto_spawned > 0 {
info!(
"Auto-spawned {auto_spawned} agent(s) from ~/.openfang/agents"
);
info!("Auto-spawned {auto_spawned} agent(s) from ~/.openfang/agents");
}
}
}
@@ -1705,7 +1702,10 @@ impl OpenFangKernel {
// does not specify one. When the user sets `workspace = "/path"` in
// agent.toml we leave that path alone — only data/, output/, skills/
// get created lazily so private state never pollutes the target dir.
let workspace_dir = manifest.workspace.clone().unwrap_or_else(|| state_dir.clone());
let workspace_dir = manifest
.workspace
.clone()
.unwrap_or_else(|| state_dir.clone());
ensure_state_dir(&state_dir, &workspace_dir)?;
ensure_workspace(&workspace_dir)?;
if manifest.generate_identity_files {
@@ -2127,7 +2127,10 @@ impl OpenFangKernel {
// to the same path unless the manifest already pinned one. See #1097.
if manifest.state_dir.is_none() {
let state_dir = self.config.effective_workspaces_dir().join(&manifest.name);
let workspace_dir = manifest.workspace.clone().unwrap_or_else(|| state_dir.clone());
let workspace_dir = manifest
.workspace
.clone()
.unwrap_or_else(|| state_dir.clone());
if let Err(e) = ensure_state_dir(&state_dir, &workspace_dir) {
warn!(agent_id = %agent_id, "Failed to backfill state_dir (streaming): {e}");
}
@@ -2699,7 +2702,10 @@ impl OpenFangKernel {
// state_dir). See issue #1097.
if manifest.state_dir.is_none() {
let state_dir = self.config.effective_workspaces_dir().join(&manifest.name);
let workspace_dir = manifest.workspace.clone().unwrap_or_else(|| state_dir.clone());
let workspace_dir = manifest
.workspace
.clone()
.unwrap_or_else(|| state_dir.clone());
if let Err(e) = ensure_state_dir(&state_dir, &workspace_dir) {
warn!(agent_id = %agent_id, "Failed to backfill state_dir: {e}");
}
@@ -5511,9 +5517,7 @@ impl OpenFangKernel {
}
for var in skill.manifest.config.values() {
if let Some(env_name) = var.env.as_deref() {
if let Some(providers) =
env_to_provider.get(&env_name.to_ascii_uppercase())
{
if let Some(providers) = env_to_provider.get(&env_name.to_ascii_uppercase()) {
for provider in providers {
set.insert(provider.clone());
}
@@ -5528,9 +5532,7 @@ impl OpenFangKernel {
// wired that provider into their MCP server.
for server in &self.config.mcp_servers {
for env_name in &server.env {
if let Some(providers) =
env_to_provider.get(&env_name.to_ascii_uppercase())
{
if let Some(providers) = env_to_provider.get(&env_name.to_ascii_uppercase()) {
for provider in providers {
set.insert(provider.clone());
}
@@ -8022,8 +8024,7 @@ mod tests {
assert_eq!(merged.description, "new", "TOML edits must apply");
assert_eq!(
merged.workspace,
entry.workspace,
merged.workspace, entry.workspace,
"kernel-assigned workspace must survive a TOML edit that omits it"
);
assert!(
@@ -8438,9 +8439,7 @@ mod tests {
let first_instance_id = instance.instance_id;
// Sanity: hand is Active and re-activation is rejected.
assert!(kernel
.activate_hand("lead", HashMap::new(), None)
.is_err());
assert!(kernel.activate_hand("lead", HashMap::new(), None).is_err());
// Simulate what POST /api/agents/{id}/stop now does for a hand-owned
// agent: look up the instance and deactivate the hand (which also
@@ -8880,7 +8879,8 @@ mod tests {
"fallback timeout edits must be hot-reloadable"
);
assert!(
plan.hot_actions.contains(&HotAction::ReloadFallbackProviders),
plan.hot_actions
.contains(&HotAction::ReloadFallbackProviders),
"ReloadFallbackProviders must be present in the plan"
);
@@ -9160,9 +9160,7 @@ mod tests {
#[test]
fn test_1188_referenced_providers_walks_mcp_env() {
use openfang_types::config::{
DefaultModelConfig, McpServerConfigEntry, McpTransportEntry,
};
use openfang_types::config::{DefaultModelConfig, McpServerConfigEntry, McpTransportEntry};
let tmp = tempfile::tempdir().unwrap();
let home_dir = tmp.path().join("openfang-1188-mcp");
+11 -7
View File
@@ -69,8 +69,7 @@ fn env_timeout_secs(var: &str) -> Option<u64> {
fn tool_timeout_for(tool_name: &str) -> Option<Duration> {
let secs = match tool_name {
"agent_send" | "agent_spawn" => {
env_timeout_secs("OPENFANG_AGENT_TOOL_TIMEOUT_SECS")
.unwrap_or(AGENT_TOOL_TIMEOUT_SECS)
env_timeout_secs("OPENFANG_AGENT_TOOL_TIMEOUT_SECS").unwrap_or(AGENT_TOOL_TIMEOUT_SECS)
}
_ => env_timeout_secs("OPENFANG_TOOL_TIMEOUT_SECS").unwrap_or(TOOL_TIMEOUT_SECS),
};
@@ -3271,7 +3270,9 @@ mod tests {
assert_eq!(blocks.len(), 2, "must preserve thinking + text");
match &blocks[0] {
ContentBlock::Thinking {
thinking, signature, ..
thinking,
signature,
..
} => {
assert_eq!(thinking, "Let me reason carefully...");
assert_eq!(signature.as_deref(), Some("sig_anthropic_xyz"));
@@ -3367,7 +3368,10 @@ mod tests {
let has_redacted = blocks
.iter()
.any(|b| matches!(b, ContentBlock::RedactedThinking { .. }));
assert!(has_thinking, "Thinking block must be preserved on MaxTokens");
assert!(
has_thinking,
"Thinking block must be preserved on MaxTokens"
);
assert!(
has_redacted,
"RedactedThinking block must be preserved on MaxTokens"
@@ -3409,9 +3413,9 @@ mod tests {
MessageContent::Blocks(b) => b,
other => panic!("expected Blocks content for redacted-only turn, got {other:?}"),
};
let has_redacted = blocks
.iter()
.any(|b| matches!(b, ContentBlock::RedactedThinking { data } if data == "encrypted_only"));
let has_redacted = blocks.iter().any(
|b| matches!(b, ContentBlock::RedactedThinking { data } if data == "encrypted_only"),
);
assert!(
has_redacted,
"RedactedThinking-only turn must be preserved as Blocks"
@@ -189,7 +189,9 @@ enum ContentBlockAccum {
/// Redacted (encrypted) thinking block streamed from Anthropic.
/// The opaque `data` blob arrives on `content_block_start` and must be
/// persisted so the next turn can echo it back verbatim.
RedactedThinking { data: String },
RedactedThinking {
data: String,
},
}
#[async_trait]
@@ -454,10 +456,8 @@ impl LlmDriver for AnthropicDriver {
"thinking" => {
// Some API versions ship the signature on
// content_block_start instead of as a delta.
let initial_sig = block["signature"]
.as_str()
.unwrap_or("")
.to_string();
let initial_sig =
block["signature"].as_str().unwrap_or("").to_string();
blocks.push(ContentBlockAccum::Thinking {
thinking: String::new(),
signature: initial_sig,
@@ -470,10 +470,7 @@ impl LlmDriver for AnthropicDriver {
// verbatim so we can echo it back on the
// next request — API rejects history
// that strips redacted_thinking blocks.
let data = block["data"]
.as_str()
.unwrap_or("")
.to_string();
let data = block["data"].as_str().unwrap_or("").to_string();
blocks.push(ContentBlockAccum::RedactedThinking { data });
}
_ => {}
@@ -751,9 +748,7 @@ fn convert_message(msg: &Message) -> ApiMessage {
if data.is_empty() {
None
} else {
Some(ApiContentBlock::RedactedThinking {
data: data.clone(),
})
Some(ApiContentBlock::RedactedThinking { data: data.clone() })
}
}
ContentBlock::Unknown => None,
@@ -328,9 +328,7 @@ fn convert_content_block(block: &ContentBlock) -> Option<BedrockContentBlock> {
}
}
// Image, Thinking, and Unknown are not supported — silently drop
ContentBlock::Image { .. }
| ContentBlock::Thinking { .. }
| ContentBlock::Unknown => None,
ContentBlock::Image { .. } | ContentBlock::Thinking { .. } | ContentBlock::Unknown => None,
}
}
+3 -3
View File
@@ -21,9 +21,9 @@ use openfang_types::model_catalog::{
HUGGINGFACE_BASE_URL, KIMI_CODING_BASE_URL, LEMONADE_BASE_URL, LMSTUDIO_BASE_URL,
MINIMAX_BASE_URL, MISTRAL_BASE_URL, MOONSHOT_BASE_URL, NOVITA_BASE_URL, NVIDIA_NIM_BASE_URL,
OLLAMA_BASE_URL, OPENAI_BASE_URL, OPENROUTER_BASE_URL, PERPLEXITY_BASE_URL, QIANFAN_BASE_URL,
QWEN_BASE_URL, REPLICATE_BASE_URL, REQUESTY_BASE_URL, SAMBANOVA_BASE_URL, TOGETHER_BASE_URL, VENICE_BASE_URL,
VLLM_BASE_URL, VOLCENGINE_BASE_URL, VOLCENGINE_CODING_BASE_URL, XAI_BASE_URL, ZAI_BASE_URL,
ZAI_CODING_BASE_URL, ZHIPU_BASE_URL, ZHIPU_CODING_BASE_URL,
QWEN_BASE_URL, REPLICATE_BASE_URL, REQUESTY_BASE_URL, SAMBANOVA_BASE_URL, TOGETHER_BASE_URL,
VENICE_BASE_URL, VLLM_BASE_URL, VOLCENGINE_BASE_URL, VOLCENGINE_CODING_BASE_URL, XAI_BASE_URL,
ZAI_BASE_URL, ZAI_CODING_BASE_URL, ZHIPU_BASE_URL, ZHIPU_CODING_BASE_URL,
};
use std::sync::Arc;
+40 -44
View File
@@ -460,8 +460,8 @@ impl LlmDriver for OpenAIDriver {
tool_calls: None,
tool_call_id: None,
reasoning_content: None,
reasoning: None,
});
reasoning: None,
});
}
// Convert messages
@@ -474,8 +474,8 @@ impl LlmDriver for OpenAIDriver {
tool_calls: None,
tool_call_id: None,
reasoning_content: None,
reasoning: None,
});
reasoning: None,
});
}
(Role::User, MessageContent::Text(text)) => {
oai_messages.push(OaiMessage {
@@ -484,8 +484,8 @@ impl LlmDriver for OpenAIDriver {
tool_calls: None,
tool_call_id: None,
reasoning_content: None,
reasoning: None,
});
reasoning: None,
});
}
(Role::Assistant, MessageContent::Text(text)) => {
oai_messages.push(OaiMessage {
@@ -494,8 +494,8 @@ impl LlmDriver for OpenAIDriver {
tool_calls: None,
tool_call_id: None,
reasoning_content: None,
reasoning: None,
});
reasoning: None,
});
}
(Role::User, MessageContent::Blocks(blocks)) => {
// Handle tool results and images in user messages
@@ -519,8 +519,8 @@ impl LlmDriver for OpenAIDriver {
tool_calls: None,
tool_call_id: Some(tool_use_id.clone()),
reasoning_content: None,
reasoning: None,
});
reasoning: None,
});
}
ContentBlock::Text { text, .. } => {
parts.push(OaiContentPart::Text { text: text.clone() });
@@ -543,8 +543,8 @@ impl LlmDriver for OpenAIDriver {
tool_calls: None,
tool_call_id: None,
reasoning_content: None,
reasoning: None,
});
reasoning: None,
});
}
}
(Role::Assistant, MessageContent::Blocks(blocks)) => {
@@ -915,8 +915,8 @@ impl LlmDriver for OpenAIDriver {
tool_calls: None,
tool_call_id: None,
reasoning_content: None,
reasoning: None,
});
reasoning: None,
});
}
for msg in &request.messages {
@@ -928,8 +928,8 @@ impl LlmDriver for OpenAIDriver {
tool_calls: None,
tool_call_id: None,
reasoning_content: None,
reasoning: None,
});
reasoning: None,
});
}
(Role::User, MessageContent::Text(text)) => {
oai_messages.push(OaiMessage {
@@ -938,8 +938,8 @@ impl LlmDriver for OpenAIDriver {
tool_calls: None,
tool_call_id: None,
reasoning_content: None,
reasoning: None,
});
reasoning: None,
});
}
(Role::Assistant, MessageContent::Text(text)) => {
oai_messages.push(OaiMessage {
@@ -948,8 +948,8 @@ impl LlmDriver for OpenAIDriver {
tool_calls: None,
tool_call_id: None,
reasoning_content: None,
reasoning: None,
});
reasoning: None,
});
}
(Role::User, MessageContent::Blocks(blocks)) => {
for block in blocks {
@@ -969,8 +969,8 @@ impl LlmDriver for OpenAIDriver {
tool_calls: None,
tool_call_id: Some(tool_use_id.clone()),
reasoning_content: None,
reasoning: None,
});
reasoning: None,
});
}
}
}
@@ -1492,7 +1492,9 @@ impl LlmDriver for OpenAIDriver {
// non-zero output_tokens so the agent loop doesn't misclassify
// this as a "silent failure" and loop unnecessarily.
if !content.is_empty() && usage.input_tokens == 0 && usage.output_tokens == 0 {
debug!("Stream has content but no usage stats — setting synthetic output_tokens=1");
debug!(
"Stream has content but no usage stats — setting synthetic output_tokens=1"
);
usage.output_tokens = 1;
}
@@ -1956,10 +1958,7 @@ mod tests {
/// the upstream server is pre- or post-vLLM 0.19.
#[test]
fn test_assemble_emits_both_reasoning_fields_for_vllm_compat() {
let driver = OpenAIDriver::new(
"test".to_string(),
"http://localhost:8000/v1".to_string(),
);
let driver = OpenAIDriver::new("test".to_string(), "http://localhost:8000/v1".to_string());
let blocks = vec![
ContentBlock::Thinking {
thinking: "MARKER-vllm-019".to_string(),
@@ -1994,8 +1993,7 @@ mod tests {
/// change in #1157.
#[test]
fn test_assemble_no_reasoning_fields_for_plain_model() {
let driver =
OpenAIDriver::new("test".to_string(), "https://api.openai.com/v1".to_string());
let driver = OpenAIDriver::new("test".to_string(), "https://api.openai.com/v1".to_string());
let blocks = vec![ContentBlock::Text {
text: "hi".to_string(),
provider_metadata: None,
@@ -2014,18 +2012,14 @@ mod tests {
/// understand the new name.
#[test]
fn test_assemble_moonshot_keeps_legacy_field_only() {
let driver = OpenAIDriver::new(
"test".to_string(),
"https://api.moonshot.cn/v1".to_string(),
);
let blocks = vec![
ContentBlock::ToolUse {
id: "call_1".to_string(),
name: "search".to_string(),
input: serde_json::json!({"q": "x"}),
provider_metadata: None,
},
];
let driver =
OpenAIDriver::new("test".to_string(), "https://api.moonshot.cn/v1".to_string());
let blocks = vec![ContentBlock::ToolUse {
id: "call_1".to_string(),
name: "search".to_string(),
input: serde_json::json!({"q": "x"}),
provider_metadata: None,
}];
let msg = assemble_assistant_message(&blocks, "kimi-k2", &driver);
assert_eq!(
msg.reasoning_content.as_deref(),
@@ -2174,7 +2168,10 @@ mod tests {
Some(OaiMessageContent::Text(t)) => t,
_ => panic!("expected text content"),
};
assert_eq!(content, "answer", "visible content must not include <think>");
assert_eq!(
content, "answer",
"visible content must not include <think>"
);
assert_eq!(
msg.reasoning_content.as_deref(),
Some("internal chain-of-thought"),
@@ -2186,8 +2183,7 @@ mod tests {
/// assistant message — preserve the legacy shape.
#[test]
fn test_assemble_assistant_no_thinking_is_plain() {
let driver =
OpenAIDriver::new("test".to_string(), "https://api.openai.com/v1".to_string());
let driver = OpenAIDriver::new("test".to_string(), "https://api.openai.com/v1".to_string());
let blocks = vec![ContentBlock::Text {
text: "Hi.".to_string(),
provider_metadata: None,
@@ -258,7 +258,6 @@ fn host_net_fetch(state: &GuestState, params: &serde_json::Value) -> serde_json:
})
}
// ---------------------------------------------------------------------------
// Shell (capability-checked)
// ---------------------------------------------------------------------------
@@ -561,7 +560,10 @@ mod tests {
assert!(web_fetch::check_ssrf("http://127.0.0.1:8080/secret", &no_allow).is_err());
assert!(web_fetch::check_ssrf("http://localhost:3000/api", &no_allow).is_err());
assert!(web_fetch::check_ssrf("http://169.254.169.254/metadata", &no_allow).is_err());
assert!(web_fetch::check_ssrf("http://metadata.google.internal/v1/instance", &no_allow).is_err());
assert!(
web_fetch::check_ssrf("http://metadata.google.internal/v1/instance", &no_allow)
.is_err()
);
// These were previously missing from host_functions — now covered:
assert!(web_fetch::check_ssrf("http://[::1]:8080/secret", &no_allow).is_err());
assert!(web_fetch::check_ssrf("http://100.100.100.200/metadata", &no_allow).is_err());
+7 -12
View File
@@ -10,8 +10,8 @@ use openfang_types::model_catalog::{
HUGGINGFACE_BASE_URL, KIMI_CODING_BASE_URL, LEMONADE_BASE_URL, LMSTUDIO_BASE_URL,
MINIMAX_BASE_URL, MISTRAL_BASE_URL, MOONSHOT_BASE_URL, NVIDIA_NIM_BASE_URL, OLLAMA_BASE_URL,
OPENAI_BASE_URL, OPENROUTER_BASE_URL, PERPLEXITY_BASE_URL, QIANFAN_BASE_URL, QWEN_BASE_URL,
REPLICATE_BASE_URL, REQUESTY_BASE_URL, SAMBANOVA_BASE_URL, TOGETHER_BASE_URL, VENICE_BASE_URL, VLLM_BASE_URL,
VOLCENGINE_BASE_URL, VOLCENGINE_CODING_BASE_URL, XAI_BASE_URL, ZAI_BASE_URL,
REPLICATE_BASE_URL, REQUESTY_BASE_URL, SAMBANOVA_BASE_URL, TOGETHER_BASE_URL, VENICE_BASE_URL,
VLLM_BASE_URL, VOLCENGINE_BASE_URL, VOLCENGINE_CODING_BASE_URL, XAI_BASE_URL, ZAI_BASE_URL,
ZAI_CODING_BASE_URL, ZHIPU_BASE_URL, ZHIPU_CODING_BASE_URL,
};
use std::collections::HashMap;
@@ -1052,10 +1052,7 @@ fn builtin_aliases() -> HashMap<String, String> {
),
("free", "openrouter/meta-llama/llama-3.3-70b-instruct:free"),
("free-reasoning", "openrouter/deepseek/deepseek-r1:free"),
(
"openrouter/free-coder",
"openrouter/qwen/qwen3-coder:free",
),
("openrouter/free-coder", "openrouter/qwen/qwen3-coder:free"),
(
"openrouter/free-large",
"openrouter/openai/gpt-oss-120b:free",
@@ -4715,9 +4712,9 @@ mod tests {
#[test]
fn test_openrouter_free_alias_supports_tools() {
let catalog = ModelCatalog::new();
let entry = catalog.find_model("openrouter/free").expect(
"openrouter/free alias must resolve to a known model",
);
let entry = catalog
.find_model("openrouter/free")
.expect("openrouter/free alias must resolve to a known model");
assert_eq!(entry.provider, "openrouter");
assert!(
entry.supports_tools,
@@ -4730,9 +4727,7 @@ mod tests {
#[test]
fn test_openrouter_free_short_alias_supports_tools() {
let catalog = ModelCatalog::new();
let entry = catalog
.find_model("free")
.expect("free alias must resolve");
let entry = catalog.find_model("free").expect("free alias must resolve");
assert_eq!(entry.provider, "openrouter");
assert!(
entry.supports_tools,
+33 -38
View File
@@ -3119,8 +3119,7 @@ async fn tool_image_generate(
// Closes #1051: route to a local OpenAI-compatible image generation
// service when `media.image_gen_base_url` is set.
let base_url_override = media_engine
.and_then(|e| e.config().image_gen_base_url.as_deref());
let base_url_override = media_engine.and_then(|e| e.config().image_gen_base_url.as_deref());
let result = crate::image_gen::generate_image(&request, base_url_override).await?;
// Save images to workspace if available
@@ -3636,9 +3635,9 @@ fn tool_skill_describe(
.ok_or("Missing 'name' parameter")?
.trim();
let registry = skill_registry.ok_or("No skill registry available")?;
let skill = registry
.get(name)
.ok_or_else(|| format!("Skill '{name}' not found. Use skill_list to see installed skills."))?;
let skill = registry.get(name).ok_or_else(|| {
format!("Skill '{name}' not found. Use skill_list to see installed skills.")
})?;
let body = skill
.manifest
.prompt_context
@@ -3672,9 +3671,9 @@ async fn tool_skill_execute(
.ok_or("Missing 'skill' parameter")?
.trim();
let registry = skill_registry.ok_or("No skill registry available")?;
let skill = registry
.get(skill_name)
.ok_or_else(|| format!("Skill '{skill_name}' not found. Use skill_list to see installed skills."))?;
let skill = registry.get(skill_name).ok_or_else(|| {
format!("Skill '{skill_name}' not found. Use skill_list to see installed skills.")
})?;
// If no tool name was given, default behavior depends on runtime.
// For prompt-only skills, return the SKILL.md body (most useful response
@@ -4017,14 +4016,14 @@ mod tests {
async fn test_create_directory_creates_nested() {
let tmp = tempfile::TempDir::new().unwrap();
let root = tmp.path();
let result = tool_create_directory(
&serde_json::json!({"path": "a/b/c"}),
Some(root),
)
.await;
let result = tool_create_directory(&serde_json::json!({"path": "a/b/c"}), Some(root)).await;
assert!(result.is_ok(), "Expected Ok, got: {:?}", result);
let expected = root.join("a").join("b").join("c");
assert!(expected.is_dir(), "Expected directory to exist: {}", expected.display());
assert!(
expected.is_dir(),
"Expected directory to exist: {}",
expected.display()
);
}
#[tokio::test]
@@ -4032,18 +4031,10 @@ mod tests {
let tmp = tempfile::TempDir::new().unwrap();
let root = tmp.path();
// First create
let r1 = tool_create_directory(
&serde_json::json!({"path": "data/logs"}),
Some(root),
)
.await;
let r1 = tool_create_directory(&serde_json::json!({"path": "data/logs"}), Some(root)).await;
assert!(r1.is_ok());
// Second create on existing dir should also succeed
let r2 = tool_create_directory(
&serde_json::json!({"path": "data/logs"}),
Some(root),
)
.await;
let r2 = tool_create_directory(&serde_json::json!({"path": "data/logs"}), Some(root)).await;
assert!(r2.is_ok(), "Expected idempotent success, got: {:?}", r2);
}
@@ -4063,23 +4054,27 @@ mod tests {
"test-id",
"create_directory",
&serde_json::json!({"path": "nested/folder"}),
None, // kernel
None, // allowed_tools
None, // caller_agent_id
None, // skill_registry
None, // mcp_connections
None, // web_ctx
None, // browser_ctx
None, // allowed_env_vars
None, // kernel
None, // allowed_tools
None, // caller_agent_id
None, // skill_registry
None, // mcp_connections
None, // web_ctx
None, // browser_ctx
None, // allowed_env_vars
Some(root.as_path()), // workspace_root
None, // media_engine
None, // exec_policy
None, // tts_engine
None, // docker_config
None, // process_manager
None, // media_engine
None, // exec_policy
None, // tts_engine
None, // docker_config
None, // process_manager
)
.await;
assert!(!result.is_error, "Expected success, got: {}", result.content);
assert!(
!result.is_error,
"Expected success, got: {}",
result.content
);
assert!(root.join("nested").join("folder").is_dir());
}
+4 -11
View File
@@ -60,11 +60,8 @@ impl InstallOptions {
/// Well-known filenames the installer searches for a detached signature
/// envelope, in priority order.
const SIGNATURE_CANDIDATES: &[&str] = &[
"signature.json",
"skill.toml.sig.json",
"SKILL.md.sig.json",
];
const SIGNATURE_CANDIDATES: &[&str] =
&["signature.json", "skill.toml.sig.json", "SKILL.md.sig.json"];
/// Normalize a manifest text for content-binding comparison.
///
@@ -167,10 +164,7 @@ pub fn load_signature(skill_dir: &Path) -> Result<Option<SignedManifest>, SkillE
/// Returns `SkillError::SecurityBlocked` when enforcement is on and the
/// skill fails any of those checks. On failure the caller is expected to
/// remove `skill_dir` to keep the skills directory clean.
pub fn enforce_require_signed(
skill_dir: &Path,
opts: &InstallOptions,
) -> Result<(), SkillError> {
pub fn enforce_require_signed(skill_dir: &Path, opts: &InstallOptions) -> Result<(), SkillError> {
if !opts.require_signed {
return Ok(());
}
@@ -214,8 +208,7 @@ pub fn enforce_require_signed(
// CRLF→LF normalization (applied symmetrically to both sides).
// `package.json` is included because openclaw_compat treats it as a
// valid SKILL manifest source.
const MANIFEST_CANDIDATES: &[&str] =
&["skill.toml", "SKILL.md", "skill.md", "package.json"];
const MANIFEST_CANDIDATES: &[&str] = &["skill.toml", "SKILL.md", "skill.md", "package.json"];
let normalized_envelope = normalize_manifest_text(&envelope.manifest);
let mut bound = false;
for name in MANIFEST_CANDIDATES {
+7 -4
View File
@@ -434,7 +434,9 @@ mod tests {
let restored: ContentBlock = serde_json::from_str(&serialized).unwrap();
match restored {
ContentBlock::Thinking {
thinking, signature, ..
thinking,
signature,
..
} => {
assert_eq!(thinking, "Let me reason about this carefully...");
assert_eq!(
@@ -517,7 +519,9 @@ mod tests {
assert_eq!(blocks.len(), 2);
match &blocks[0] {
ContentBlock::Thinking {
thinking, signature, ..
thinking,
signature,
..
} => {
assert_eq!(thinking, "Internal reasoning");
assert_eq!(signature.as_deref(), Some("sig_xyz"));
@@ -547,8 +551,7 @@ mod tests {
fn test_provider_msg_id_is_recorded_but_not_primary() {
// The LLM-supplied identifier is preserved for debugging only — the
// server-generated `msg_id` is unchanged and remains the primary key.
let msg =
Message::assistant("hi").with_provider_msg_id("msg_abc123_anthropic_collidable");
let msg = Message::assistant("hi").with_provider_msg_id("msg_abc123_anthropic_collidable");
assert_eq!(
msg.provider_msg_id.as_deref(),
Some("msg_abc123_anthropic_collidable")