From 1bc7580ecce167b5807501c44216a47f164af74b Mon Sep 17 00:00:00 2001 From: Zer4tul Date: Tue, 12 May 2026 10:59:19 +0800 Subject: [PATCH] refactor: remove Matrix bot, make agent-fleet platform-agnostic API service MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove src/integrations/matrix/ (bot connection, command parsing, notification formatting) - Remove matrix-sdk dependency from Cargo.toml - Remove MatrixConfig from config.rs and [matrix] from config.example.toml - Add GET /api/v1/tasks (list with status/agent_id filter) - Add POST /api/v1/tasks/{task_id}/retry (Failed/AgentLost → Assigned) - Add EventStore::list_tasks() with parameterized query - 29/29 tests pass Platform integration (Telegram, Matrix, Feishu) is Agent-side responsibility. agent-fleet is now a pure HTTP API orchestration engine. --- Cargo.lock | 1798 +---------------- Cargo.toml | 8 +- config.example.toml | 6 - .../.openspec.yaml | 2 + .../chatops-architecture-revision/design.md | 73 + .../chatops-architecture-revision/proposal.md | 38 + .../specs/matrix-chatops/spec.md | 31 + .../specs/task-api-endpoints/spec.md | 29 + .../chatops-architecture-revision/tasks.md | 22 + src/api.rs | 197 ++ src/config.rs | 15 - src/core/event_store.rs | 30 + src/integrations/matrix/mod.rs | 531 ----- src/integrations/mod.rs | 1 - src/main.rs | 21 +- 15 files changed, 435 insertions(+), 2367 deletions(-) create mode 100644 openspec/changes/chatops-architecture-revision/.openspec.yaml create mode 100644 openspec/changes/chatops-architecture-revision/design.md create mode 100644 openspec/changes/chatops-architecture-revision/proposal.md create mode 100644 openspec/changes/chatops-architecture-revision/specs/matrix-chatops/spec.md create mode 100644 openspec/changes/chatops-architecture-revision/specs/task-api-endpoints/spec.md create mode 100644 openspec/changes/chatops-architecture-revision/tasks.md delete mode 100644 src/integrations/matrix/mod.rs diff --git a/Cargo.lock b/Cargo.lock index d159cac..a0f1755 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,45 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "accessory" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87537f9ae7cfa78d5b8ebd1a1db25959f5e737126be4d8eb44a5452fc4b63cde" -dependencies = [ - "macroific", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "generic-array", -] - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures 0.2.17", -] - [[package]] name = "agent-fleet" version = "0.1.0" @@ -51,9 +12,7 @@ dependencies = [ "clap", "hex", "hmac", - "matrix-sdk", "reqwest", - "ruma", "rusqlite", "serde", "serde_json", @@ -155,99 +114,6 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" -[[package]] -name = "anymap2" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" - -[[package]] -name = "aquamarine" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f50776554130342de4836ba542aa85a4ddb361690d7e8df13774d7284c3d5c2" -dependencies = [ - "include_dir", - "itertools 0.10.5", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" -dependencies = [ - "serde", -] - -[[package]] -name = "as_variant" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dbc3a507a82b17ba0d98f6ce8fd6954ea0c8152e98009d36a40d8dcc8ce078a" - -[[package]] -name = "assign" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f093eed78becd229346bf859eec0aa4dd7ddde0757287b2b4107a1f09c80002" - -[[package]] -name = "async-channel" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-compression" -version = "0.4.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79b3f8a79cccc2898f31920fc69f304859b3bd567490f75ebf51ae1c792a9ac" -dependencies = [ - "compression-codecs", - "compression-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "async-trait" version = "0.1.89" @@ -335,60 +201,17 @@ dependencies = [ "syn", ] -[[package]] -name = "backoff" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" -dependencies = [ - "futures-core", - "getrandom 0.2.17", - "instant", - "pin-project-lite", - "rand 0.8.6", - "tokio", -] - [[package]] name = "base64" 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 = "bitflags" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" -dependencies = [ - "serde_core", -] - -[[package]] -name = "bitmaps" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d084b0137aaa901caf9f1e8b21daa6aa24d41cd806e111335541eff9683bd6" - -[[package]] -name = "blake3" -version = "1.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if", - "constant_time_eq", - "cpufeatures 0.3.0", -] [[package]] name = "block-buffer" @@ -399,57 +222,18 @@ dependencies = [ "generic-array", ] -[[package]] -name = "block-padding" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bs58" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" -dependencies = [ - "tinyvec", -] - [[package]] name = "bumpalo" version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -[[package]] -name = "bytesize" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e93abca9e28e0a1b9877922aacb20576e05d4679ffa78c3d6dc22a26a216659" - -[[package]] -name = "cbc" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" -dependencies = [ - "cipher", -] - [[package]] name = "cc" version = "1.2.62" @@ -466,30 +250,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" -[[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 = "chacha20poly1305" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" -dependencies = [ - "aead", - "chacha20", - "cipher", - "poly1305", - "zeroize", -] - [[package]] name = "chrono" version = "0.4.44" @@ -504,17 +264,6 @@ 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", - "inout", - "zeroize", -] - [[package]] name = "clap" version = "4.6.1" @@ -561,53 +310,6 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" -[[package]] -name = "compression-codecs" -version = "0.4.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" -dependencies = [ - "compression-core", - "flate2", - "memchr", -] - -[[package]] -name = "compression-core" -version = "0.4.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "const_panic" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e262cdaac42494e3ae34c43969f9cdeb7da178bdb4b66fa6a1ea2edb4c8ae652" -dependencies = [ - "typewit", -] - -[[package]] -name = "constant_time_eq" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" - [[package]] name = "core-foundation" version = "0.9.4" @@ -643,30 +345,6 @@ dependencies = [ "libc", ] -[[package]] -name = "cpufeatures" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - [[package]] name = "crypto-common" version = "0.1.7" @@ -674,135 +352,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", - "rand_core 0.6.4", "typenum", ] -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher", -] - -[[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", - "fiat-crypto", - "rustc_version", - "serde", - "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]] -name = "date_header" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c03c416ed1a30fbb027ef484ba6ab6f80e1eada675e1a2b92fd673c045a1f1d" - -[[package]] -name = "deadpool" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0be2b1d1d6ec8d846f05e137292d0b89133caf95ef33695424c09568bdd39b1b" -dependencies = [ - "deadpool-runtime", - "lazy_static", - "num_cpus", - "tokio", -] - -[[package]] -name = "deadpool-runtime" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" -dependencies = [ - "tokio", -] - -[[package]] -name = "deadpool-sqlite" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "656f14fc1ab819c65f332045ea7cb38841bbe551f3b2bc7e3abefb559af4155c" -dependencies = [ - "deadpool", - "deadpool-sync", - "rusqlite", -] - -[[package]] -name = "deadpool-sync" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524bc3df0d57e98ecd022e21ba31166c2625e7d3e5bcc4510efaeeab4abcab04" -dependencies = [ - "deadpool-runtime", -] - -[[package]] -name = "decancer" -version = "3.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9244323129647178bf41ac861a2cdb9d9c81b9b09d3d0d1de9cd302b33b8a1d" -dependencies = [ - "lazy_static", - "regex", -] - -[[package]] -name = "delegate-display" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98a85201f233142ac819bbf6226e36d0b5e129a47bd325084674261c82d4cd66" -dependencies = [ - "macroific", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid", - "zeroize", -] - -[[package]] -name = "deranged" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "powerfmt", -] - [[package]] name = "digest" version = "0.10.7" @@ -825,38 +377,6 @@ dependencies = [ "syn", ] -[[package]] -name = "ed25519" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" -dependencies = [ - "pkcs8", - "serde", - "signature", -] - -[[package]] -name = "ed25519-dalek" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" -dependencies = [ - "curve25519-dalek", - "ed25519", - "rand_core 0.6.4", - "serde", - "sha2", - "subtle", - "zeroize", -] - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - [[package]] name = "encoding_rs" version = "0.8.35" @@ -882,53 +402,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "event-listener" -version = "5.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener", - "pin-project-lite", -] - -[[package]] -name = "eyeball" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93bd0ebf93d61d6332d3c09a96e97975968a44e19a64c947bde06e6baff383f" -dependencies = [ - "futures-core", - "readlock", - "readlock-tokio", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "eyeball-im" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bce23da1ef2af356501c395426370ef09d778989557a749a874645ec74aba23" -dependencies = [ - "futures-core", - "imbl", - "tokio", - "tracing", -] - [[package]] name = "fallible-iterator" version = "0.3.0" @@ -941,46 +414,18 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" -[[package]] -name = "fancy_constructor" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07b19d0e43eae2bfbafe4931b5e79c73fb1a849ca15cd41a761a7b8587f9a1a2" -dependencies = [ - "macroific", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "fastrand" 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" -[[package]] -name = "flate2" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - [[package]] name = "fnv" version = "1.0.7" @@ -1032,23 +477,6 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" -[[package]] -name = "futures-io" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" - -[[package]] -name = "futures-macro" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "futures-sink" version = "0.3.32" @@ -1067,13 +495,8 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ - "futures-channel", "futures-core", - "futures-io", - "futures-macro", - "futures-sink", "futures-task", - "memchr", "pin-project-lite", "slab", ] @@ -1095,22 +518,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi 5.3.0", - "wasip2", ] [[package]] @@ -1121,48 +530,11 @@ checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi 6.0.0", + "r-efi", "wasip2", "wasip3", ] -[[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "gloo-utils" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" -dependencies = [ - "js-sys", - "serde", - "serde_json", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "growable-bloom-filter" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d174ccb4ba660d431329e7f0797870d0a4281e36353ec4b4a3c5eab6c2cfb6f1" -dependencies = [ - "serde", - "serde_bytes", - "serde_derive", - "xxhash-rust", -] - [[package]] name = "h2" version = "0.4.14" @@ -1221,27 +593,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - [[package]] name = "hmac" version = "0.12.1" @@ -1507,65 +864,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "imbl" -version = "4.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ae128b3bc67ed43ec0a7bb1c337a9f026717628b3c4033f07ded1da3e854951" -dependencies = [ - "bitmaps", - "imbl-sized-chunks", - "rand_core 0.6.4", - "rand_xoshiro", - "serde", - "version_check", -] - -[[package]] -name = "imbl-sized-chunks" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4241005618a62f8d57b2febd02510fb96e0137304728543dfc5fd6f052c22d" -dependencies = [ - "bitmaps", -] - -[[package]] -name = "include_dir" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" -dependencies = [ - "include_dir_macros", -] - -[[package]] -name = "include_dir_macros" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "indexed_db_futures" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43315957678a70eb21fb0d2384fe86dde0d6c859a01e24ce127eb65a0143d28c" -dependencies = [ - "accessory", - "cfg-if", - "delegate-display", - "fancy_constructor", - "js-sys", - "uuid", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "indexmap" version = "2.14.0" @@ -1578,25 +876,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "inout" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "block-padding", - "generic-array", -] - -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - [[package]] name = "ipnet" version = "2.12.0" @@ -1609,24 +888,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.18" @@ -1645,44 +906,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "js_int" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d937f95470b270ce8b8950207715d71aa8e153c0d44c6684d59397ed4949160a" -dependencies = [ - "serde", -] - -[[package]] -name = "js_option" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68421373957a1593a767013698dbf206e2b221eefe97a44d98d18672ff38423c" -dependencies = [ - "serde", -] - -[[package]] -name = "konst" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97feab15b395d1860944abe6a8dd8ed9f8eadfae01750fada8427abda531d887" -dependencies = [ - "const_panic", - "konst_kernel", - "typewit", -] - -[[package]] -name = "konst_kernel" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4b1eb7788f3824c629b1116a7a9060d6e898c358ebff59070093d51103dcc3c" -dependencies = [ - "typewit", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -1739,59 +962,6 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" -[[package]] -name = "macroific" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05c00ac596022625d01047c421a0d97d7f09a18e429187b341c201cb631b9dd" -dependencies = [ - "macroific_attr_parse", - "macroific_core", - "macroific_macro", -] - -[[package]] -name = "macroific_attr_parse" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd94d5da95b30ae6e10621ad02340909346ad91661f3f8c0f2b62345e46a2f67" -dependencies = [ - "cfg-if", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "macroific_core" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13198c120864097a565ccb3ff947672d969932b7975ebd4085732c9f09435e55" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "macroific_macro" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c9853143cbed7f1e41dc39fee95f9b361bec65c8dc2a01bf609be01b61f5ae" -dependencies = [ - "macroific_attr_parse", - "macroific_core", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - [[package]] name = "matchers" version = "0.2.0" @@ -1807,246 +977,6 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" -[[package]] -name = "matrix-pickle" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c34e6db65145740459f2ca56623b40cd4e6000ffae2a7d91515fa82aa935dbf" -dependencies = [ - "matrix-pickle-derive", - "thiserror", -] - -[[package]] -name = "matrix-pickle-derive" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a962fc9981f823f6555416dcb2ae9ae67ca412d767ee21ecab5150113ee6285b" -dependencies = [ - "proc-macro-crate", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "matrix-sdk" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27119e566a60f5681eb8d05f51ef10862dd9af611ac6c6e0dc9aa9bf3bcc493" -dependencies = [ - "anymap2", - "aquamarine", - "as_variant", - "async-channel", - "async-stream", - "async-trait", - "backoff", - "bytes", - "bytesize", - "event-listener", - "eyeball", - "eyeball-im", - "futures-core", - "futures-util", - "gloo-timers", - "growable-bloom-filter", - "http", - "imbl", - "indexmap", - "js_int", - "matrix-sdk-base", - "matrix-sdk-common", - "matrix-sdk-indexeddb", - "matrix-sdk-sqlite", - "mime", - "mime2ext", - "once_cell", - "percent-encoding", - "pin-project-lite", - "reqwest", - "ruma", - "serde", - "serde_html_form", - "serde_json", - "tempfile", - "thiserror", - "tokio", - "tokio-stream", - "tokio-util", - "tracing", - "url", - "urlencoding", - "vodozemac", - "zeroize", -] - -[[package]] -name = "matrix-sdk-base" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58884b338e0c2eb4aa09d63ba2a5937fb5bd691525884f09935900137fc6b908" -dependencies = [ - "as_variant", - "async-trait", - "bitflags", - "decancer", - "eyeball", - "eyeball-im", - "futures-util", - "growable-bloom-filter", - "matrix-sdk-common", - "matrix-sdk-crypto", - "matrix-sdk-store-encryption", - "once_cell", - "regex", - "ruma", - "serde", - "serde_json", - "thiserror", - "tokio", - "tracing", - "unicode-normalization", -] - -[[package]] -name = "matrix-sdk-common" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "072d77e461933834e12810d63906409f37a039acad31a16dda62b63e1f4c31cf" -dependencies = [ - "async-trait", - "eyeball-im", - "futures-core", - "futures-util", - "gloo-timers", - "imbl", - "ruma", - "serde", - "serde_json", - "thiserror", - "tokio", - "tracing", - "tracing-subscriber", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "matrix-sdk-crypto" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed1ec9d645eb86630b2ed71e5890565ca023f569d9d0ebdcb25bfca8a088c2f3" -dependencies = [ - "aes", - "aquamarine", - "as_variant", - "async-trait", - "bs58", - "byteorder", - "cfg-if", - "ctr", - "eyeball", - "futures-core", - "futures-util", - "hkdf", - "hmac", - "itertools 0.14.0", - "js_option", - "matrix-sdk-common", - "pbkdf2", - "rand 0.8.6", - "rmp-serde", - "ruma", - "serde", - "serde_json", - "sha2", - "subtle", - "thiserror", - "time", - "tokio", - "tokio-stream", - "tracing", - "ulid", - "url", - "vodozemac", - "zeroize", -] - -[[package]] -name = "matrix-sdk-indexeddb" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da30f51dbfcd03297a04f49f92c365a41cb2b012ad3338c0fc5d4efafcbff88b" -dependencies = [ - "anyhow", - "async-trait", - "base64", - "getrandom 0.2.17", - "gloo-utils", - "hkdf", - "indexed_db_futures", - "js-sys", - "matrix-sdk-crypto", - "matrix-sdk-store-encryption", - "ruma", - "serde", - "serde-wasm-bindgen", - "serde_json", - "sha2", - "thiserror", - "tokio", - "tracing", - "wasm-bindgen", - "web-sys", - "zeroize", -] - -[[package]] -name = "matrix-sdk-sqlite" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d07fb4e87c6ace1d05a87a91404acc3fd0b480ba9de75c08685ed18f1ea79f" -dependencies = [ - "async-trait", - "deadpool-sqlite", - "itertools 0.14.0", - "matrix-sdk-base", - "matrix-sdk-crypto", - "matrix-sdk-store-encryption", - "rmp-serde", - "ruma", - "rusqlite", - "serde", - "serde_json", - "thiserror", - "tokio", - "tracing", - "vodozemac", -] - -[[package]] -name = "matrix-sdk-store-encryption" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcc8b6650757f953664e5f906988690cef05c09d83081946adce446c45810a2d" -dependencies = [ - "base64", - "blake3", - "chacha20poly1305", - "hmac", - "pbkdf2", - "rand 0.8.6", - "rmp-serde", - "serde", - "serde_json", - "sha2", - "thiserror", - "zeroize", -] - [[package]] name = "memchr" version = "2.8.0" @@ -2059,22 +989,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "mime2ext" -version = "0.1.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf6f36070878c42c5233846cd3de24cf9016828fd47bc22957a687298bb21fc" - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - [[package]] name = "mio" version = "1.2.0" @@ -2112,12 +1026,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "num-conv" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" - [[package]] name = "num-traits" version = "0.2.19" @@ -2127,16 +1035,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "once_cell" version = "1.21.4" @@ -2149,12 +1047,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - [[package]] name = "openssl" version = "0.10.79" @@ -2198,12 +1090,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - [[package]] name = "parking_lot" version = "0.12.5" @@ -2227,16 +1113,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "pbkdf2" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" -dependencies = [ - "digest", - "hmac", -] - [[package]] name = "percent-encoding" version = "2.3.2" @@ -2249,33 +1125,12 @@ 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", - "spki", -] - [[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" @@ -2285,21 +1140,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - [[package]] name = "prettyplease" version = "0.2.37" @@ -2310,36 +1150,6 @@ dependencies = [ "syn", ] -[[package]] -name = "proc-macro-crate" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" -dependencies = [ - "toml_edit 0.25.11+spec-1.1.0", -] - -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", -] - [[package]] name = "proc-macro2" version = "1.0.106" @@ -2349,29 +1159,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "prost" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-derive" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" -dependencies = [ - "anyhow", - "itertools 0.14.0", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "quote" version = "1.0.45" @@ -2381,101 +1168,12 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - [[package]] name = "r-efi" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" -[[package]] -name = "rand" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] - -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "rand_xoshiro" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" -dependencies = [ - "rand_core 0.6.4", -] - -[[package]] -name = "readlock" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6da6f291b23556edd9edaf655a0be2ad8ef8002ff5f1bca62b264f3f58b53f34" - -[[package]] -name = "readlock-tokio" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7e264f9ec4f3d112e8e2f214e8e7cb5cf3b83278f3570b7e00bfe13d3bd8ff" -dependencies = [ - "tokio", -] - [[package]] name = "redox_syscall" version = "0.5.18" @@ -2485,18 +1183,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "regex" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - [[package]] name = "regex-automata" version = "0.4.14" @@ -2524,7 +1210,6 @@ dependencies = [ "bytes", "encoding_rs", "futures-core", - "futures-util", "h2", "http", "http-body", @@ -2546,14 +1231,12 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-native-tls", - "tokio-util", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", "web-sys", ] @@ -2571,163 +1254,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rmp" -version = "0.8.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" -dependencies = [ - "num-traits", -] - -[[package]] -name = "rmp-serde" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" -dependencies = [ - "rmp", - "serde", -] - -[[package]] -name = "ruma" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fdaae631940eda62844a8a3026aba2ba84c22588c888ebec44861ba4d0c18" -dependencies = [ - "assign", - "js_int", - "js_option", - "ruma-client-api", - "ruma-common", - "ruma-events", - "ruma-federation-api", - "web-time", -] - -[[package]] -name = "ruma-client-api" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9a89ac03a0f4451f946ed9aed6fdd16ef5a78a3a2849e87af4b2474a176b2fb" -dependencies = [ - "as_variant", - "assign", - "bytes", - "date_header", - "http", - "js_int", - "js_option", - "maplit", - "ruma-common", - "ruma-events", - "serde", - "serde_html_form", - "serde_json", - "thiserror", - "url", - "web-time", -] - -[[package]] -name = "ruma-common" -version = "0.15.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "387e1898e868d32ff7b205e7db327361d5dcf635c00a8ae5865068607595a9cf" -dependencies = [ - "as_variant", - "base64", - "bytes", - "form_urlencoded", - "getrandom 0.2.17", - "http", - "indexmap", - "js-sys", - "js_int", - "konst", - "percent-encoding", - "rand 0.8.6", - "regex", - "ruma-identifiers-validation", - "ruma-macros", - "serde", - "serde_html_form", - "serde_json", - "thiserror", - "time", - "tracing", - "url", - "uuid", - "web-time", - "wildmatch", -] - -[[package]] -name = "ruma-events" -version = "0.30.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f141b37dcd3cfa1199d6a13929db59be529b2c69107edc9f1702b81015e970b2" -dependencies = [ - "as_variant", - "indexmap", - "js_int", - "js_option", - "percent-encoding", - "regex", - "ruma-common", - "ruma-identifiers-validation", - "ruma-macros", - "serde", - "serde_json", - "thiserror", - "tracing", - "url", - "web-time", - "wildmatch", -] - -[[package]] -name = "ruma-federation-api" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2a705c3911870782e036a3a8b676d0166c6c93800b84f6b8b23c981f78ef08" -dependencies = [ - "http", - "js_int", - "mime", - "ruma-common", - "ruma-events", - "serde", - "serde_json", -] - -[[package]] -name = "ruma-identifiers-validation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ad674b5e5368c53a2c90fde7dac7e30747004aaf7b1827b72874a25fc06d4d8" -dependencies = [ - "js_int", - "thiserror", -] - -[[package]] -name = "ruma-macros" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ff13fbd6045a7278533390826de316d6116d8582ed828352661337b0c422e1c" -dependencies = [ - "cfg-if", - "proc-macro-crate", - "proc-macro2", - "quote", - "ruma-identifiers-validation", - "serde", - "syn", - "toml", -] - [[package]] name = "rusqlite" version = "0.32.1" @@ -2742,15 +1268,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - [[package]] name = "rustix" version = "1.1.4" @@ -2863,27 +1380,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-wasm-bindgen" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" -dependencies = [ - "js-sys", - "serde", - "wasm-bindgen", -] - -[[package]] -name = "serde_bytes" -version = "0.11.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" -dependencies = [ - "serde", - "serde_core", -] - [[package]] name = "serde_core" version = "1.0.228" @@ -2904,19 +1400,6 @@ dependencies = [ "syn", ] -[[package]] -name = "serde_html_form" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2f2d7ff8a2140333718bb329f5c40fc5f0865b84c426183ce14c97d2ab8154f" -dependencies = [ - "form_urlencoded", - "indexmap", - "itoa", - "ryu", - "serde_core", -] - [[package]] name = "serde_json" version = "1.0.149" @@ -2969,7 +1452,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures 0.2.17", + "cpufeatures", "digest", ] @@ -2998,21 +1481,6 @@ 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 = "simd-adler32" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" - [[package]] name = "slab" version = "0.4.12" @@ -3035,16 +1503,6 @@ dependencies = [ "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", -] - [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -3157,37 +1615,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "time" -version = "0.3.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" - -[[package]] -name = "time-macros" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" -dependencies = [ - "num-conv", - "time-core", -] - [[package]] name = "tinystr" version = "0.8.3" @@ -3198,21 +1625,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "tinyvec" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tokio" version = "1.52.3" @@ -3261,18 +1673,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-stream" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", - "tokio-util", -] - [[package]] name = "tokio-util" version = "0.7.18" @@ -3294,8 +1694,8 @@ checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", - "toml_datetime 0.6.11", - "toml_edit 0.22.27", + "toml_datetime", + "toml_edit", ] [[package]] @@ -3307,15 +1707,6 @@ dependencies = [ "serde", ] -[[package]] -name = "toml_datetime" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" -dependencies = [ - "serde_core", -] - [[package]] name = "toml_edit" version = "0.22.27" @@ -3325,30 +1716,9 @@ dependencies = [ "indexmap", "serde", "serde_spanned", - "toml_datetime 0.6.11", + "toml_datetime", "toml_write", - "winnow 0.7.15", -] - -[[package]] -name = "toml_edit" -version = "0.25.11+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" -dependencies = [ - "indexmap", - "toml_datetime 1.1.1+spec-1.1.0", - "toml_parser", - "winnow 1.0.2", -] - -[[package]] -name = "toml_parser" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" -dependencies = [ - "winnow 1.0.2", + "winnow", ] [[package]] @@ -3379,17 +1749,12 @@ version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51" dependencies = [ - "async-compression", "bitflags", "bytes", - "futures-core", "futures-util", "http", "http-body", - "http-body-util", "pin-project-lite", - "tokio", - "tokio-util", "tower", "tower-layer", "tower-service", @@ -3496,62 +1861,18 @@ version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" -[[package]] -name = "typewit" -version = "1.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "214ca0b2191785cbc06209b9ca1861e048e39b5ba33574b3cedd58363d5bb5f6" -dependencies = [ - "typewit_proc_macros", -] - -[[package]] -name = "typewit_proc_macros" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36a83ea2b3c704935a01b4642946aadd445cea40b10935e3f8bd8052b8193d6" - -[[package]] -name = "ulid" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "470dbf6591da1b39d43c14523b2b469c86879a53e8b758c8e090a470fe7b1fbe" -dependencies = [ - "rand 0.9.4", - "web-time", -] - [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" -[[package]] -name = "unicode-normalization" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" -dependencies = [ - "tinyvec", -] - [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common", - "subtle", -] - [[package]] name = "untrusted" version = "0.9.0" @@ -3568,15 +1889,8 @@ dependencies = [ "idna", "percent-encoding", "serde", - "serde_derive", ] -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -3619,36 +1933,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "vodozemac" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c022a277687e4e8685d72b95a7ca3ccfec907daa946678e715f8badaa650883d" -dependencies = [ - "aes", - "arrayvec", - "base64", - "base64ct", - "cbc", - "chacha20poly1305", - "curve25519-dalek", - "ed25519-dalek", - "getrandom 0.2.17", - "hkdf", - "hmac", - "matrix-pickle", - "prost", - "rand 0.8.6", - "serde", - "serde_bytes", - "serde_json", - "sha2", - "subtle", - "thiserror", - "x25519-dalek", - "zeroize", -] - [[package]] name = "want" version = "0.3.1" @@ -3759,19 +2043,6 @@ dependencies = [ "wasmparser", ] -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "wasmparser" version = "0.244.0" @@ -3794,22 +2065,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "wildmatch" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29333c3ea1ba8b17211763463ff24ee84e41c78224c16b001cd907e663a38c68" - [[package]] name = "windows-core" version = "0.62.2" @@ -3971,15 +2226,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winnow" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" -dependencies = [ - "memchr", -] - [[package]] name = "wit-bindgen" version = "0.51.0" @@ -4080,24 +2326,6 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" -[[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 = "xxhash-rust" -version = "0.8.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" - [[package]] name = "yoke" version = "0.8.2" @@ -4167,20 +2395,6 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] [[package]] name = "zerotrie" diff --git a/Cargo.toml b/Cargo.toml index ef5a30c..7339c79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "agent-fleet" version = "0.1.0" edition = "2024" -description = "Agent Fleet Platform - Multi-agent orchestration with Forgejo + Matrix" +description = "Agent Fleet Platform - Multi-agent orchestration with Forgejo" license = "MIT" [dependencies] @@ -22,13 +22,9 @@ rusqlite = { version = "0.32", features = ["bundled"] } # Configuration toml = "0.8" -# HTTP client (for Forgejo API, Matrix API) +# HTTP client (for Forgejo API) reqwest = { version = "0.12", features = ["json"] } -# Matrix SDK -matrix-sdk = "0.10" -ruma = { version = "0.12", features = ["client-api-c", "rand", "unstable-msc3061", "unstable-msc2448"] } - # Logging tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } diff --git a/config.example.toml b/config.example.toml index cc65d96..610c0b2 100644 --- a/config.example.toml +++ b/config.example.toml @@ -7,12 +7,6 @@ url = "https://git.0x08.org" token = "" # Forgejo API token webhook_secret = "" # Webhook shared secret -[matrix] -homeserver_url = "https://matrix.0x08.org" -user_id = "@jeeves:0x08.org" -access_token = "" # Matrix bot access token -room_id = "" # Coordination room ID - [orchestrator] db_path = "data/agent-fleet.db" heartbeat_interval_secs = 60 diff --git a/openspec/changes/chatops-architecture-revision/.openspec.yaml b/openspec/changes/chatops-architecture-revision/.openspec.yaml new file mode 100644 index 0000000..81cd71f --- /dev/null +++ b/openspec/changes/chatops-architecture-revision/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-05-11 diff --git a/openspec/changes/chatops-architecture-revision/design.md b/openspec/changes/chatops-architecture-revision/design.md new file mode 100644 index 0000000..590e3f6 --- /dev/null +++ b/openspec/changes/chatops-architecture-revision/design.md @@ -0,0 +1,73 @@ +## Context + +agent-fleet Phase 1 实现了独立的 Matrix bot(通过 matrix-sdk),直接处理 ChatOps 命令和通知推送。这违反了架构职责分离原则:agent-fleet 的角色是纯编排引擎,不应该知道聊天平台的存在。 + +正确的职责边界: +- **agent-fleet**:纯 HTTP API 服务,管理任务队列、状态机、receipt 验证。返回结构化 JSON。 +- **Agent 层**:各自负责平台接入。OpenClaw/Jeeves 连接 Telegram/Matrix/Feishu;Hermes 连接 Telegram/Matrix;Claude Code / Codex / OpenCode 通过 cc-connect 等工具连接。Agent 通过 HTTP API 调用 agent-fleet,自行决定如何展示给人类。 + +## Goals / Non-Goals + +**Goals:** +- agent-fleet 成为纯 HTTP API 服务,不持有任何聊天协议连接 +- 新增 HTTP API 端点暴露任务查询和操作 +- 移除 matrix-sdk 依赖,减小编译时间和二进制体积 +- 删除所有不属于编排引擎职责的代码(通知格式化、命令解析) + +**Non-Goals:** +- 不设计 webhook/SSE 推送机制(Phase 2 考虑) +- 不实现 Agent 侧的展示逻辑(各 Agent 自行处理) +- 不关心 Agent 如何连接聊天平台(Agent 自己的事) + +## Decisions + +### Decision 1: agent-fleet 是纯 API 服务,平台无关 + +**选择**: agent-fleet 不连接任何聊天平台,只暴露 HTTP API + +**理由**: +- 编排引擎不应该绑定特定聊天平台 +- 平台接入是 Agent 侧的职责:OpenClaw、Hermes 自行连接,Claude Code / Codex 通过 cc-connect 连接 +- 用户可能同时使用 Telegram、Matrix、Feishu,agent-fleet 不需要知道这些 +- 减少 matrix-sdk 依赖(编译慢、体积大) + +**替代方案**: +- 内置多平台支持(Telegram bot + Matrix bot + Feishu bot):严重过度设计 +- 单平台 bot:限制了平台选择 + +### Decision 2: 删除通知格式化函数 + +**选择**: 不在 agent-fleet 中保留任何通知格式化代码 + +**理由**: +- agent-fleet 返回结构化 JSON,格式化为人类可读文本是 Agent 的事 +- 不同平台有不同的展示能力(Telegram 支持 Markdown,Feishu 支持卡片,Matrix 支持-thread) +- 保留格式化函数会诱导未来继续往 agent-fleet 里塞展示逻辑 + +### Decision 3: 新增任务查询和重试 API + +**选择**: `GET /api/v1/tasks` + `POST /api/v1/tasks/{id}/retry` + +**理由**: +- Agent 需要查询任务状态来展示给人类 +- Agent 需要触发重试操作响应人类命令 +- RESTful 风格,与已有 API 一致 + +## Risks / Trade-offs + +- **[实时性] Agent 需要轮询获取状态变更** → Phase 1 可接受;Phase 2 加 SSE 或 webhook +- **[重复劳动] 每个 Agent 都要写通知格式化** → 各 Agent 最了解自己平台的展示能力,这不算重复 + +## Migration Plan + +1. 删除 `src/integrations/matrix/` 目录 +2. 删除通知格式化代码 +3. 新增 API 端点和 EventStore 查询方法 +4. 清理 config 和依赖 +5. 更新测试 + +**Rollback**: git history 可恢复。 + +## Open Questions + +- Phase 2 是否需要 webhook/SSE 推送?还是 Agent heartbeat 轮询足够? diff --git a/openspec/changes/chatops-architecture-revision/proposal.md b/openspec/changes/chatops-architecture-revision/proposal.md new file mode 100644 index 0000000..ecfe02d --- /dev/null +++ b/openspec/changes/chatops-architecture-revision/proposal.md @@ -0,0 +1,38 @@ +## Why + +agent-fleet 当前实现了一个独立的 Matrix bot 连接(matrix-sdk),直接处理 ChatOps 命令和通知推送。这违反了架构职责分离原则: + +1. **Session 冲突**:`@jeeves:0x08.org` 已被 OpenClaw 占用 +2. **架构冗余**:agent-fleet 重新实现了消息收发、命令路由、通知推送 +3. **违背最小化原则**:agent-fleet 的核心价值是编排引擎(任务队列、状态机、receipt 验证),不是聊天界面 +4. **平台绑定**:硬编码 Matrix 协议,但 agent-fleet 作为纯 API 服务应该是平台无关的 + +agent-fleet 应该是一个纯 HTTP API 服务,不知道也不关心聊天平台的存在。平台接入(Telegram、Matrix、Feishu 等)完全是 Agent 侧的事情: +- OpenClaw/Jeeves 自己连接多个平台 +- Hermes Agent 自己连接 Telegram 或 Matrix +- Claude Code / Codex / OpenCode 通过 cc-connect 等扩展工具连接平台 + +## What Changes + +- **BREAKING**: 移除 `src/integrations/matrix/` 中的独立 Matrix bot 实现 +- 移除通知格式化函数(不属于编排引擎的职责) +- 新增 `GET /api/v1/tasks` 端点(任务列表查询,支持过滤) +- 新增 `POST /api/v1/tasks/{id}/retry` 端点(任务重试) +- `config.toml` 移除 `[matrix]` section +- `Cargo.toml` 移除 `matrix-sdk` 依赖 + +## Capabilities + +### New Capabilities +- `task-api-endpoints`: 任务查询和操作 HTTP API 端点(`GET /api/v1/tasks`, `POST /api/v1/tasks/{id}/retry`),返回结构化 JSON + +### Modified Capabilities +- `matrix-chatops`: 移除独立 bot 连接和 ChatOps 功能。Orchestrator 只负责暴露 HTTP API,不涉及任何聊天平台集成。本 capability 修订后实质上变为"Orchestrator API 暴露足够信息供外部 Agent 展示"。 + +## Impact + +- **代码**:删除 `src/integrations/matrix/` 目录及相关引用 +- **API**:新增 2 个端点,无破坏性变更 +- **配置**:`config.toml` 移除 `[matrix]` section +- **依赖**:从 `Cargo.toml` 移除 `matrix-sdk` +- **职责边界**:agent-fleet 只返回结构化 JSON,不关心如何展示给人类 diff --git a/openspec/changes/chatops-architecture-revision/specs/matrix-chatops/spec.md b/openspec/changes/chatops-architecture-revision/specs/matrix-chatops/spec.md new file mode 100644 index 0000000..0b6f8ab --- /dev/null +++ b/openspec/changes/chatops-architecture-revision/specs/matrix-chatops/spec.md @@ -0,0 +1,31 @@ +## MODIFIED Requirements + +### Requirement: Orchestrator exposes status via API only +Orchestrator SHALL 通过 HTTP API 暴露所有任务和 Agent 状态信息,不连接任何外部聊天或通知平台。平台接入由各 Agent 自行处理。 + +#### Scenario: Task state change queryable via API +- **WHEN** 任务 #42 状态变更(assigned、completed、failed 等) +- **THEN** 变更 SHALL 通过 `GET /api/v1/tasks` API 可查询 +- **AND** 变更 SHALL 记录在 task_events 表中 + +#### Scenario: Agent state change queryable via API +- **WHEN** Agent `worker-03` 状态变更(online、offline 等) +- **THEN** 变更 SHALL 通过 `GET /api/v1/agents` API 可查询 + +## REMOVED Requirements + +### Requirement: Matrix room as coordination channel +**Reason**: Orchestrator 不连接任何聊天平台。平台接入是 Agent 侧的职责。 +**Migration**: 各 Agent(OpenClaw/Jeeves、Hermes 等)自行连接聊天平台,通过 Orchestrator HTTP API 获取状态后展示。 + +### Requirement: Slash commands for orchestration +**Reason**: Orchestrator 不处理命令。命令解析和路由由各 Agent 自行处理。 +**Migration**: 各 Agent 解析用户命令,调用 Orchestrator HTTP API 执行操作。 + +### Requirement: Matrix notifications for receipts +**Reason**: Orchestrator 不推送通知。通知由各 Agent 根据所在平台能力自行处理。 +**Migration**: Agent 通过 API 轮询或 webhook 获取 receipt 状态变更,自行决定通知方式和格式。 + +### Requirement: Per-agent Matrix thread +**Reason**: Orchestrator 不了解任何聊天平台概念。展示方式由各 Agent 根据平台能力决定。 +**Migration**: Agent 通过 `GET /api/v1/tasks?agent_id=xxx` 获取任务历史,自行选择展示方式。 diff --git a/openspec/changes/chatops-architecture-revision/specs/task-api-endpoints/spec.md b/openspec/changes/chatops-architecture-revision/specs/task-api-endpoints/spec.md new file mode 100644 index 0000000..d3ce0d4 --- /dev/null +++ b/openspec/changes/chatops-architecture-revision/specs/task-api-endpoints/spec.md @@ -0,0 +1,29 @@ +## ADDED Requirements + +### Requirement: Task list API endpoint +Orchestrator SHALL 提供 `GET /api/v1/tasks` 端点,返回任务列表的结构化 JSON。 + +#### Scenario: List all tasks +- **WHEN** 发送 `GET /api/v1/tasks` +- **THEN** SHALL 返回 JSON 数组,每项包含:task_id, source, task_type, priority, status, assigned_agent_id, retry_count, max_retries, created_at, assigned_at, started_at, completed_at + +#### Scenario: Filter by status +- **WHEN** 发送 `GET /api/v1/tasks?status=running` +- **THEN** SHALL 仅返回 status 为 `running` 的任务 + +#### Scenario: Filter by agent +- **WHEN** 发送 `GET /api/v1/tasks?agent_id=worker-03` +- **THEN** SHALL 仅返回 assigned_agent_id 为 `worker-03` 的任务 + +### Requirement: Task retry API endpoint +Orchestrator SHALL 提供 `POST /api/v1/tasks/{task_id}/retry` 端点。 + +#### Scenario: Retry a failed task +- **WHEN** 发送 `POST /api/v1/tasks/org/repo#42/retry` +- **AND** 任务当前状态为 `failed` 或 `agent_lost` +- **THEN** SHALL 将任务状态转换为 `assigned`,返回更新后的任务 JSON + +#### Scenario: Retry a non-retryable task +- **WHEN** 发送 `POST /api/v1/tasks/org/repo#42/retry` +- **AND** 任务当前状态不是 `failed` 或 `agent_lost` +- **THEN** SHALL 返回 400 错误,说明任务不在可重试状态 diff --git a/openspec/changes/chatops-architecture-revision/tasks.md b/openspec/changes/chatops-architecture-revision/tasks.md new file mode 100644 index 0000000..f17e94c --- /dev/null +++ b/openspec/changes/chatops-architecture-revision/tasks.md @@ -0,0 +1,22 @@ +## 1. 移除 Matrix bot 和通知格式化代码 + +- [ ] 1.1 删除 `src/integrations/matrix/` 目录 +- [ ] 1.2 从 `src/integrations/mod.rs` 移除 `pub mod matrix;` +- [ ] 1.3 从 `src/main.rs` 移除 Matrix bot 启动逻辑 +- [ ] 1.4 从 `Cargo.toml` 移除 `matrix-sdk` 依赖 +- [ ] 1.5 从 `config.example.toml` 移除 `[matrix]` section +- [ ] 1.6 从 `src/config.rs` 移除 `MatrixConfig` struct 及相关字段 +- [ ] 1.7 删除通知格式化函数和命令解析代码(如已从 matrix 模块导出) + +## 2. 新增 HTTP API 端点 + +- [ ] 2.1 实现 `GET /api/v1/tasks`:返回任务列表 JSON,支持 `status` 和 `agent_id` 查询参数过滤 +- [ ] 2.2 实现 `POST /api/v1/tasks/{task_id}/retry`:对 failed/agent_lost 任务触发重新入队,非可重试状态返回 400 +- [ ] 2.3 在 EventStore 中添加 `list_tasks(status: Option<&str>, agent_id: Option<&str>)` 查询方法 +- [ ] 2.4 在 `src/main.rs` 注册新路由 + +## 3. 测试与验证 + +- [ ] 3.1 `cargo check` 通过(无 matrix-sdk 依赖) +- [ ] 3.2 `cargo test` 全部通过(移除 matrix 相关测试,新增 API 端点测试) +- [ ] 3.3 新增 API 端点测试:任务列表过滤、重试成功、重试失败场景 diff --git a/src/api.rs b/src/api.rs index fbc994a..8270859 100644 --- a/src/api.rs +++ b/src/api.rs @@ -379,6 +379,58 @@ fn parse_task_source(source: &str) -> Option<(String, u64)> { Some((repo.to_string(), issue_number)) } +#[derive(Debug, Deserialize)] +pub struct ListTasksQuery { + pub status: Option, + pub agent_id: Option, +} + +pub async fn list_tasks( + State(state): State, + Query(query): Query, +) -> Result>, ApiError> { + let store = state.store.clone(); + + tokio::task::spawn_blocking(move || -> Result>, ApiError> { + let store = store.lock().map_err(|e| ApiError::Poisoned(e.to_string()))?; + let tasks = store.list_tasks(query.status.as_deref(), query.agent_id.as_deref())?; + Ok(Json(tasks)) + }) + .await? +} + +pub async fn retry_task( + State(state): State, + axum::extract::Path(task_id): axum::extract::Path, +) -> Result, ApiError> { + let store = state.store.clone(); + let sm = StateMachine::new(store.clone()); + + let task_id_for_check = task_id.clone(); + let current = tokio::task::spawn_blocking(move || -> Result, ApiError> { + let store = store.lock().map_err(|e| ApiError::Poisoned(e.to_string()))?; + Ok(store.read_task(&task_id_for_check)?) + }) + .await??; + + let task = current.ok_or_else(|| ApiError::NotFound(format!("task {}", task_id)))?; + + if !matches!(task.status, TaskStatus::Failed | TaskStatus::AgentLost) { + return Err(ApiError::BadRequest(format!( + "task {} is not retryable (current status: {})", + task.task_id, + task.status.as_str() + ))); + } + + let updated = sm + .transition(&task_id, TaskStatus::Assigned, None, "retry") + .await + .map_err(|e| ApiError::BadRequest(e.to_string()))?; + + Ok(Json(updated)) +} + #[cfg(test)] mod tests { use super::*; @@ -734,4 +786,149 @@ mod tests { }; assert_eq!(task.status, TaskStatus::Running); } + + // ─── Task API tests ───────────────────────────────────────── + + fn sample_task_variant(task_id: &str, status: TaskStatus, agent_id: Option<&str>) -> Task { + Task { + task_id: task_id.to_string(), + source: format!("forgejo:org/repo#{task_id}"), + task_type: "code".into(), + priority: Priority::High, + status, + assigned_agent_id: agent_id.map(String::from), + requirements: "do something".into(), + labels: vec!["agent:code".into(), "priority:high".into()], + created_at: Utc::now(), + assigned_at: None, + started_at: None, + completed_at: None, + retry_count: 0, + max_retries: 2, + timeout_seconds: 1800, + } + } + + #[tokio::test] + async fn list_tasks_returns_all_tasks() { + let (_dir, state) = test_store(); + { + let store = state.store.lock().unwrap(); + store.insert_task(&sample_task_variant("task-1", TaskStatus::Created, None)).unwrap(); + store.insert_task(&sample_task_variant("task-2", TaskStatus::Running, Some("worker-01"))).unwrap(); + } + + let tasks = list_tasks( + State(state), + Query(ListTasksQuery { status: None, agent_id: None }), + ) + .await + .unwrap(); + + assert_eq!(tasks.0.len(), 2); + } + + #[tokio::test] + async fn list_tasks_filters_by_status() { + let (_dir, state) = test_store(); + { + let store = state.store.lock().unwrap(); + store.insert_task(&sample_task_variant("task-1", TaskStatus::Created, None)).unwrap(); + store.insert_task(&sample_task_variant("task-2", TaskStatus::Running, Some("worker-01"))).unwrap(); + } + + let tasks = list_tasks( + State(state), + Query(ListTasksQuery { status: Some("running".into()), agent_id: None }), + ) + .await + .unwrap(); + + assert_eq!(tasks.0.len(), 1); + assert_eq!(tasks.0[0].task_id, "task-2"); + assert_eq!(tasks.0[0].status, TaskStatus::Running); + } + + #[tokio::test] + async fn list_tasks_filters_by_agent() { + let (_dir, state) = test_store(); + { + let store = state.store.lock().unwrap(); + store.insert_task(&sample_task_variant("task-1", TaskStatus::Running, Some("worker-01"))).unwrap(); + store.insert_task(&sample_task_variant("task-2", TaskStatus::Running, Some("worker-02"))).unwrap(); + } + + let tasks = list_tasks( + State(state), + Query(ListTasksQuery { status: None, agent_id: Some("worker-01".into()) }), + ) + .await + .unwrap(); + + assert_eq!(tasks.0.len(), 1); + assert_eq!(tasks.0[0].task_id, "task-1"); + } + + #[tokio::test] + async fn retry_task_succeeds_for_failed_task() { + let (_dir, state) = test_store(); + { + let store = state.store.lock().unwrap(); + store.insert_task(&sample_task_variant("task-failed", TaskStatus::Failed, Some("worker-01"))).unwrap(); + } + + let updated = retry_task(State(state.clone()), axum::extract::Path("task-failed".to_string())) + .await + .unwrap(); + + assert_eq!(updated.0.status, TaskStatus::Assigned); + + // Verify in DB + let task = { + let store = state.store.lock().unwrap(); + store.read_task("task-failed").unwrap().unwrap() + }; + assert_eq!(task.status, TaskStatus::Assigned); + } + + #[tokio::test] + async fn retry_task_succeeds_for_agent_lost_task() { + let (_dir, state) = test_store(); + { + let store = state.store.lock().unwrap(); + store.insert_task(&sample_task_variant("task-lost", TaskStatus::AgentLost, Some("worker-01"))).unwrap(); + } + + let updated = retry_task(State(state.clone()), axum::extract::Path("task-lost".to_string())) + .await + .unwrap(); + + assert_eq!(updated.0.status, TaskStatus::Assigned); + } + + #[tokio::test] + async fn retry_task_rejects_non_retryable_status() { + let (_dir, state) = test_store(); + { + let store = state.store.lock().unwrap(); + store.insert_task(&sample_task_variant("task-running", TaskStatus::Running, Some("worker-01"))).unwrap(); + } + + let err = retry_task(State(state.clone()), axum::extract::Path("task-running".to_string())) + .await + .unwrap_err(); + + assert!(matches!(err, ApiError::BadRequest(_))); + } + + #[tokio::test] + async fn retry_task_returns_not_found_for_missing_task() { + let (_dir, state) = test_store(); + + let err = retry_task(State(state), axum::extract::Path("nonexistent".to_string())) + .await + .unwrap_err(); + + assert!(matches!(err, ApiError::NotFound(_))); + } } diff --git a/src/config.rs b/src/config.rs index c8f5663..5f15d4f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,7 +6,6 @@ use crate::adapters::AdapterInstanceConfig; pub struct Config { pub server: ServerConfig, pub forgejo: ForgejoConfig, - pub matrix: MatrixConfig, pub orchestrator: OrchestratorConfig, #[serde(default)] pub adapters: Vec, @@ -25,14 +24,6 @@ pub struct ForgejoConfig { pub webhook_secret: String, } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct MatrixConfig { - pub homeserver_url: String, - pub user_id: String, - pub access_token: String, - pub room_id: String, -} - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct OrchestratorConfig { pub db_path: String, @@ -54,12 +45,6 @@ impl Default for Config { token: String::new(), webhook_secret: String::new(), }, - matrix: MatrixConfig { - homeserver_url: "https://matrix.0x08.org".into(), - user_id: "@jeeves:0x08.org".into(), - access_token: String::new(), - room_id: String::new(), - }, orchestrator: OrchestratorConfig { db_path: "data/agent-fleet.db".into(), heartbeat_interval_secs: 60, diff --git a/src/core/event_store.rs b/src/core/event_store.rs index cd758ab..12ae497 100644 --- a/src/core/event_store.rs +++ b/src/core/event_store.rs @@ -275,6 +275,36 @@ impl EventStore { .collect::>>() } + pub fn list_tasks( + &self, + status: Option<&str>, + agent_id: Option<&str>, + ) -> SqlResult> { + let mut sql = String::from( + "SELECT task_id, source, task_type, priority, status, assigned_agent_id, + requirements, labels, created_at, assigned_at, started_at, completed_at, + retry_count, max_retries, timeout_seconds + FROM tasks WHERE 1=1", + ); + let mut param_values: Vec> = Vec::new(); + + if let Some(s) = status { + sql.push_str(" AND status = ?"); + param_values.push(Box::new(s.to_string())); + } + if let Some(a) = agent_id { + sql.push_str(" AND assigned_agent_id = ?"); + param_values.push(Box::new(a.to_string())); + } + sql.push_str(" ORDER BY created_at DESC"); + + let params: Vec<&dyn rusqlite::types::ToSql> = param_values.iter().map(|p| p.as_ref()).collect(); + + let mut stmt = self.conn.prepare(&sql)?; + stmt.query_map(params.as_slice(), Self::row_to_task)? + .collect::>>() + } + // ─── Task/event write operations ───────────────────────────── pub fn insert_task(&self, task: &Task) -> SqlResult<()> { diff --git a/src/integrations/matrix/mod.rs b/src/integrations/matrix/mod.rs deleted file mode 100644 index 1103a86..0000000 --- a/src/integrations/matrix/mod.rs +++ /dev/null @@ -1,531 +0,0 @@ -use std::collections::HashMap; -use std::sync::{Arc, Mutex}; - -use matrix_sdk::authentication::matrix::MatrixSession; -use matrix_sdk::config::SyncSettings; -use matrix_sdk::ruma::events::room::message::{ - MessageType, OriginalSyncRoomMessageEvent, RoomMessageEventContent, -}; -use matrix_sdk::ruma::events::relation::Thread; -use matrix_sdk::ruma::OwnedEventId; -use matrix_sdk::ruma::{OwnedDeviceId, OwnedRoomId, OwnedUserId, UserId}; -use matrix_sdk::{Client, Room}; -use tokio::sync::RwLock; - -use crate::config::MatrixConfig; -use crate::core::event_store::EventStore; -use crate::core::models::{Agent, TaskStatus}; -use crate::core::state_machine::StateMachine; - -/// The bot context — shared state for all handlers. -#[derive(Clone)] -pub struct BotContext { - pub store: Arc>, - pub sm: Arc, - pub config: MatrixConfig, - /// Maps agent_id → root event_id for per-agent threads. - pub agent_threads: Arc>>, -} - -// ─── Command parsing ──────────────────────────────────────────── - -/// A parsed Matrix command. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum BotCommand { - FleetStatus, - Assign { agent_id: String, issue_ref: String }, - Retry { issue_ref: String }, - Unknown(String), -} - -/// Parse a Matrix message body into a BotCommand. -/// Only returns Some for known command prefixes: fleet, assign, retry. -pub fn parse_command(body: &str) -> Option { - let body = body.trim(); - // Accept both "/fleet status" and "fleet status" (in case the / is stripped) - let body = body.strip_prefix('/').unwrap_or(body); - - let mut parts = body.splitn(4, ' '); - let first = parts.next()?.to_lowercase(); - match first.as_str() { - "fleet" => { - let sub = parts.next()?.to_lowercase(); - match sub.as_str() { - "status" => Some(BotCommand::FleetStatus), - _ => Some(BotCommand::Unknown(format!("fleet {sub}"))), - } - } - "assign" => { - let agent_id = parts.next()?.to_string(); - let issue_ref = parts.next()?.to_string(); - Some(BotCommand::Assign { agent_id, issue_ref }) - } - "retry" => { - let issue_ref = parts.next()?.to_string(); - Some(BotCommand::Retry { issue_ref }) - } - _ => None, - } -} - -// ─── Notification formatting ──────────────────────────────────── - -/// Format a task-assigned notification. -/// -/// Spec: `📋 #42 → worker-03 [code:typescript]` -pub fn format_task_assigned(task_id: &str, agent_id: &str, task_type: &str) -> String { - format!("📋 {task_id} → {agent_id} [{task_type}]") -} - -/// Format a task-completed notification. -/// -/// Spec: `✅ #42 completed by worker-03 — PR #15 — "修复登录验证 bug"` -pub fn format_task_completed(task_id: &str, agent_id: &str, summary: &str, artifact_hint: Option<&str>) -> String { - match artifact_hint { - Some(hint) => format!("✅ {task_id} completed by {agent_id} — {hint} — \"{summary}\""), - None => format!("✅ {task_id} completed by {agent_id} — \"{summary}\""), - } -} - -/// Format a task-failed notification. -/// -/// Spec: `❌ #42 failed — worker-03 — "构建超时"` -pub fn format_task_failed(task_id: &str, agent_id: &str, error: &str) -> String { - format!("❌ {task_id} failed — {agent_id} — \"{error}\"") -} - -/// Format an agent-offline alert. -/// -/// Spec: `⚠️ worker-03 offline — 2 running tasks affected` -pub fn format_agent_offline(agent_id: &str, affected_tasks: usize) -> String { - format!("⚠️ {agent_id} offline — {affected_tasks} running tasks affected") -} - -/// Format the fleet status table as plain text. -pub fn format_fleet_status(agents: &[Agent]) -> String { - if agents.is_empty() { - return "No agents registered.".to_string(); - } - - let mut lines = vec!["Agent ID | Type | Status | Tasks | Capabilities".to_string()]; - lines.push("-----------------+--------------+---------+-------+---------------------------".to_string()); - - for agent in agents { - let caps = agent.capabilities.join(", "); - let caps_display = if caps.len() > 27 { - format!("{}…", &caps[..24]) - } else { - caps - }; - lines.push(format!( - "{:<16} | {:<12} | {:<7} | {:<5} | {}", - truncate_str(&agent.agent_id, 16), - truncate_str(agent.agent_type.as_str(), 12), - agent.status.as_str(), - agent.current_tasks, - caps_display, - )); - } - - lines.join("\n") -} - -fn truncate_str(s: &str, max_len: usize) -> String { - if s.len() <= max_len { - s.to_string() - } else { - format!("{}…", &s[..max_len - 1]) - } -} - -// ─── Bot lifecycle ────────────────────────────────────────────── - -/// Build a Matrix client from config. -pub async fn build_client(config: &MatrixConfig) -> Result> { - let client = Client::builder() - .homeserver_url(&config.homeserver_url) - .build() - .await?; - - let user_id = UserId::parse(&config.user_id)?; - let device_id: OwnedDeviceId = "AGENTFLEET".into(); - - let session = MatrixSession { - meta: matrix_sdk::SessionMeta { - user_id, - device_id, - }, - tokens: matrix_sdk::authentication::matrix::MatrixSessionTokens { - access_token: config.access_token.clone(), - refresh_token: None, - }, - }; - - client.restore_session(session).await?; - Ok(client) -} - -/// Start the Matrix bot — spawns a background sync loop. -pub async fn start_bot( - config: MatrixConfig, - store: Arc>, - sm: Arc, -) -> Result<(), Box> { - let room_id = OwnedRoomId::try_from(config.room_id.as_str())?; - let client = build_client(&config).await?; - - // Join the room - let _ = client.join_room_by_id(&room_id).await?; - - let ctx = BotContext { - store, - sm, - config, - agent_threads: Arc::new(RwLock::new(HashMap::new())), - }; - - // Register the message handler - client.add_event_handler(move |ev: OriginalSyncRoomMessageEvent, room: Room| { - let ctx = ctx.clone(); - async move { - handle_message(ev, room, ctx).await; - } - }); - - // Spawn the sync loop in background - tokio::spawn(async move { - loop { - match client.sync(SyncSettings::default()).await { - Ok(_) => {} - Err(e) => { - tracing::error!("Matrix sync error: {e}"); - tokio::time::sleep(std::time::Duration::from_secs(5)).await; - } - } - } - }); - - tracing::info!("Matrix bot started, monitoring room {}", room_id); - Ok(()) -} - -/// Handle an incoming Matrix room message. -async fn handle_message( - ev: OriginalSyncRoomMessageEvent, - room: Room, - ctx: BotContext, -) { - // Only handle text messages - let text = match &ev.content.msgtype { - MessageType::Text(text) => text.body.clone(), - _ => return, - }; - - // Skip our own messages - if ev.sender == ctx.config.user_id.parse::().unwrap_or_else(|_| ev.sender.clone()) { - return; - } - - let Some(cmd) = parse_command(&text) else { - return; - }; - - tracing::debug!(?cmd, "received matrix command"); - - match cmd { - BotCommand::FleetStatus => { - let agents = { - let store = ctx.store.lock().unwrap(); - store.list_agents(None, None).unwrap_or_default() - }; - let table = format_fleet_status(&agents); - send_plain(&room, &table).await; - } - BotCommand::Assign { agent_id, issue_ref } => { - let result = { - let store = ctx.store.lock().unwrap(); - let task = store.read_task(&issue_ref); - let agent = store.find_agent_by_id(&agent_id); - (task, agent) - }; - - match result { - (Ok(Some(_task)), Ok(Some(_agent))) => { - match ctx.sm.transition(&issue_ref, TaskStatus::Assigned, Some(&agent_id), "manual assign via matrix").await { - Ok(updated) => { - let msg = format_task_assigned(&updated.task_id, &agent_id, &updated.task_type); - send_plain(&room, &msg).await; - } - Err(e) => { - send_plain(&room, &format!("❌ Failed to assign: {e}")).await; - } - } - } - (Ok(None), _) => { - send_plain(&room, &format!("Task not found: {issue_ref}")).await; - } - (_, Ok(None)) => { - send_plain(&room, &format!("Agent not found: {agent_id}")).await; - } - (Err(e), _) | (_, Err(e)) => { - send_plain(&room, &format!("Database error: {e}")).await; - } - } - } - BotCommand::Retry { issue_ref } => { - let task_status = { - let store = ctx.store.lock().unwrap(); - store.read_task(&issue_ref).ok().flatten() - }; - - let Some(task) = task_status else { - send_plain(&room, &format!("Task not found: {issue_ref}")).await; - return; - }; - - if !matches!(task.status, TaskStatus::Failed | TaskStatus::AgentLost) { - send_plain(&room, &format!("Task {issue_ref} is not in a retryable state (current: {})", task.status.as_str())).await; - return; - } - - // Reset to created (re-enqueue) - match ctx.sm.transition(&issue_ref, TaskStatus::Assigned, None, "retry via matrix").await { - Ok(_) => { - send_plain(&room, &format!("🔄 Task {issue_ref} re-queued for assignment")).await; - } - Err(e) => { - send_plain(&room, &format!("❌ Failed to retry: {e}")).await; - } - } - } - BotCommand::Unknown(cmd) => { - send_plain(&room, &format!("Unknown command: `{cmd}`. Available: /fleet status, /assign , /retry ")).await; - } - } -} - -/// Send a plain text message to a room. -async fn send_plain(room: &Room, body: &str) { - let content = RoomMessageEventContent::text_plain(body); - if let Err(e) = room.send(content).await { - tracing::error!("Failed to send Matrix message: {e}"); - } -} - -/// Send a message as a thread reply (per-agent thread). -pub async fn send_thread_message( - room: &Room, - thread_root_event_id: &ruma::OwnedEventId, - body: &str, -) { - let mut content = RoomMessageEventContent::text_plain(body); - content.relates_to = Some(matrix_sdk::ruma::events::room::message::Relation::Thread( - Thread::without_fallback(thread_root_event_id.clone()), - )); - if let Err(e) = room.send(content).await { - tracing::error!("Failed to send thread message: {e}"); - } -} - -/// Post a notification for a task event to the Matrix room. -/// This is a helper meant to be called from the API layer. -pub async fn notify_task_event( - room: &Room, - event_type: &str, - task_id: &str, - agent_id: &str, - task_type: &str, - summary: Option<&str>, - error: Option<&str>, - artifact_hint: Option<&str>, -) { - let msg = match event_type { - "task.assigned" => format_task_assigned(task_id, agent_id, task_type), - "task.completed" => format_task_completed( - task_id, - agent_id, - summary.unwrap_or(""), - artifact_hint, - ), - "task.failed" => format_task_failed( - task_id, - agent_id, - error.unwrap_or("unknown error"), - ), - _ => return, - }; - send_plain(room, &msg).await; -} - -/// Post an agent-offline alert to the Matrix room. -pub async fn notify_agent_offline(room: &Room, agent_id: &str, affected_tasks: usize) { - let msg = format_agent_offline(agent_id, affected_tasks); - send_plain(room, &msg).await; -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::core::models::{Agent, AgentType, AgentStatus}; - use chrono::Utc; - use std::collections::HashMap; - - // ─── Command parsing tests ────────────────────────────────── - - #[test] - fn parse_fleet_status() { - assert_eq!(parse_command("/fleet status"), Some(BotCommand::FleetStatus)); - assert_eq!(parse_command("fleet status"), Some(BotCommand::FleetStatus)); - assert_eq!(parse_command("/FLEET STATUS"), Some(BotCommand::FleetStatus)); - assert_eq!(parse_command(" /fleet status "), Some(BotCommand::FleetStatus)); - } - - #[test] - fn parse_assign_command() { - assert_eq!( - parse_command("/assign worker-03 org/repo#42"), - Some(BotCommand::Assign { - agent_id: "worker-03".into(), - issue_ref: "org/repo#42".into(), - }) - ); - assert_eq!( - parse_command("assign worker-03 org/repo#42"), - Some(BotCommand::Assign { - agent_id: "worker-03".into(), - issue_ref: "org/repo#42".into(), - }) - ); - } - - #[test] - fn parse_retry_command() { - assert_eq!( - parse_command("/retry org/repo#42"), - Some(BotCommand::Retry { - issue_ref: "org/repo#42".into(), - }) - ); - assert_eq!( - parse_command("retry #42"), - Some(BotCommand::Retry { - issue_ref: "#42".into(), - }) - ); - } - - #[test] - fn parse_unknown_command() { - // Non-fleet/assign/retry prefixes return None (not a bot command) - assert_eq!(parse_command("/deploy prod"), None); - } - - #[test] - fn parse_non_command_returns_none() { - assert_eq!(parse_command("hello world"), None); - assert_eq!(parse_command(""), None); - assert_eq!(parse_command("just chatting"), None); - } - - #[test] - fn parse_fleet_subcommand_unknown() { - assert_eq!( - parse_command("/fleet deploy"), - Some(BotCommand::Unknown("fleet deploy".into())) - ); - } - - // ─── Notification formatting tests ────────────────────────── - - #[test] - fn task_assigned_format() { - let msg = format_task_assigned("org/repo#42", "worker-03", "code:typescript"); - assert_eq!(msg, "📋 org/repo#42 → worker-03 [code:typescript]"); - } - - #[test] - fn task_completed_format_with_artifact() { - let msg = format_task_completed( - "org/repo#42", - "worker-03", - "修复登录验证 bug", - Some("PR #15"), - ); - assert_eq!( - msg, - "✅ org/repo#42 completed by worker-03 — PR #15 — \"修复登录验证 bug\"" - ); - } - - #[test] - fn task_completed_format_without_artifact() { - let msg = format_task_completed( - "org/repo#42", - "worker-03", - "fixed the thing", - None, - ); - assert_eq!(msg, "✅ org/repo#42 completed by worker-03 — \"fixed the thing\""); - } - - #[test] - fn task_failed_format() { - let msg = format_task_failed("org/repo#42", "worker-03", "构建超时"); - assert_eq!(msg, "❌ org/repo#42 failed — worker-03 — \"构建超时\""); - } - - #[test] - fn agent_offline_format() { - let msg = format_agent_offline("worker-03", 2); - assert_eq!(msg, "⚠️ worker-03 offline — 2 running tasks affected"); - } - - // ─── Fleet status table tests ─────────────────────────────── - - fn sample_agent(id: &str, status: AgentStatus, tasks: u32) -> Agent { - Agent { - agent_id: id.to_string(), - agent_type: AgentType::CodexCli, - hostname: "host-01".into(), - capabilities: vec!["code:rust".into(), "review".into()], - max_concurrency: 2, - current_tasks: tasks, - status, - last_heartbeat_at: Utc::now(), - registered_at: Utc::now(), - metadata: HashMap::new(), - } - } - - #[test] - fn fleet_status_empty() { - let table = format_fleet_status(&[]); - assert_eq!(table, "No agents registered."); - } - - #[test] - fn fleet_status_with_agents() { - let agents = vec![ - sample_agent("worker-01", AgentStatus::Online, 1), - sample_agent("worker-02", AgentStatus::Offline, 0), - ]; - let table = format_fleet_status(&agents); - assert!(table.contains("worker-01")); - assert!(table.contains("worker-02")); - assert!(table.contains("online")); - assert!(table.contains("offline")); - assert!(table.contains("code:rust")); - assert!(table.contains("review")); - } - - #[test] - fn fleet_status_table_has_header() { - let agents = vec![sample_agent("w1", AgentStatus::Online, 0)]; - let table = format_fleet_status(&agents); - assert!(table.contains("Agent ID")); - assert!(table.contains("Type")); - assert!(table.contains("Status")); - assert!(table.contains("Tasks")); - assert!(table.contains("Capabilities")); - } -} diff --git a/src/integrations/mod.rs b/src/integrations/mod.rs index dbb9804..607ec12 100644 --- a/src/integrations/mod.rs +++ b/src/integrations/mod.rs @@ -1,2 +1 @@ pub mod forgejo; -pub mod matrix; diff --git a/src/main.rs b/src/main.rs index 9d7bffc..32ef9d3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -87,10 +87,15 @@ async fn main() { let app = axum::Router::new() .route("/healthz", axum::routing::get(|| async { "ok" })) + // Agent registry .route("/api/v1/agents/register", axum::routing::post(api::register_agent)) .route("/api/v1/agents/heartbeat", axum::routing::post(api::heartbeat)) .route("/api/v1/agents/deregister", axum::routing::post(api::deregister)) .route("/api/v1/agents", axum::routing::get(api::list_agents)) + // Task management + .route("/api/v1/tasks", axum::routing::get(api::list_tasks)) + .route("/api/v1/tasks/{task_id}/retry", axum::routing::post(api::retry_task)) + // Receipts & webhooks .route("/api/v1/receipts", axum::routing::post(api::submit_receipt)) .route( "/api/v1/webhooks/forgejo", @@ -106,21 +111,5 @@ async fn main() { .expect("failed to bind"); tracing::info!("listening on {}", listener.local_addr().unwrap()); - - // Start Matrix bot - if !config.matrix.access_token.is_empty() && !config.matrix.room_id.is_empty() { - let matrix_cfg = config.matrix.clone(); - let matrix_store = store.clone(); - let matrix_sm = state_machine.clone(); - tokio::spawn(async move { - match crate::integrations::matrix::start_bot(matrix_cfg, matrix_store, matrix_sm).await { - Ok(_) => tracing::info!("Matrix bot stopped"), - Err(e) => tracing::error!("Matrix bot error: {e}"), - } - }); - } else { - tracing::info!("Matrix bot disabled (no access_token or room_id configured)"); - } - axum::serve(listener, app).await.expect("server error"); }