diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5aa7ba3fc..edcc5d83c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -117,7 +117,7 @@ jobs: - name: Test WebUI modules working-directory: crates/agent-gateway - run: node --test test/webui/*.test.mjs + run: node --test test/webui/*.test.mjs web/test/*.test.mjs gui: name: GUI diff --git a/.gitignore b/.gitignore index 898cbf387..ae1553e2a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ lerna-debug.log* .codex-artifacts .liveagent workspace +!crates/agent-gui/src-tauri/src/commands/workspace/ +!crates/agent-gui/src-tauri/src/commands/workspace/** cert/ node_modules dist diff --git a/Cargo.lock b/Cargo.lock index 5361af655..14db4cabd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,16 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aead" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef60ac202874e574ce7a7158cc8bca7313dd344322482e4fadee288bf4a306b8" +dependencies = [ + "crypto-common 0.2.2", + "inout 0.2.2", +] + [[package]] name = "aes" version = "0.8.4" @@ -15,8 +25,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", - "cipher", - "cpufeatures", + "cipher 0.4.4", + "cpufeatures 0.2.17", +] + +[[package]] +name = "aes" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" +dependencies = [ + "cipher 0.5.2", + "cpubits", + "cpufeatures 0.3.0", + "zeroize", +] + +[[package]] +name = "aes-gcm" +version = "0.11.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da8c919c118108f144adecad74b425b804ad075580d605d9b33c2d6d1c62a2f8" +dependencies = [ + "aead", + "aes 0.9.1", + "cipher 0.5.2", + "ctr", + "ghash", + "subtle", + "zeroize", ] [[package]] @@ -67,6 +104,18 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "argon2" +version = "0.6.0-rc.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7af50940b73bf4e16c15c448a2b121c63f2d68e3e54b6a8731673cb4aa0cdff5" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures 0.3.0", + "password-hash", +] + [[package]] name = "async-broadcast" version = "0.7.2" @@ -262,6 +311,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" dependencies = [ "aws-lc-sys", + "untrusted 0.7.1", "zeroize", ] @@ -376,6 +426,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "base16ct" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" + [[package]] name = "base64" version = "0.21.7" @@ -388,6 +444,23 @@ 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 = "bcrypt-pbkdf" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "144e573728da132683b9488acd528274c790e07fc06ff81ee29f9d8f8b1041e0" +dependencies = [ + "blowfish", + "pbkdf2", + "sha2 0.11.0", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -411,20 +484,39 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" dependencies = [ "serde_core", ] +[[package]] +name = "blake2" +version = "0.11.0-rc.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061f1a09225e328e1ffbb378d2d49923c0ca5fee19fb5ac1cc9c1e9d52b93690" +dependencies = [ + "digest 0.11.3", +] + [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array", + "generic-array 0.14.7", +] + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", + "zeroize", ] [[package]] @@ -433,7 +525,16 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" dependencies = [ - "generic-array", + "generic-array 0.14.7", +] + +[[package]] +name = "block-padding" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b" +dependencies = [ + "hybrid-array", ] [[package]] @@ -467,6 +568,16 @@ dependencies = [ "piper", ] +[[package]] +name = "blowfish" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ce3946557b35e71d1bbe07ec385073ce9eda05043f95de134eb578fcf1a298" +dependencies = [ + "byteorder", + "cipher 0.5.2", +] + [[package]] name = "brotli" version = "8.0.2" @@ -543,7 +654,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "cairo-sys-rs", "glib", "libc", @@ -610,7 +721,16 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" dependencies = [ - "cipher", + "cipher 0.4.4", +] + +[[package]] +name = "cbc" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896" +dependencies = [ + "cipher 0.5.2", ] [[package]] @@ -670,6 +790,19 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cipher 0.5.2", + "cpufeatures 0.3.0", + "rand_core 0.10.1", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.44" @@ -700,8 +833,20 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", - "inout", + "crypto-common 0.1.7", + "inout 0.1.4", +] + +[[package]] +name = "cipher" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", + "inout 0.2.2", + "zeroize", ] [[package]] @@ -713,6 +858,12 @@ dependencies = [ "cc", ] +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "combine" version = "4.6.7" @@ -732,6 +883,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "convert_case" version = "0.4.0" @@ -780,7 +937,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "core-foundation 0.10.1", "core-graphics-types", "foreign-types", @@ -793,11 +950,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "core-foundation 0.10.1", "libc", ] +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -807,6 +970,15 @@ 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" @@ -861,16 +1033,54 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crypto-bigint" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a0d26b245348befa0c121944541476763dcc46ede886c88f9d12e1697d27c3" +dependencies = [ + "cpubits", + "ctutils", + "getrandom 0.4.2", + "hybrid-array", + "num-traits", + "rand_core 0.10.1", + "serdect", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ - "generic-array", + "generic-array 0.14.7", "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "getrandom 0.4.2", + "hybrid-array", + "rand_core 0.10.1", +] + +[[package]] +name = "crypto-primes" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3633a51a39c69ebbaa4feaa694bd83d241e4093901c84a0963b19d9bb3f0cf8f" +dependencies = [ + "crypto-bigint", + "rand_core 0.10.1", +] + [[package]] name = "cssparser" version = "0.29.6" @@ -921,6 +1131,52 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "ctr" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21" +dependencies = [ + "cipher 0.5.2", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", + "subtle", +] + +[[package]] +name = "curve25519-dalek" +version = "5.0.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f359e08ca85e7bd759e1fd933ff2bccd81864c60a8fba0e259c7f822b0924bf" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "curve25519-dalek-derive", + "digest 0.11.3", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "darling" version = "0.20.11" @@ -990,12 +1246,48 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "dashmap" +version = "6.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "data-encoding" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +[[package]] +name = "delegate" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "der" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.5.8" @@ -1082,14 +1374,35 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "des" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916a94e407b54f9034d71dd748234cd1e516ced6284009906ae246f177eafe5a" +dependencies = [ + "cipher 0.5.2", +] + [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "crypto-common 0.1.7", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid", + "crypto-common 0.2.2", + "ctutils", ] [[package]] @@ -1119,7 +1432,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "block2 0.6.2", "libc", "objc2 0.6.4", @@ -1225,12 +1538,76 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "ecdsa" +version = "0.17.0-rc.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54fb064faabbee66e1fc8e5c5a9458d4269dc2d8b638fe86a425adb2510d1a96" +dependencies = [ + "der", + "digest 0.11.3", + "elliptic-curve", + "rfc6979", + "signature", + "spki", + "zeroize", +] + +[[package]] +name = "ed25519" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "3.0.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b011170fe4f04665565b4110afef66774fe9ffff278f3eb5b81cc73d26e27d60" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.10.1", + "serde", + "sha2 0.11.0", + "signature", + "subtle", + "zeroize", +] + [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "elliptic-curve" +version = "0.14.0-rc.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "102d3643d30dd8b559613c5cced68317199597fffb278cdc88daa2ef7fafc935" +dependencies = [ + "base16ct", + "crypto-bigint", + "crypto-common 0.2.2", + "digest 0.11.3", + "ff", + "group", + "hkdf", + "hybrid-array", + "once_cell", + "pem-rfc7468", + "pkcs8", + "rand_core 0.10.1", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "embed-resource" version = "3.0.8" @@ -1266,6 +1643,18 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "enumflags2" version = "0.7.12" @@ -1362,6 +1751,22 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "ff" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f686ab92a9fb0eaf188f6c6c87b89490baa6fdb0db4544ba4dc47f7942489f" +dependencies = [ + "rand_core 0.10.1", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" + [[package]] name = "field-offset" version = "0.3.6" @@ -1485,6 +1890,21 @@ dependencies = [ "new_debug_unreachable", ] +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.32" @@ -1560,6 +1980,7 @@ 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", @@ -1688,6 +2109,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "generic-array" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb130435a959a8d525e6bca66ff6c40981a300ee96d70e3ef56f046556d614a3" +dependencies = [ + "generic-array 0.14.7", + "rustversion", + "typenum", +] + [[package]] name = "getrandom" version = "0.1.16" @@ -1733,10 +2165,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 6.0.0", + "rand_core 0.10.1", "wasip2", "wasip3", + "wasm-bindgen", +] + +[[package]] +name = "ghash" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5" +dependencies = [ + "polyval", ] [[package]] @@ -1777,7 +2221,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "futures-channel", "futures-core", "futures-executor", @@ -1837,6 +2281,18 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "gloo-timers" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "482ce8a491a501da4cd806bd190275363d674f2845005c6ddbd5d3e1dd54495d" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "gobject-sys" version = "0.18.0" @@ -1848,6 +2304,17 @@ dependencies = [ "system-deps", ] +[[package]] +name = "group" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fd1a1c7a5206c5b7a3f5a0d7ccd3ff85d0c8f5133d62a02680255b0004af5f4" +dependencies = [ + "ff", + "rand_core 0.10.1", + "subtle", +] + [[package]] name = "gtk" version = "0.18.2" @@ -1925,6 +2392,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.5" @@ -1976,6 +2449,30 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-literal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" + +[[package]] +name = "hkdf" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", +] + [[package]] name = "html5ever" version = "0.29.1" @@ -2043,6 +2540,18 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hybrid-array" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" +dependencies = [ + "ctutils", + "subtle", + "typenum", + "zeroize", +] + [[package]] name = "hyper" version = "1.9.0" @@ -2337,8 +2846,30 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ - "block-padding", - "generic-array", + "block-padding 0.3.3", + "generic-array 0.14.7", +] + +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "block-padding 0.4.2", + "hybrid-array", +] + +[[package]] +name = "internal-russh-num-bigint" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8e22120c32fb4d19ec55fba35015f57095cd95a2e3b732e44457f5915b2ee8" +dependencies = [ + "num-integer", + "num-traits", + "rand 0.10.1", + "rand_core 0.10.1", ] [[package]] @@ -2470,13 +3001,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.94" +version = "0.3.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] @@ -2502,13 +3032,33 @@ dependencies = [ "serde_json", ] +[[package]] +name = "keccak" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", +] + +[[package]] +name = "kem" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01737161ba802849cfd486b5bd209d38ba4943494c249a8126005170c7621edd" +dependencies = [ + "crypto-common 0.2.2", + "rand_core 0.10.1", +] + [[package]] name = "keyboard-types" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "serde", "unicode-segmentation", ] @@ -2563,9 +3113,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.184" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libloading" @@ -2639,10 +3189,12 @@ dependencies = [ "reqwest", "rfd", "rusqlite", + "russh", + "russh-sftp", "rustls", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "tauri", "tauri-build", "tauri-plugin-mcp-bridge", @@ -2683,8 +3235,8 @@ version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c7c1d3350d071cb86987a6bcb205c7019a0eb70dcad92b454fec722cca8d68b" dependencies = [ - "aes", - "cbc", + "aes 0.8.4", + "cbc 0.1.2", "chrono", "encoding_rs", "flate2", @@ -2774,9 +3326,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if", - "digest", + "digest 0.10.7", ] +[[package]] +name = "md5" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0" + [[package]] name = "memchr" version = "2.8.0" @@ -2831,6 +3389,31 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "ml-kem" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e15f3e5b957493873e396a66914e83e616b6afe335cdef7efe5c6e1216aba66" +dependencies = [ + "hybrid-array", + "kem", + "module-lattice", + "pkcs8", + "rand_core 0.10.1", + "sha3", +] + +[[package]] +name = "module-lattice" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe" +dependencies = [ + "ctutils", + "hybrid-array", + "num-traits", +] + [[package]] name = "moxcms" version = "0.8.1" @@ -2874,7 +3457,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "jni-sys 0.3.1", "log", "ndk-sys", @@ -2910,12 +3493,24 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "cfg-if", "cfg_aliases 0.1.1", "libc", ] +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.13.0", + "cfg-if", + "cfg_aliases 0.2.1", + "libc", +] + [[package]] name = "nodrop" version = "0.1.14" @@ -2943,6 +3538,16 @@ dependencies = [ "nom", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-conv" version = "0.2.1" @@ -2960,6 +3565,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -3023,7 +3637,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "block2 0.5.1", "libc", "objc2 0.5.2", @@ -3039,7 +3653,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "block2 0.6.2", "objc2 0.6.4", "objc2-core-foundation", @@ -3052,7 +3666,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", @@ -3076,7 +3690,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -3088,7 +3702,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "dispatch2", "objc2 0.6.4", ] @@ -3099,7 +3713,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "dispatch2", "objc2 0.6.4", "objc2-core-foundation", @@ -3151,7 +3765,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "block2 0.5.1", "libc", "objc2 0.5.2", @@ -3163,7 +3777,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "block2 0.6.2", "libc", "objc2 0.6.4", @@ -3176,7 +3790,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "objc2 0.6.4", "objc2-core-foundation", ] @@ -3199,7 +3813,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -3211,7 +3825,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "objc2 0.6.4", "objc2-app-kit 0.3.2", "objc2-foundation 0.3.2", @@ -3223,7 +3837,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -3236,7 +3850,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "objc2 0.6.4", "objc2-core-foundation", "objc2-foundation 0.3.2", @@ -3258,7 +3872,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "block2 0.5.1", "objc2 0.5.2", "objc2-cloud-kit", @@ -3279,7 +3893,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "objc2 0.6.4", "objc2-core-foundation", "objc2-foundation 0.3.2", @@ -3302,7 +3916,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", @@ -3315,7 +3929,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68bc69301064cebefc6c4c90ce9cba69225239e4b8ff99d445a2b5563797da65" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "block2 0.5.1", "objc2 0.5.2", "objc2-app-kit 0.2.2", @@ -3328,7 +3942,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "block2 0.6.2", "objc2 0.6.4", "objc2-app-kit 0.3.2", @@ -3390,6 +4004,67 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "p256" +version = "0.14.0-rc.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41adc63effe99d48837a8cc0e6d7a77e32ae6a07f6000df466178dbc2193093e" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primefield", + "primeorder", + "sha2 0.11.0", +] + +[[package]] +name = "p384" +version = "0.14.0-rc.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd5333afa5ae0347f39e6a0f2c9c155da431583fd71fe5555bd0521b4ccaf02" +dependencies = [ + "ecdsa", + "elliptic-curve", + "fiat-crypto", + "primefield", + "primeorder", + "sha2 0.11.0", +] + +[[package]] +name = "p521" +version = "0.14.0-rc.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3a5297f53dc16d35909060ba3032cff7867e8809f01e273ff325579d5f0ceae" +dependencies = [ + "base16ct", + "ecdsa", + "elliptic-curve", + "primefield", + "primeorder", + "sha2 0.11.0", +] + +[[package]] +name = "pageant" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3a5ae18f65a85c67a77d18d42d3606c07948e3c17c1e5f74852b26589e88a5" +dependencies = [ + "base16ct", + "byteorder", + "bytes", + "delegate", + "futures", + "log", + "rand 0.10.1", + "sha2 0.11.0", + "thiserror 2.0.18", + "tokio", + "windows 0.62.2", + "windows-strings 0.5.1", +] + [[package]] name = "pango" version = "0.18.3" @@ -3444,12 +4119,40 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "password-hash" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aab41826031698d6ffcd9cff78ef56ef998e39dc7e5067cdfebe373842d4723b" +dependencies = [ + "phc", +] + [[package]] name = "pathdiff" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +[[package]] +name = "pbkdf2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" +dependencies = [ + "digest 0.11.3", + "hmac", +] + +[[package]] +name = "pem-rfc7468" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -3466,6 +4169,16 @@ dependencies = [ "indexmap 2.13.1", ] +[[package]] +name = "phc" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44dc769b75f93afdddd8c7fa12d685292ddeff1e66f7f0f3a234cf1818afe892" +dependencies = [ + "base64ct", + "ctutils", +] + [[package]] name = "phf" version = "0.8.0" @@ -3692,20 +4405,58 @@ dependencies = [ ] [[package]] -name = "pin-project-lite" -version = "0.2.17" +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkcs1" +version = "0.8.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "986d2e952779af96ea048f160fd9194e1751b4faea78bcf3ceb456efe008088e" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkcs5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +checksum = "279a91971a1d8eb1260a30938eae3be9cb67b472dffecb222fbbbe2fd2dc1453" +dependencies = [ + "aes 0.9.1", + "cbc 0.2.1", + "der", + "pbkdf2", + "rand_core 0.10.1", + "scrypt", + "sha2 0.11.0", + "spki", +] [[package]] -name = "piper" -version = "0.2.5" +name = "pkcs8" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", + "der", + "pkcs5", + "rand_core 0.10.1", + "spki", ] [[package]] @@ -3746,7 +4497,7 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "crc32fast", "fdeflate", "flate2", @@ -3773,6 +4524,28 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" +[[package]] +name = "poly1305" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00baa632505d05512f48a963e16051c54fda9a95cc9acea1a4e3c90991c4a2e" +dependencies = [ + "cpufeatures 0.3.0", + "universal-hash", + "zeroize", +] + +[[package]] +name = "polyval" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfc63250416fea14f5749b90725916a6c903f599d51cb635aa7a52bfd03eede" +dependencies = [ + "cpubits", + "cpufeatures 0.3.0", + "universal-hash", +] + [[package]] name = "portable-pty" version = "0.9.0" @@ -3786,7 +4559,7 @@ dependencies = [ "lazy_static", "libc", "log", - "nix", + "nix 0.28.0", "serial2", "shared_library", "shell-words", @@ -3834,6 +4607,29 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "primefield" +version = "0.14.0-rc.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d7e42f46a29abc16fb621a3466ee453358ebaae48a9e515f287e0af052ed8f" +dependencies = [ + "crypto-bigint", + "crypto-common 0.2.2", + "ff", + "rand_core 0.10.1", + "subtle", + "zeroize", +] + +[[package]] +name = "primeorder" +version = "0.14.0-rc.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d2793f22b9b6fd11ef3ac1d59bf003c2573593e4968702341605c2748fd90bf" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -4090,6 +4886,17 @@ dependencies = [ "rand_core 0.9.5", ] +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -4147,6 +4954,12 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "rand_hc" version = "0.2.0" @@ -4203,7 +5016,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", ] [[package]] @@ -4310,6 +5123,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "rfc6979" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5236ce872cac07e0fb3969b0cbf468c7d2f37d432f1b627dcb7b8d34563fb0c3" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "rfd" version = "0.17.2" @@ -4347,10 +5170,29 @@ dependencies = [ "cfg-if", "getrandom 0.2.17", "libc", - "untrusted", + "untrusted 0.9.0", "windows-sys 0.52.0", ] +[[package]] +name = "rsa" +version = "0.10.0-rc.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b2aa4ba0d89f73d1e332df05be0eeab8840351c36ca5654341dfdb57bb3caf" +dependencies = [ + "const-oid", + "crypto-bigint", + "crypto-primes", + "digest 0.11.3", + "pkcs1", + "pkcs8", + "rand_core 0.10.1", + "sha2 0.11.0", + "signature", + "spki", + "zeroize", +] + [[package]] name = "rsqlite-vfs" version = "0.1.0" @@ -4367,7 +5209,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0d2b0146dd9661bf67bb107c0bb2a55064d556eeb3fc314151b957f313bcd4e" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -4376,6 +5218,122 @@ dependencies = [ "sqlite-wasm-rs", ] +[[package]] +name = "russh" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbf893f64684e58da8a68d56a5e84d1cf0440226274c515770fe267707a7d0b0" +dependencies = [ + "aes 0.9.1", + "aws-lc-rs", + "bitflags 2.13.0", + "block-padding 0.4.2", + "byteorder", + "bytes", + "cbc 0.2.1", + "cipher 0.5.2", + "crypto-bigint", + "ctr", + "curve25519-dalek", + "data-encoding", + "delegate", + "der", + "digest 0.11.3", + "ecdsa", + "ed25519-dalek", + "elliptic-curve", + "enum_dispatch", + "flate2", + "futures", + "generic-array 1.4.2", + "getrandom 0.4.2", + "ghash", + "hex-literal", + "hmac", + "inout 0.2.2", + "internal-russh-num-bigint", + "keccak", + "log", + "md5", + "ml-kem", + "module-lattice", + "num-bigint", + "p256", + "p384", + "p521", + "pageant", + "pbkdf2", + "pkcs1", + "pkcs5", + "pkcs8", + "polyval", + "rand 0.10.1", + "rand_core 0.10.1", + "rsa", + "russh-cryptovec", + "russh-util", + "salsa20", + "scrypt", + "sec1", + "sha1 0.11.0", + "sha2 0.11.0", + "sha3", + "signature", + "spki", + "ssh-encoding", + "ssh-key", + "subtle", + "thiserror 2.0.18", + "tokio", + "typenum", + "universal-hash", + "zeroize", +] + +[[package]] +name = "russh-cryptovec" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443f6bbcfacb34a1aab2b12b99bf08e0c63abdc5a0db261901365df9d57fff51" +dependencies = [ + "log", + "nix 0.31.3", + "ssh-encoding", + "windows-sys 0.61.2", +] + +[[package]] +name = "russh-sftp" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed8949eca4163c18a8f59ff96d32cf61e9c13b9735e21ef32b3907f4aafa1a9" +dependencies = [ + "bitflags 2.13.0", + "bytes", + "chrono", + "dashmap", + "gloo-timers", + "log", + "serde", + "serde_bytes", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "wasm-bindgen-futures", +] + +[[package]] +name = "russh-util" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668424a5dde0bcb45b55ba7de8476b93831b4aa2fa6947e145f3b053e22c60b6" +dependencies = [ + "chrono", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", +] + [[package]] name = "rustc-hash" version = "2.1.2" @@ -4397,7 +5355,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys", @@ -4487,7 +5445,7 @@ dependencies = [ "aws-lc-rs", "ring", "rustls-pki-types", - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -4502,6 +5460,16 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "salsa20" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f874456e72520ff1375a06c588eaf074b0f01f9e9e1aada45bd9b7954a6e42c" +dependencies = [ + "cfg-if", + "cipher 0.5.2", +] + [[package]] name = "same-file" version = "1.0.6" @@ -4583,13 +5551,39 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scrypt" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87af57419b594aa23fa95f09f0e06d80d84ba01c26148c43844cad6ff4485f0" +dependencies = [ + "cfg-if", + "pbkdf2", + "salsa20", + "sha2 0.11.0", +] + +[[package]] +name = "sec1" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d" +dependencies = [ + "base16ct", + "ctutils", + "der", + "hybrid-array", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -4630,7 +5624,7 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "cssparser 0.36.0", "derive_more 2.1.1", "log", @@ -4675,6 +5669,16 @@ dependencies = [ "typeid", ] +[[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" @@ -4802,6 +5806,16 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "serdect" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" +dependencies = [ + "base16ct", + "serde", +] + [[package]] name = "serial2" version = "0.2.37" @@ -4861,8 +5875,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -4872,8 +5897,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", +] + +[[package]] +name = "sha3" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" +dependencies = [ + "digest 0.11.3", + "keccak", ] [[package]] @@ -4908,6 +5954,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" +dependencies = [ + "digest 0.11.3", + "rand_core 0.10.1", +] + [[package]] name = "simd-adler32" version = "0.3.9" @@ -5006,6 +6062,16 @@ dependencies = [ "system-deps", ] +[[package]] +name = "spki" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "sqlite-wasm-rs" version = "0.5.2" @@ -5018,6 +6084,67 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "ssh-cipher" +version = "0.3.0-rc.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10db6f219196a8528f9ec904d9d45cdad692d65b0e57e72be4dedd1c5fddce36" +dependencies = [ + "aead", + "aes 0.9.1", + "aes-gcm", + "cbc 0.2.1", + "chacha20", + "cipher 0.5.2", + "ctr", + "ctutils", + "des", + "poly1305", + "ssh-encoding", + "zeroize", +] + +[[package]] +name = "ssh-encoding" +version = "0.3.0-rc.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abf34aa716da5d5b4c496936d042ea282ab392092cd68a72ef6a8863ff8c96a" +dependencies = [ + "base64ct", + "bytes", + "crypto-bigint", + "ctutils", + "digest 0.11.3", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "ssh-key" +version = "0.7.0-rc.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45735ce3dea95690e4a9e414c4cfde7f79835063c3dcd35881df85a84118e74b" +dependencies = [ + "argon2", + "bcrypt-pbkdf", + "ctutils", + "ed25519-dalek", + "hex", + "hmac", + "p256", + "p384", + "p521", + "rand_core 0.10.1", + "rsa", + "sec1", + "sha1 0.11.0", + "sha2 0.11.0", + "signature", + "ssh-cipher", + "ssh-encoding", + "zeroize", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -5165,7 +6292,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -5199,7 +6326,7 @@ version = "0.34.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9103edf55f2da3c82aea4c7fab7c4241032bfeea0e71fa557d98e00e7ce7cc20" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "block2 0.6.2", "core-foundation 0.10.1", "core-graphics", @@ -5225,7 +6352,7 @@ dependencies = [ "tao-macros", "unicode-segmentation", "url", - "windows", + "windows 0.61.3", "windows-core 0.61.2", "windows-version", "x11-dl", @@ -5308,7 +6435,7 @@ dependencies = [ "webkit2gtk", "webview2-com", "window-vibrancy", - "windows", + "windows 0.61.3", ] [[package]] @@ -5350,7 +6477,7 @@ dependencies = [ "semver", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "syn 2.0.117", "tauri-utils", "thiserror 2.0.18", @@ -5416,7 +6543,7 @@ dependencies = [ "tokio-tungstenite", "uuid", "webview2-com", - "windows", + "windows 0.61.3", "windows-core 0.61.2", ] @@ -5438,7 +6565,7 @@ dependencies = [ "tauri-plugin", "thiserror 2.0.18", "url", - "windows", + "windows 0.61.3", "zbus", ] @@ -5497,7 +6624,7 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows", + "windows 0.61.3", ] [[package]] @@ -5522,7 +6649,7 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows", + "windows 0.61.3", "wry", ] @@ -5791,6 +6918,7 @@ dependencies = [ "bytes", "futures-core", "futures-sink", + "futures-util", "pin-project-lite", "tokio", ] @@ -5989,7 +7117,7 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "bytes", "futures-util", "http", @@ -6085,7 +7213,7 @@ dependencies = [ "httparse", "log", "rand 0.9.2", - "sha1", + "sha1 0.10.6", "thiserror 2.0.18", "utf-8", ] @@ -6172,6 +7300,22 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "universal-hash" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96" +dependencies = [ + "crypto-common 0.2.2", + "ctutils", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "untrusted" version = "0.9.0" @@ -6325,9 +7469,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.117" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" dependencies = [ "cfg-if", "once_cell", @@ -6338,9 +7482,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.67" +version = "0.4.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" +checksum = "503b14d284f2c8dac03b819967e155ea753f573586193b2b2c95990cb5d69280" dependencies = [ "js-sys", "wasm-bindgen", @@ -6348,9 +7492,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.117" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6358,9 +7502,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.117" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" dependencies = [ "bumpalo", "proc-macro2", @@ -6371,9 +7515,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.117" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" dependencies = [ "unicode-ident", ] @@ -6419,7 +7563,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "hashbrown 0.15.5", "indexmap 2.13.1", "semver", @@ -6445,7 +7589,7 @@ version = "0.31.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "rustix", "wayland-backend", "wayland-scanner", @@ -6457,7 +7601,7 @@ version = "0.32.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.0", "wayland-backend", "wayland-client", "wayland-scanner", @@ -6487,9 +7631,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.94" +version = "0.3.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" +checksum = "a6430a72df5eb332242960fe84b3002a241163998241eb596d4f739b9757061d" dependencies = [ "js-sys", "wasm-bindgen", @@ -6596,7 +7740,7 @@ checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" dependencies = [ "webview2-com-macros", "webview2-com-sys", - "windows", + "windows 0.61.3", "windows-core 0.61.2", "windows-implement", "windows-interface", @@ -6620,7 +7764,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" dependencies = [ "thiserror 2.0.18", - "windows", + "windows 0.61.3", "windows-core 0.61.2", ] @@ -6682,11 +7826,23 @@ version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-collections", + "windows-collections 0.2.0", "windows-core 0.61.2", - "windows-future", + "windows-future 0.2.1", "windows-link 0.1.3", - "windows-numerics", + "windows-numerics 0.2.0", +] + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections 0.3.2", + "windows-core 0.62.2", + "windows-future 0.3.2", + "windows-numerics 0.3.1", ] [[package]] @@ -6698,6 +7854,15 @@ dependencies = [ "windows-core 0.61.2", ] +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", +] + [[package]] name = "windows-core" version = "0.61.2" @@ -6732,7 +7897,18 @@ checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ "windows-core 0.61.2", "windows-link 0.1.3", - "windows-threading", + "windows-threading 0.1.0", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", + "windows-threading 0.2.1", ] [[package]] @@ -6779,6 +7955,16 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", +] + [[package]] name = "windows-registry" version = "0.6.1" @@ -6928,6 +8114,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-version" version = "0.1.7" @@ -7179,7 +8374,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.0", + "bitflags 2.13.0", "indexmap 2.13.1", "log", "serde", @@ -7245,7 +8440,7 @@ dependencies = [ "once_cell", "percent-encoding", "raw-window-handle", - "sha2", + "sha2 0.10.9", "soup3", "tao-macros", "thiserror 2.0.18", @@ -7253,7 +8448,7 @@ dependencies = [ "webkit2gtk", "webkit2gtk-sys", "webview2-com", - "windows", + "windows 0.61.3", "windows-core 0.61.2", "windows-version", "x11-dl", diff --git a/crates/agent-gateway/internal/proto/v1/gateway.pb.go b/crates/agent-gateway/internal/proto/v1/gateway.pb.go index 7c159763e..4d01dd494 100644 --- a/crates/agent-gateway/internal/proto/v1/gateway.pb.go +++ b/crates/agent-gateway/internal/proto/v1/gateway.pb.go @@ -158,7 +158,7 @@ func (x ChatEvent_ChatEventType) Number() protoreflect.EnumNumber { // Deprecated: Use ChatEvent_ChatEventType.Descriptor instead. func (ChatEvent_ChatEventType) EnumDescriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{28, 0} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{35, 0} } type AuthRequest struct { @@ -324,9 +324,11 @@ type GatewayEnvelope struct { // *GatewayEnvelope_GitRequest // *GatewayEnvelope_FsReadEditableText // *GatewayEnvelope_FsReadWorkspaceImage + // *GatewayEnvelope_SftpRequest // *GatewayEnvelope_TunnelControl // *GatewayEnvelope_TunnelControlResp // *GatewayEnvelope_TunnelFrame + // *GatewayEnvelope_SettingsResetSshKnownHost Payload isGatewayEnvelope_Payload `protobuf_oneof:"payload"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -716,6 +718,15 @@ func (x *GatewayEnvelope) GetFsReadWorkspaceImage() *FsReadWorkspaceImageRequest return nil } +func (x *GatewayEnvelope) GetSftpRequest() *SftpRequest { + if x != nil { + if x, ok := x.Payload.(*GatewayEnvelope_SftpRequest); ok { + return x.SftpRequest + } + } + return nil +} + func (x *GatewayEnvelope) GetTunnelControl() *TunnelControlRequest { if x != nil { if x, ok := x.Payload.(*GatewayEnvelope_TunnelControl); ok { @@ -743,6 +754,15 @@ func (x *GatewayEnvelope) GetTunnelFrame() *TunnelFrame { return nil } +func (x *GatewayEnvelope) GetSettingsResetSshKnownHost() *SettingsResetSshKnownHostRequest { + if x != nil { + if x, ok := x.Payload.(*GatewayEnvelope_SettingsResetSshKnownHost); ok { + return x.SettingsResetSshKnownHost + } + } + return nil +} + type isGatewayEnvelope_Payload interface { isGatewayEnvelope_Payload() } @@ -895,6 +915,10 @@ type GatewayEnvelope_FsReadWorkspaceImage struct { FsReadWorkspaceImage *FsReadWorkspaceImageRequest `protobuf:"bytes,63,opt,name=fs_read_workspace_image,json=fsReadWorkspaceImage,proto3,oneof"` } +type GatewayEnvelope_SftpRequest struct { + SftpRequest *SftpRequest `protobuf:"bytes,64,opt,name=sftp_request,json=sftpRequest,proto3,oneof"` +} + type GatewayEnvelope_TunnelControl struct { TunnelControl *TunnelControlRequest `protobuf:"bytes,67,opt,name=tunnel_control,json=tunnelControl,proto3,oneof"` } @@ -907,6 +931,10 @@ type GatewayEnvelope_TunnelFrame struct { TunnelFrame *TunnelFrame `protobuf:"bytes,69,opt,name=tunnel_frame,json=tunnelFrame,proto3,oneof"` } +type GatewayEnvelope_SettingsResetSshKnownHost struct { + SettingsResetSshKnownHost *SettingsResetSshKnownHostRequest `protobuf:"bytes,72,opt,name=settings_reset_ssh_known_host,json=settingsResetSshKnownHost,proto3,oneof"` +} + func (*GatewayEnvelope_ChatRequest) isGatewayEnvelope_Payload() {} func (*GatewayEnvelope_CancelChat) isGatewayEnvelope_Payload() {} @@ -981,12 +1009,16 @@ func (*GatewayEnvelope_FsReadEditableText) isGatewayEnvelope_Payload() {} func (*GatewayEnvelope_FsReadWorkspaceImage) isGatewayEnvelope_Payload() {} +func (*GatewayEnvelope_SftpRequest) isGatewayEnvelope_Payload() {} + func (*GatewayEnvelope_TunnelControl) isGatewayEnvelope_Payload() {} func (*GatewayEnvelope_TunnelControlResp) isGatewayEnvelope_Payload() {} func (*GatewayEnvelope_TunnelFrame) isGatewayEnvelope_Payload() {} +func (*GatewayEnvelope_SettingsResetSshKnownHost) isGatewayEnvelope_Payload() {} + type AgentEnvelope struct { state protoimpl.MessageState `protogen:"open.v1"` RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` @@ -1032,11 +1064,14 @@ type AgentEnvelope struct { // *AgentEnvelope_GitResponse // *AgentEnvelope_FsReadEditableTextResp // *AgentEnvelope_FsReadWorkspaceImageResp + // *AgentEnvelope_SftpResponse + // *AgentEnvelope_SftpEvent // *AgentEnvelope_TunnelControl // *AgentEnvelope_TunnelControlResp // *AgentEnvelope_TunnelFrame // *AgentEnvelope_ChatControl // *AgentEnvelope_RuntimeStatus + // *AgentEnvelope_SettingsResetSshKnownHostResp // *AgentEnvelope_Error Payload isAgentEnvelope_Payload `protobuf_oneof:"payload"` unknownFields protoimpl.UnknownFields @@ -1445,6 +1480,24 @@ func (x *AgentEnvelope) GetFsReadWorkspaceImageResp() *FsReadWorkspaceImageRespo return nil } +func (x *AgentEnvelope) GetSftpResponse() *SftpResponse { + if x != nil { + if x, ok := x.Payload.(*AgentEnvelope_SftpResponse); ok { + return x.SftpResponse + } + } + return nil +} + +func (x *AgentEnvelope) GetSftpEvent() *SftpEvent { + if x != nil { + if x, ok := x.Payload.(*AgentEnvelope_SftpEvent); ok { + return x.SftpEvent + } + } + return nil +} + func (x *AgentEnvelope) GetTunnelControl() *TunnelControlRequest { if x != nil { if x, ok := x.Payload.(*AgentEnvelope_TunnelControl); ok { @@ -1490,6 +1543,15 @@ func (x *AgentEnvelope) GetRuntimeStatus() *RuntimeStatusEvent { return nil } +func (x *AgentEnvelope) GetSettingsResetSshKnownHostResp() *SettingsResetSshKnownHostResponse { + if x != nil { + if x, ok := x.Payload.(*AgentEnvelope_SettingsResetSshKnownHostResp); ok { + return x.SettingsResetSshKnownHostResp + } + } + return nil +} + func (x *AgentEnvelope) GetError() *ErrorResponse { if x != nil { if x, ok := x.Payload.(*AgentEnvelope_Error); ok { @@ -1659,6 +1721,14 @@ type AgentEnvelope_FsReadWorkspaceImageResp struct { FsReadWorkspaceImageResp *FsReadWorkspaceImageResponse `protobuf:"bytes,66,opt,name=fs_read_workspace_image_resp,json=fsReadWorkspaceImageResp,proto3,oneof"` } +type AgentEnvelope_SftpResponse struct { + SftpResponse *SftpResponse `protobuf:"bytes,73,opt,name=sftp_response,json=sftpResponse,proto3,oneof"` +} + +type AgentEnvelope_SftpEvent struct { + SftpEvent *SftpEvent `protobuf:"bytes,74,opt,name=sftp_event,json=sftpEvent,proto3,oneof"` +} + type AgentEnvelope_TunnelControl struct { TunnelControl *TunnelControlRequest `protobuf:"bytes,67,opt,name=tunnel_control,json=tunnelControl,proto3,oneof"` } @@ -1679,6 +1749,10 @@ type AgentEnvelope_RuntimeStatus struct { RuntimeStatus *RuntimeStatusEvent `protobuf:"bytes,71,opt,name=runtime_status,json=runtimeStatus,proto3,oneof"` } +type AgentEnvelope_SettingsResetSshKnownHostResp struct { + SettingsResetSshKnownHostResp *SettingsResetSshKnownHostResponse `protobuf:"bytes,72,opt,name=settings_reset_ssh_known_host_resp,json=settingsResetSshKnownHostResp,proto3,oneof"` +} + type AgentEnvelope_Error struct { Error *ErrorResponse `protobuf:"bytes,99,opt,name=error,proto3,oneof"` } @@ -1761,6 +1835,10 @@ func (*AgentEnvelope_FsReadEditableTextResp) isAgentEnvelope_Payload() {} func (*AgentEnvelope_FsReadWorkspaceImageResp) isAgentEnvelope_Payload() {} +func (*AgentEnvelope_SftpResponse) isAgentEnvelope_Payload() {} + +func (*AgentEnvelope_SftpEvent) isAgentEnvelope_Payload() {} + func (*AgentEnvelope_TunnelControl) isAgentEnvelope_Payload() {} func (*AgentEnvelope_TunnelControlResp) isAgentEnvelope_Payload() {} @@ -1771,6 +1849,8 @@ func (*AgentEnvelope_ChatControl) isAgentEnvelope_Payload() {} func (*AgentEnvelope_RuntimeStatus) isAgentEnvelope_Payload() {} +func (*AgentEnvelope_SettingsResetSshKnownHostResp) isAgentEnvelope_Payload() {} + func (*AgentEnvelope_Error) isAgentEnvelope_Payload() {} type ChatSelectedModel struct { @@ -2837,6 +2917,11 @@ type TerminalRequest struct { Cols uint32 `protobuf:"varint,8,opt,name=cols,proto3" json:"cols,omitempty"` Rows uint32 `protobuf:"varint,9,opt,name=rows,proto3" json:"rows,omitempty"` MaxBytes uint32 `protobuf:"varint,10,opt,name=max_bytes,json=maxBytes,proto3" json:"max_bytes,omitempty"` + SshHostId string `protobuf:"bytes,11,opt,name=ssh_host_id,json=sshHostId,proto3" json:"ssh_host_id,omitempty"` + PromptId string `protobuf:"bytes,12,opt,name=prompt_id,json=promptId,proto3" json:"prompt_id,omitempty"` + PromptAnswer string `protobuf:"bytes,13,opt,name=prompt_answer,json=promptAnswer,proto3" json:"prompt_answer,omitempty"` + TrustHostKey bool `protobuf:"varint,14,opt,name=trust_host_key,json=trustHostKey,proto3" json:"trust_host_key,omitempty"` + SftpEnabled bool `protobuf:"varint,15,opt,name=sftp_enabled,json=sftpEnabled,proto3" json:"sftp_enabled,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -2941,6 +3026,41 @@ func (x *TerminalRequest) GetMaxBytes() uint32 { return 0 } +func (x *TerminalRequest) GetSshHostId() string { + if x != nil { + return x.SshHostId + } + return "" +} + +func (x *TerminalRequest) GetPromptId() string { + if x != nil { + return x.PromptId + } + return "" +} + +func (x *TerminalRequest) GetPromptAnswer() string { + if x != nil { + return x.PromptAnswer + } + return "" +} + +func (x *TerminalRequest) GetTrustHostKey() bool { + if x != nil { + return x.TrustHostKey + } + return false +} + +func (x *TerminalRequest) GetSftpEnabled() bool { + if x != nil { + return x.SftpEnabled + } + return false +} + type TerminalSession struct { state protoimpl.MessageState `protogen:"open.v1"` Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` @@ -2956,6 +3076,8 @@ type TerminalSession struct { FinishedAt uint64 `protobuf:"varint,11,opt,name=finished_at,json=finishedAt,proto3" json:"finished_at,omitempty"` ExitCode int32 `protobuf:"varint,12,opt,name=exit_code,json=exitCode,proto3" json:"exit_code,omitempty"` Running bool `protobuf:"varint,13,opt,name=running,proto3" json:"running,omitempty"` + Kind string `protobuf:"bytes,14,opt,name=kind,proto3" json:"kind,omitempty"` + Ssh *TerminalSshMetadata `protobuf:"bytes,15,opt,name=ssh,proto3" json:"ssh,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -3081,29 +3203,50 @@ func (x *TerminalSession) GetRunning() bool { return false } -type TerminalShellOption struct { - state protoimpl.MessageState `protogen:"open.v1"` - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - Label string `protobuf:"bytes,2,opt,name=label,proto3" json:"label,omitempty"` - Command string `protobuf:"bytes,3,opt,name=command,proto3" json:"command,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +func (x *TerminalSession) GetKind() string { + if x != nil { + return x.Kind + } + return "" } -func (x *TerminalShellOption) Reset() { - *x = TerminalShellOption{} +func (x *TerminalSession) GetSsh() *TerminalSshMetadata { + if x != nil { + return x.Ssh + } + return nil +} + +type TerminalSshMetadata struct { + state protoimpl.MessageState `protogen:"open.v1"` + HostId string `protobuf:"bytes,1,opt,name=host_id,json=hostId,proto3" json:"host_id,omitempty"` + HostName string `protobuf:"bytes,2,opt,name=host_name,json=hostName,proto3" json:"host_name,omitempty"` + Username string `protobuf:"bytes,3,opt,name=username,proto3" json:"username,omitempty"` + Host string `protobuf:"bytes,4,opt,name=host,proto3" json:"host,omitempty"` + Port uint32 `protobuf:"varint,5,opt,name=port,proto3" json:"port,omitempty"` + AuthType string `protobuf:"bytes,6,opt,name=auth_type,json=authType,proto3" json:"auth_type,omitempty"` + Status string `protobuf:"bytes,7,opt,name=status,proto3" json:"status,omitempty"` + ReconnectAttempt uint32 `protobuf:"varint,8,opt,name=reconnect_attempt,json=reconnectAttempt,proto3" json:"reconnect_attempt,omitempty"` + ReconnectMaxAttempts uint32 `protobuf:"varint,9,opt,name=reconnect_max_attempts,json=reconnectMaxAttempts,proto3" json:"reconnect_max_attempts,omitempty"` + SftpEnabled bool `protobuf:"varint,10,opt,name=sftp_enabled,json=sftpEnabled,proto3" json:"sftp_enabled,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TerminalSshMetadata) Reset() { + *x = TerminalSshMetadata{} mi := &file_proto_v1_gateway_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *TerminalShellOption) String() string { +func (x *TerminalSshMetadata) String() string { return protoimpl.X.MessageStringOf(x) } -func (*TerminalShellOption) ProtoMessage() {} +func (*TerminalSshMetadata) ProtoMessage() {} -func (x *TerminalShellOption) ProtoReflect() protoreflect.Message { +func (x *TerminalSshMetadata) ProtoReflect() protoreflect.Message { mi := &file_proto_v1_gateway_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -3115,61 +3258,113 @@ func (x *TerminalShellOption) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use TerminalShellOption.ProtoReflect.Descriptor instead. -func (*TerminalShellOption) Descriptor() ([]byte, []int) { +// Deprecated: Use TerminalSshMetadata.ProtoReflect.Descriptor instead. +func (*TerminalSshMetadata) Descriptor() ([]byte, []int) { return file_proto_v1_gateway_proto_rawDescGZIP(), []int{21} } -func (x *TerminalShellOption) GetId() string { +func (x *TerminalSshMetadata) GetHostId() string { if x != nil { - return x.Id + return x.HostId } return "" } -func (x *TerminalShellOption) GetLabel() string { +func (x *TerminalSshMetadata) GetHostName() string { if x != nil { - return x.Label + return x.HostName } return "" } -func (x *TerminalShellOption) GetCommand() string { +func (x *TerminalSshMetadata) GetUsername() string { if x != nil { - return x.Command + return x.Username } return "" } -type TerminalResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Action string `protobuf:"bytes,1,opt,name=action,proto3" json:"action,omitempty"` - Sessions []*TerminalSession `protobuf:"bytes,2,rep,name=sessions,proto3" json:"sessions,omitempty"` - Session *TerminalSession `protobuf:"bytes,3,opt,name=session,proto3" json:"session,omitempty"` - Output string `protobuf:"bytes,4,opt,name=output,proto3" json:"output,omitempty"` - Truncated bool `protobuf:"varint,5,opt,name=truncated,proto3" json:"truncated,omitempty"` - ShellOptions []*TerminalShellOption `protobuf:"bytes,6,rep,name=shell_options,json=shellOptions,proto3" json:"shell_options,omitempty"` - DefaultShell string `protobuf:"bytes,7,opt,name=default_shell,json=defaultShell,proto3" json:"default_shell,omitempty"` - OutputStartOffset uint64 `protobuf:"varint,8,opt,name=output_start_offset,json=outputStartOffset,proto3" json:"output_start_offset,omitempty"` - OutputEndOffset uint64 `protobuf:"varint,9,opt,name=output_end_offset,json=outputEndOffset,proto3" json:"output_end_offset,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +func (x *TerminalSshMetadata) GetHost() string { + if x != nil { + return x.Host + } + return "" } -func (x *TerminalResponse) Reset() { - *x = TerminalResponse{} +func (x *TerminalSshMetadata) GetPort() uint32 { + if x != nil { + return x.Port + } + return 0 +} + +func (x *TerminalSshMetadata) GetAuthType() string { + if x != nil { + return x.AuthType + } + return "" +} + +func (x *TerminalSshMetadata) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *TerminalSshMetadata) GetReconnectAttempt() uint32 { + if x != nil { + return x.ReconnectAttempt + } + return 0 +} + +func (x *TerminalSshMetadata) GetReconnectMaxAttempts() uint32 { + if x != nil { + return x.ReconnectMaxAttempts + } + return 0 +} + +func (x *TerminalSshMetadata) GetSftpEnabled() bool { + if x != nil { + return x.SftpEnabled + } + return false +} + +type SftpRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Action string `protobuf:"bytes,1,opt,name=action,proto3" json:"action,omitempty"` + SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + ProjectPathKey string `protobuf:"bytes,3,opt,name=project_path_key,json=projectPathKey,proto3" json:"project_path_key,omitempty"` + Workdir string `protobuf:"bytes,4,opt,name=workdir,proto3" json:"workdir,omitempty"` + LocalPath string `protobuf:"bytes,5,opt,name=local_path,json=localPath,proto3" json:"local_path,omitempty"` + RemotePath string `protobuf:"bytes,6,opt,name=remote_path,json=remotePath,proto3" json:"remote_path,omitempty"` + FromPath string `protobuf:"bytes,7,opt,name=from_path,json=fromPath,proto3" json:"from_path,omitempty"` + ToPath string `protobuf:"bytes,8,opt,name=to_path,json=toPath,proto3" json:"to_path,omitempty"` + Direction string `protobuf:"bytes,9,opt,name=direction,proto3" json:"direction,omitempty"` + TargetPath string `protobuf:"bytes,10,opt,name=target_path,json=targetPath,proto3" json:"target_path,omitempty"` + Recursive bool `protobuf:"varint,11,opt,name=recursive,proto3" json:"recursive,omitempty"` + Overwrite bool `protobuf:"varint,12,opt,name=overwrite,proto3" json:"overwrite,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SftpRequest) Reset() { + *x = SftpRequest{} mi := &file_proto_v1_gateway_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *TerminalResponse) String() string { +func (x *SftpRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*TerminalResponse) ProtoMessage() {} +func (*SftpRequest) ProtoMessage() {} -func (x *TerminalResponse) ProtoReflect() protoreflect.Message { +func (x *SftpRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_v1_gateway_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -3181,101 +3376,120 @@ func (x *TerminalResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use TerminalResponse.ProtoReflect.Descriptor instead. -func (*TerminalResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use SftpRequest.ProtoReflect.Descriptor instead. +func (*SftpRequest) Descriptor() ([]byte, []int) { return file_proto_v1_gateway_proto_rawDescGZIP(), []int{22} } -func (x *TerminalResponse) GetAction() string { +func (x *SftpRequest) GetAction() string { if x != nil { return x.Action } return "" } -func (x *TerminalResponse) GetSessions() []*TerminalSession { +func (x *SftpRequest) GetSessionId() string { if x != nil { - return x.Sessions + return x.SessionId } - return nil + return "" } -func (x *TerminalResponse) GetSession() *TerminalSession { +func (x *SftpRequest) GetProjectPathKey() string { if x != nil { - return x.Session + return x.ProjectPathKey } - return nil + return "" } -func (x *TerminalResponse) GetOutput() string { +func (x *SftpRequest) GetWorkdir() string { if x != nil { - return x.Output + return x.Workdir } return "" } -func (x *TerminalResponse) GetTruncated() bool { +func (x *SftpRequest) GetLocalPath() string { if x != nil { - return x.Truncated + return x.LocalPath } - return false + return "" } -func (x *TerminalResponse) GetShellOptions() []*TerminalShellOption { +func (x *SftpRequest) GetRemotePath() string { if x != nil { - return x.ShellOptions + return x.RemotePath } - return nil + return "" } -func (x *TerminalResponse) GetDefaultShell() string { +func (x *SftpRequest) GetFromPath() string { if x != nil { - return x.DefaultShell + return x.FromPath } return "" } -func (x *TerminalResponse) GetOutputStartOffset() uint64 { +func (x *SftpRequest) GetToPath() string { if x != nil { - return x.OutputStartOffset + return x.ToPath } - return 0 + return "" } -func (x *TerminalResponse) GetOutputEndOffset() uint64 { +func (x *SftpRequest) GetDirection() string { if x != nil { - return x.OutputEndOffset + return x.Direction } - return 0 + return "" } -type TerminalEvent struct { - state protoimpl.MessageState `protogen:"open.v1"` - Kind string `protobuf:"bytes,1,opt,name=kind,proto3" json:"kind,omitempty"` - SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` - ProjectPathKey string `protobuf:"bytes,3,opt,name=project_path_key,json=projectPathKey,proto3" json:"project_path_key,omitempty"` - Session *TerminalSession `protobuf:"bytes,4,opt,name=session,proto3" json:"session,omitempty"` - Data string `protobuf:"bytes,5,opt,name=data,proto3" json:"data,omitempty"` - OutputStartOffset uint64 `protobuf:"varint,6,opt,name=output_start_offset,json=outputStartOffset,proto3" json:"output_start_offset,omitempty"` - OutputEndOffset uint64 `protobuf:"varint,7,opt,name=output_end_offset,json=outputEndOffset,proto3" json:"output_end_offset,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +func (x *SftpRequest) GetTargetPath() string { + if x != nil { + return x.TargetPath + } + return "" } -func (x *TerminalEvent) Reset() { - *x = TerminalEvent{} +func (x *SftpRequest) GetRecursive() bool { + if x != nil { + return x.Recursive + } + return false +} + +func (x *SftpRequest) GetOverwrite() bool { + if x != nil { + return x.Overwrite + } + return false +} + +type SftpEntry struct { + state protoimpl.MessageState `protogen:"open.v1"` + Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Kind string `protobuf:"bytes,3,opt,name=kind,proto3" json:"kind,omitempty"` + SizeBytes uint64 `protobuf:"varint,4,opt,name=size_bytes,json=sizeBytes,proto3" json:"size_bytes,omitempty"` + Mtime uint64 `protobuf:"varint,5,opt,name=mtime,proto3" json:"mtime,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SftpEntry) Reset() { + *x = SftpEntry{} mi := &file_proto_v1_gateway_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *TerminalEvent) String() string { +func (x *SftpEntry) String() string { return protoimpl.X.MessageStringOf(x) } -func (*TerminalEvent) ProtoMessage() {} +func (*SftpEntry) ProtoMessage() {} -func (x *TerminalEvent) ProtoReflect() protoreflect.Message { +func (x *SftpEntry) ProtoReflect() protoreflect.Message { mi := &file_proto_v1_gateway_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -3287,42 +3501,688 @@ func (x *TerminalEvent) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use TerminalEvent.ProtoReflect.Descriptor instead. -func (*TerminalEvent) Descriptor() ([]byte, []int) { +// Deprecated: Use SftpEntry.ProtoReflect.Descriptor instead. +func (*SftpEntry) Descriptor() ([]byte, []int) { return file_proto_v1_gateway_proto_rawDescGZIP(), []int{23} } -func (x *TerminalEvent) GetKind() string { +func (x *SftpEntry) GetPath() string { if x != nil { - return x.Kind + return x.Path } return "" } -func (x *TerminalEvent) GetSessionId() string { +func (x *SftpEntry) GetName() string { if x != nil { - return x.SessionId + return x.Name } return "" } -func (x *TerminalEvent) GetProjectPathKey() string { +func (x *SftpEntry) GetKind() string { if x != nil { - return x.ProjectPathKey + return x.Kind } return "" } -func (x *TerminalEvent) GetSession() *TerminalSession { +func (x *SftpEntry) GetSizeBytes() uint64 { if x != nil { - return x.Session + return x.SizeBytes } - return nil + return 0 } -func (x *TerminalEvent) GetData() string { +func (x *SftpEntry) GetMtime() uint64 { if x != nil { - return x.Data + return x.Mtime + } + return 0 +} + +type SftpTransfer struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + Direction string `protobuf:"bytes,3,opt,name=direction,proto3" json:"direction,omitempty"` + Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"` + SourcePath string `protobuf:"bytes,5,opt,name=source_path,json=sourcePath,proto3" json:"source_path,omitempty"` + TargetPath string `protobuf:"bytes,6,opt,name=target_path,json=targetPath,proto3" json:"target_path,omitempty"` + CurrentPath string `protobuf:"bytes,7,opt,name=current_path,json=currentPath,proto3" json:"current_path,omitempty"` + BytesDone uint64 `protobuf:"varint,8,opt,name=bytes_done,json=bytesDone,proto3" json:"bytes_done,omitempty"` + BytesTotal uint64 `protobuf:"varint,9,opt,name=bytes_total,json=bytesTotal,proto3" json:"bytes_total,omitempty"` + FilesDone uint32 `protobuf:"varint,10,opt,name=files_done,json=filesDone,proto3" json:"files_done,omitempty"` + FilesTotal uint32 `protobuf:"varint,11,opt,name=files_total,json=filesTotal,proto3" json:"files_total,omitempty"` + Error string `protobuf:"bytes,12,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SftpTransfer) Reset() { + *x = SftpTransfer{} + mi := &file_proto_v1_gateway_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SftpTransfer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SftpTransfer) ProtoMessage() {} + +func (x *SftpTransfer) ProtoReflect() protoreflect.Message { + mi := &file_proto_v1_gateway_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SftpTransfer.ProtoReflect.Descriptor instead. +func (*SftpTransfer) Descriptor() ([]byte, []int) { + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{24} +} + +func (x *SftpTransfer) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *SftpTransfer) GetSessionId() string { + if x != nil { + return x.SessionId + } + return "" +} + +func (x *SftpTransfer) GetDirection() string { + if x != nil { + return x.Direction + } + return "" +} + +func (x *SftpTransfer) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *SftpTransfer) GetSourcePath() string { + if x != nil { + return x.SourcePath + } + return "" +} + +func (x *SftpTransfer) GetTargetPath() string { + if x != nil { + return x.TargetPath + } + return "" +} + +func (x *SftpTransfer) GetCurrentPath() string { + if x != nil { + return x.CurrentPath + } + return "" +} + +func (x *SftpTransfer) GetBytesDone() uint64 { + if x != nil { + return x.BytesDone + } + return 0 +} + +func (x *SftpTransfer) GetBytesTotal() uint64 { + if x != nil { + return x.BytesTotal + } + return 0 +} + +func (x *SftpTransfer) GetFilesDone() uint32 { + if x != nil { + return x.FilesDone + } + return 0 +} + +func (x *SftpTransfer) GetFilesTotal() uint32 { + if x != nil { + return x.FilesTotal + } + return 0 +} + +func (x *SftpTransfer) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type SftpResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Action string `protobuf:"bytes,1,opt,name=action,proto3" json:"action,omitempty"` + Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` + Entries []*SftpEntry `protobuf:"bytes,3,rep,name=entries,proto3" json:"entries,omitempty"` + Entry *SftpEntry `protobuf:"bytes,4,opt,name=entry,proto3" json:"entry,omitempty"` + Exists bool `protobuf:"varint,5,opt,name=exists,proto3" json:"exists,omitempty"` + Transfer *SftpTransfer `protobuf:"bytes,6,opt,name=transfer,proto3" json:"transfer,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SftpResponse) Reset() { + *x = SftpResponse{} + mi := &file_proto_v1_gateway_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SftpResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SftpResponse) ProtoMessage() {} + +func (x *SftpResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_v1_gateway_proto_msgTypes[25] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SftpResponse.ProtoReflect.Descriptor instead. +func (*SftpResponse) Descriptor() ([]byte, []int) { + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{25} +} + +func (x *SftpResponse) GetAction() string { + if x != nil { + return x.Action + } + return "" +} + +func (x *SftpResponse) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *SftpResponse) GetEntries() []*SftpEntry { + if x != nil { + return x.Entries + } + return nil +} + +func (x *SftpResponse) GetEntry() *SftpEntry { + if x != nil { + return x.Entry + } + return nil +} + +func (x *SftpResponse) GetExists() bool { + if x != nil { + return x.Exists + } + return false +} + +func (x *SftpResponse) GetTransfer() *SftpTransfer { + if x != nil { + return x.Transfer + } + return nil +} + +type SftpEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + Kind string `protobuf:"bytes,1,opt,name=kind,proto3" json:"kind,omitempty"` + Transfer *SftpTransfer `protobuf:"bytes,2,opt,name=transfer,proto3" json:"transfer,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SftpEvent) Reset() { + *x = SftpEvent{} + mi := &file_proto_v1_gateway_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SftpEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SftpEvent) ProtoMessage() {} + +func (x *SftpEvent) ProtoReflect() protoreflect.Message { + mi := &file_proto_v1_gateway_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SftpEvent.ProtoReflect.Descriptor instead. +func (*SftpEvent) Descriptor() ([]byte, []int) { + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{26} +} + +func (x *SftpEvent) GetKind() string { + if x != nil { + return x.Kind + } + return "" +} + +func (x *SftpEvent) GetTransfer() *SftpTransfer { + if x != nil { + return x.Transfer + } + return nil +} + +type TerminalSshPrompt struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Kind string `protobuf:"bytes,2,opt,name=kind,proto3" json:"kind,omitempty"` + HostId string `protobuf:"bytes,3,opt,name=host_id,json=hostId,proto3" json:"host_id,omitempty"` + HostName string `protobuf:"bytes,4,opt,name=host_name,json=hostName,proto3" json:"host_name,omitempty"` + Host string `protobuf:"bytes,5,opt,name=host,proto3" json:"host,omitempty"` + Port uint32 `protobuf:"varint,6,opt,name=port,proto3" json:"port,omitempty"` + Message string `protobuf:"bytes,7,opt,name=message,proto3" json:"message,omitempty"` + FingerprintSha256 string `protobuf:"bytes,8,opt,name=fingerprint_sha256,json=fingerprintSha256,proto3" json:"fingerprint_sha256,omitempty"` + KeyType string `protobuf:"bytes,9,opt,name=key_type,json=keyType,proto3" json:"key_type,omitempty"` + AnswerEcho bool `protobuf:"varint,10,opt,name=answer_echo,json=answerEcho,proto3" json:"answer_echo,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TerminalSshPrompt) Reset() { + *x = TerminalSshPrompt{} + mi := &file_proto_v1_gateway_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TerminalSshPrompt) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TerminalSshPrompt) ProtoMessage() {} + +func (x *TerminalSshPrompt) ProtoReflect() protoreflect.Message { + mi := &file_proto_v1_gateway_proto_msgTypes[27] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TerminalSshPrompt.ProtoReflect.Descriptor instead. +func (*TerminalSshPrompt) Descriptor() ([]byte, []int) { + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{27} +} + +func (x *TerminalSshPrompt) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *TerminalSshPrompt) GetKind() string { + if x != nil { + return x.Kind + } + return "" +} + +func (x *TerminalSshPrompt) GetHostId() string { + if x != nil { + return x.HostId + } + return "" +} + +func (x *TerminalSshPrompt) GetHostName() string { + if x != nil { + return x.HostName + } + return "" +} + +func (x *TerminalSshPrompt) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *TerminalSshPrompt) GetPort() uint32 { + if x != nil { + return x.Port + } + return 0 +} + +func (x *TerminalSshPrompt) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *TerminalSshPrompt) GetFingerprintSha256() string { + if x != nil { + return x.FingerprintSha256 + } + return "" +} + +func (x *TerminalSshPrompt) GetKeyType() string { + if x != nil { + return x.KeyType + } + return "" +} + +func (x *TerminalSshPrompt) GetAnswerEcho() bool { + if x != nil { + return x.AnswerEcho + } + return false +} + +type TerminalShellOption struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Label string `protobuf:"bytes,2,opt,name=label,proto3" json:"label,omitempty"` + Command string `protobuf:"bytes,3,opt,name=command,proto3" json:"command,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TerminalShellOption) Reset() { + *x = TerminalShellOption{} + mi := &file_proto_v1_gateway_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TerminalShellOption) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TerminalShellOption) ProtoMessage() {} + +func (x *TerminalShellOption) ProtoReflect() protoreflect.Message { + mi := &file_proto_v1_gateway_proto_msgTypes[28] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TerminalShellOption.ProtoReflect.Descriptor instead. +func (*TerminalShellOption) Descriptor() ([]byte, []int) { + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{28} +} + +func (x *TerminalShellOption) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *TerminalShellOption) GetLabel() string { + if x != nil { + return x.Label + } + return "" +} + +func (x *TerminalShellOption) GetCommand() string { + if x != nil { + return x.Command + } + return "" +} + +type TerminalResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Action string `protobuf:"bytes,1,opt,name=action,proto3" json:"action,omitempty"` + Sessions []*TerminalSession `protobuf:"bytes,2,rep,name=sessions,proto3" json:"sessions,omitempty"` + Session *TerminalSession `protobuf:"bytes,3,opt,name=session,proto3" json:"session,omitempty"` + Output string `protobuf:"bytes,4,opt,name=output,proto3" json:"output,omitempty"` + Truncated bool `protobuf:"varint,5,opt,name=truncated,proto3" json:"truncated,omitempty"` + ShellOptions []*TerminalShellOption `protobuf:"bytes,6,rep,name=shell_options,json=shellOptions,proto3" json:"shell_options,omitempty"` + DefaultShell string `protobuf:"bytes,7,opt,name=default_shell,json=defaultShell,proto3" json:"default_shell,omitempty"` + OutputStartOffset uint64 `protobuf:"varint,8,opt,name=output_start_offset,json=outputStartOffset,proto3" json:"output_start_offset,omitempty"` + OutputEndOffset uint64 `protobuf:"varint,9,opt,name=output_end_offset,json=outputEndOffset,proto3" json:"output_end_offset,omitempty"` + SshPrompt *TerminalSshPrompt `protobuf:"bytes,10,opt,name=ssh_prompt,json=sshPrompt,proto3" json:"ssh_prompt,omitempty"` + LatencyMs uint32 `protobuf:"varint,11,opt,name=latency_ms,json=latencyMs,proto3" json:"latency_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TerminalResponse) Reset() { + *x = TerminalResponse{} + mi := &file_proto_v1_gateway_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TerminalResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TerminalResponse) ProtoMessage() {} + +func (x *TerminalResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_v1_gateway_proto_msgTypes[29] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TerminalResponse.ProtoReflect.Descriptor instead. +func (*TerminalResponse) Descriptor() ([]byte, []int) { + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{29} +} + +func (x *TerminalResponse) GetAction() string { + if x != nil { + return x.Action + } + return "" +} + +func (x *TerminalResponse) GetSessions() []*TerminalSession { + if x != nil { + return x.Sessions + } + return nil +} + +func (x *TerminalResponse) GetSession() *TerminalSession { + if x != nil { + return x.Session + } + return nil +} + +func (x *TerminalResponse) GetOutput() string { + if x != nil { + return x.Output + } + return "" +} + +func (x *TerminalResponse) GetTruncated() bool { + if x != nil { + return x.Truncated + } + return false +} + +func (x *TerminalResponse) GetShellOptions() []*TerminalShellOption { + if x != nil { + return x.ShellOptions + } + return nil +} + +func (x *TerminalResponse) GetDefaultShell() string { + if x != nil { + return x.DefaultShell + } + return "" +} + +func (x *TerminalResponse) GetOutputStartOffset() uint64 { + if x != nil { + return x.OutputStartOffset + } + return 0 +} + +func (x *TerminalResponse) GetOutputEndOffset() uint64 { + if x != nil { + return x.OutputEndOffset + } + return 0 +} + +func (x *TerminalResponse) GetSshPrompt() *TerminalSshPrompt { + if x != nil { + return x.SshPrompt + } + return nil +} + +func (x *TerminalResponse) GetLatencyMs() uint32 { + if x != nil { + return x.LatencyMs + } + return 0 +} + +type TerminalEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + Kind string `protobuf:"bytes,1,opt,name=kind,proto3" json:"kind,omitempty"` + SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + ProjectPathKey string `protobuf:"bytes,3,opt,name=project_path_key,json=projectPathKey,proto3" json:"project_path_key,omitempty"` + Session *TerminalSession `protobuf:"bytes,4,opt,name=session,proto3" json:"session,omitempty"` + Data string `protobuf:"bytes,5,opt,name=data,proto3" json:"data,omitempty"` + OutputStartOffset uint64 `protobuf:"varint,6,opt,name=output_start_offset,json=outputStartOffset,proto3" json:"output_start_offset,omitempty"` + OutputEndOffset uint64 `protobuf:"varint,7,opt,name=output_end_offset,json=outputEndOffset,proto3" json:"output_end_offset,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TerminalEvent) Reset() { + *x = TerminalEvent{} + mi := &file_proto_v1_gateway_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TerminalEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TerminalEvent) ProtoMessage() {} + +func (x *TerminalEvent) ProtoReflect() protoreflect.Message { + mi := &file_proto_v1_gateway_proto_msgTypes[30] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TerminalEvent.ProtoReflect.Descriptor instead. +func (*TerminalEvent) Descriptor() ([]byte, []int) { + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{30} +} + +func (x *TerminalEvent) GetKind() string { + if x != nil { + return x.Kind + } + return "" +} + +func (x *TerminalEvent) GetSessionId() string { + if x != nil { + return x.SessionId + } + return "" +} + +func (x *TerminalEvent) GetProjectPathKey() string { + if x != nil { + return x.ProjectPathKey + } + return "" +} + +func (x *TerminalEvent) GetSession() *TerminalSession { + if x != nil { + return x.Session + } + return nil +} + +func (x *TerminalEvent) GetData() string { + if x != nil { + return x.Data } return "" } @@ -3352,7 +4212,7 @@ type GitRequest struct { func (x *GitRequest) Reset() { *x = GitRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[24] + mi := &file_proto_v1_gateway_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3364,7 +4224,7 @@ func (x *GitRequest) String() string { func (*GitRequest) ProtoMessage() {} func (x *GitRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[24] + mi := &file_proto_v1_gateway_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3377,7 +4237,7 @@ func (x *GitRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GitRequest.ProtoReflect.Descriptor instead. func (*GitRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{24} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{31} } func (x *GitRequest) GetAction() string { @@ -3411,7 +4271,7 @@ type GitResponse struct { func (x *GitResponse) Reset() { *x = GitResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[25] + mi := &file_proto_v1_gateway_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3423,7 +4283,7 @@ func (x *GitResponse) String() string { func (*GitResponse) ProtoMessage() {} func (x *GitResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[25] + mi := &file_proto_v1_gateway_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3436,7 +4296,7 @@ func (x *GitResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GitResponse.ProtoReflect.Descriptor instead. func (*GitResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{25} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{32} } func (x *GitResponse) GetAction() string { @@ -3470,7 +4330,7 @@ type ChatRequest struct { func (x *ChatRequest) Reset() { *x = ChatRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[26] + mi := &file_proto_v1_gateway_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3482,7 +4342,7 @@ func (x *ChatRequest) String() string { func (*ChatRequest) ProtoMessage() {} func (x *ChatRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[26] + mi := &file_proto_v1_gateway_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3495,7 +4355,7 @@ func (x *ChatRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ChatRequest.ProtoReflect.Descriptor instead. func (*ChatRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{26} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{33} } func (x *ChatRequest) GetConversationId() string { @@ -3570,7 +4430,7 @@ type CancelChatRequest struct { func (x *CancelChatRequest) Reset() { *x = CancelChatRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[27] + mi := &file_proto_v1_gateway_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3582,7 +4442,7 @@ func (x *CancelChatRequest) String() string { func (*CancelChatRequest) ProtoMessage() {} func (x *CancelChatRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[27] + mi := &file_proto_v1_gateway_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3595,7 +4455,7 @@ func (x *CancelChatRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CancelChatRequest.ProtoReflect.Descriptor instead. func (*CancelChatRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{27} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{34} } func (x *CancelChatRequest) GetConversationId() string { @@ -3616,7 +4476,7 @@ type ChatEvent struct { func (x *ChatEvent) Reset() { *x = ChatEvent{} - mi := &file_proto_v1_gateway_proto_msgTypes[28] + mi := &file_proto_v1_gateway_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3628,7 +4488,7 @@ func (x *ChatEvent) String() string { func (*ChatEvent) ProtoMessage() {} func (x *ChatEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[28] + mi := &file_proto_v1_gateway_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3641,7 +4501,7 @@ func (x *ChatEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use ChatEvent.ProtoReflect.Descriptor instead. func (*ChatEvent) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{28} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{35} } func (x *ChatEvent) GetType() ChatEvent_ChatEventType { @@ -3682,7 +4542,7 @@ type ChatControlEvent struct { func (x *ChatControlEvent) Reset() { *x = ChatControlEvent{} - mi := &file_proto_v1_gateway_proto_msgTypes[29] + mi := &file_proto_v1_gateway_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3694,7 +4554,7 @@ func (x *ChatControlEvent) String() string { func (*ChatControlEvent) ProtoMessage() {} func (x *ChatControlEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[29] + mi := &file_proto_v1_gateway_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3707,7 +4567,7 @@ func (x *ChatControlEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use ChatControlEvent.ProtoReflect.Descriptor instead. func (*ChatControlEvent) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{29} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{36} } func (x *ChatControlEvent) GetRequestId() string { @@ -3786,7 +4646,7 @@ type RuntimeStatusEvent struct { func (x *RuntimeStatusEvent) Reset() { *x = RuntimeStatusEvent{} - mi := &file_proto_v1_gateway_proto_msgTypes[30] + mi := &file_proto_v1_gateway_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3798,7 +4658,7 @@ func (x *RuntimeStatusEvent) String() string { func (*RuntimeStatusEvent) ProtoMessage() {} func (x *RuntimeStatusEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[30] + mi := &file_proto_v1_gateway_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3811,7 +4671,7 @@ func (x *RuntimeStatusEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use RuntimeStatusEvent.ProtoReflect.Descriptor instead. func (*RuntimeStatusEvent) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{30} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{37} } func (x *RuntimeStatusEvent) GetWorkerId() string { @@ -3860,7 +4720,7 @@ type CronManageRequest struct { func (x *CronManageRequest) Reset() { *x = CronManageRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[31] + mi := &file_proto_v1_gateway_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3872,7 +4732,7 @@ func (x *CronManageRequest) String() string { func (*CronManageRequest) ProtoMessage() {} func (x *CronManageRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[31] + mi := &file_proto_v1_gateway_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3885,7 +4745,7 @@ func (x *CronManageRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CronManageRequest.ProtoReflect.Descriptor instead. func (*CronManageRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{31} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{38} } func (x *CronManageRequest) GetAction() string { @@ -3919,7 +4779,7 @@ type CronManageResponse struct { func (x *CronManageResponse) Reset() { *x = CronManageResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[32] + mi := &file_proto_v1_gateway_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3931,7 +4791,7 @@ func (x *CronManageResponse) String() string { func (*CronManageResponse) ProtoMessage() {} func (x *CronManageResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[32] + mi := &file_proto_v1_gateway_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3944,7 +4804,7 @@ func (x *CronManageResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CronManageResponse.ProtoReflect.Descriptor instead. func (*CronManageResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{32} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{39} } func (x *CronManageResponse) GetAction() string { @@ -3973,7 +4833,7 @@ type HistoryListRequest struct { func (x *HistoryListRequest) Reset() { *x = HistoryListRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[33] + mi := &file_proto_v1_gateway_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3985,7 +4845,7 @@ func (x *HistoryListRequest) String() string { func (*HistoryListRequest) ProtoMessage() {} func (x *HistoryListRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[33] + mi := &file_proto_v1_gateway_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3998,7 +4858,7 @@ func (x *HistoryListRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryListRequest.ProtoReflect.Descriptor instead. func (*HistoryListRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{33} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{40} } func (x *HistoryListRequest) GetPage() int32 { @@ -4039,7 +4899,7 @@ type HistoryListResponse struct { func (x *HistoryListResponse) Reset() { *x = HistoryListResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[34] + mi := &file_proto_v1_gateway_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4051,7 +4911,7 @@ func (x *HistoryListResponse) String() string { func (*HistoryListResponse) ProtoMessage() {} func (x *HistoryListResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[34] + mi := &file_proto_v1_gateway_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4064,7 +4924,7 @@ func (x *HistoryListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryListResponse.ProtoReflect.Descriptor instead. func (*HistoryListResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{34} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{41} } func (x *HistoryListResponse) GetConversations() []*ConversationSummary { @@ -4101,7 +4961,7 @@ type ConversationSummary struct { func (x *ConversationSummary) Reset() { *x = ConversationSummary{} - mi := &file_proto_v1_gateway_proto_msgTypes[35] + mi := &file_proto_v1_gateway_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4113,7 +4973,7 @@ func (x *ConversationSummary) String() string { func (*ConversationSummary) ProtoMessage() {} func (x *ConversationSummary) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[35] + mi := &file_proto_v1_gateway_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4126,7 +4986,7 @@ func (x *ConversationSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use ConversationSummary.ProtoReflect.Descriptor instead. func (*ConversationSummary) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{35} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{42} } func (x *ConversationSummary) GetId() string { @@ -4223,7 +5083,7 @@ type HistoryGetRequest struct { func (x *HistoryGetRequest) Reset() { *x = HistoryGetRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[36] + mi := &file_proto_v1_gateway_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4235,7 +5095,7 @@ func (x *HistoryGetRequest) String() string { func (*HistoryGetRequest) ProtoMessage() {} func (x *HistoryGetRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[36] + mi := &file_proto_v1_gateway_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4248,7 +5108,7 @@ func (x *HistoryGetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryGetRequest.ProtoReflect.Descriptor instead. func (*HistoryGetRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{36} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{43} } func (x *HistoryGetRequest) GetConversationId() string { @@ -4279,7 +5139,7 @@ type HistoryGetResponse struct { func (x *HistoryGetResponse) Reset() { *x = HistoryGetResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[37] + mi := &file_proto_v1_gateway_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4291,7 +5151,7 @@ func (x *HistoryGetResponse) String() string { func (*HistoryGetResponse) ProtoMessage() {} func (x *HistoryGetResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[37] + mi := &file_proto_v1_gateway_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4304,7 +5164,7 @@ func (x *HistoryGetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryGetResponse.ProtoReflect.Descriptor instead. func (*HistoryGetResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{37} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{44} } func (x *HistoryGetResponse) GetConversationId() string { @@ -4359,7 +5219,7 @@ type HistoryRenameRequest struct { func (x *HistoryRenameRequest) Reset() { *x = HistoryRenameRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[38] + mi := &file_proto_v1_gateway_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4371,7 +5231,7 @@ func (x *HistoryRenameRequest) String() string { func (*HistoryRenameRequest) ProtoMessage() {} func (x *HistoryRenameRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[38] + mi := &file_proto_v1_gateway_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4384,7 +5244,7 @@ func (x *HistoryRenameRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryRenameRequest.ProtoReflect.Descriptor instead. func (*HistoryRenameRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{38} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{45} } func (x *HistoryRenameRequest) GetConversationId() string { @@ -4410,7 +5270,7 @@ type HistoryRenameResponse struct { func (x *HistoryRenameResponse) Reset() { *x = HistoryRenameResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[39] + mi := &file_proto_v1_gateway_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4422,7 +5282,7 @@ func (x *HistoryRenameResponse) String() string { func (*HistoryRenameResponse) ProtoMessage() {} func (x *HistoryRenameResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[39] + mi := &file_proto_v1_gateway_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4435,7 +5295,7 @@ func (x *HistoryRenameResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryRenameResponse.ProtoReflect.Descriptor instead. func (*HistoryRenameResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{39} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{46} } func (x *HistoryRenameResponse) GetConversation() *ConversationSummary { @@ -4455,7 +5315,7 @@ type HistoryPinRequest struct { func (x *HistoryPinRequest) Reset() { *x = HistoryPinRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[40] + mi := &file_proto_v1_gateway_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4467,7 +5327,7 @@ func (x *HistoryPinRequest) String() string { func (*HistoryPinRequest) ProtoMessage() {} func (x *HistoryPinRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[40] + mi := &file_proto_v1_gateway_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4480,7 +5340,7 @@ func (x *HistoryPinRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryPinRequest.ProtoReflect.Descriptor instead. func (*HistoryPinRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{40} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{47} } func (x *HistoryPinRequest) GetConversationId() string { @@ -4506,7 +5366,7 @@ type HistoryPinResponse struct { func (x *HistoryPinResponse) Reset() { *x = HistoryPinResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[41] + mi := &file_proto_v1_gateway_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4518,7 +5378,7 @@ func (x *HistoryPinResponse) String() string { func (*HistoryPinResponse) ProtoMessage() {} func (x *HistoryPinResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[41] + mi := &file_proto_v1_gateway_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4531,7 +5391,7 @@ func (x *HistoryPinResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryPinResponse.ProtoReflect.Descriptor instead. func (*HistoryPinResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{41} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{48} } func (x *HistoryPinResponse) GetConversation() *ConversationSummary { @@ -4555,7 +5415,7 @@ type HistoryShareStatus struct { func (x *HistoryShareStatus) Reset() { *x = HistoryShareStatus{} - mi := &file_proto_v1_gateway_proto_msgTypes[42] + mi := &file_proto_v1_gateway_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4567,7 +5427,7 @@ func (x *HistoryShareStatus) String() string { func (*HistoryShareStatus) ProtoMessage() {} func (x *HistoryShareStatus) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[42] + mi := &file_proto_v1_gateway_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4580,7 +5440,7 @@ func (x *HistoryShareStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryShareStatus.ProtoReflect.Descriptor instead. func (*HistoryShareStatus) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{42} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{49} } func (x *HistoryShareStatus) GetConversationId() string { @@ -4634,7 +5494,7 @@ type HistoryShareGetRequest struct { func (x *HistoryShareGetRequest) Reset() { *x = HistoryShareGetRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[43] + mi := &file_proto_v1_gateway_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4646,7 +5506,7 @@ func (x *HistoryShareGetRequest) String() string { func (*HistoryShareGetRequest) ProtoMessage() {} func (x *HistoryShareGetRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[43] + mi := &file_proto_v1_gateway_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4659,7 +5519,7 @@ func (x *HistoryShareGetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryShareGetRequest.ProtoReflect.Descriptor instead. func (*HistoryShareGetRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{43} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{50} } func (x *HistoryShareGetRequest) GetConversationId() string { @@ -4678,7 +5538,7 @@ type HistoryShareGetResponse struct { func (x *HistoryShareGetResponse) Reset() { *x = HistoryShareGetResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[44] + mi := &file_proto_v1_gateway_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4690,7 +5550,7 @@ func (x *HistoryShareGetResponse) String() string { func (*HistoryShareGetResponse) ProtoMessage() {} func (x *HistoryShareGetResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[44] + mi := &file_proto_v1_gateway_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4703,7 +5563,7 @@ func (x *HistoryShareGetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryShareGetResponse.ProtoReflect.Descriptor instead. func (*HistoryShareGetResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{44} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{51} } func (x *HistoryShareGetResponse) GetShare() *HistoryShareStatus { @@ -4724,7 +5584,7 @@ type HistoryShareSetRequest struct { func (x *HistoryShareSetRequest) Reset() { *x = HistoryShareSetRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[45] + mi := &file_proto_v1_gateway_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4736,7 +5596,7 @@ func (x *HistoryShareSetRequest) String() string { func (*HistoryShareSetRequest) ProtoMessage() {} func (x *HistoryShareSetRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[45] + mi := &file_proto_v1_gateway_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4749,7 +5609,7 @@ func (x *HistoryShareSetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryShareSetRequest.ProtoReflect.Descriptor instead. func (*HistoryShareSetRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{45} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{52} } func (x *HistoryShareSetRequest) GetConversationId() string { @@ -4782,7 +5642,7 @@ type HistoryShareSetResponse struct { func (x *HistoryShareSetResponse) Reset() { *x = HistoryShareSetResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[46] + mi := &file_proto_v1_gateway_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4794,7 +5654,7 @@ func (x *HistoryShareSetResponse) String() string { func (*HistoryShareSetResponse) ProtoMessage() {} func (x *HistoryShareSetResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[46] + mi := &file_proto_v1_gateway_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4807,7 +5667,7 @@ func (x *HistoryShareSetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryShareSetResponse.ProtoReflect.Descriptor instead. func (*HistoryShareSetResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{46} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{53} } func (x *HistoryShareSetResponse) GetShare() *HistoryShareStatus { @@ -4826,7 +5686,7 @@ type HistoryShareResolveRequest struct { func (x *HistoryShareResolveRequest) Reset() { *x = HistoryShareResolveRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[47] + mi := &file_proto_v1_gateway_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4838,7 +5698,7 @@ func (x *HistoryShareResolveRequest) String() string { func (*HistoryShareResolveRequest) ProtoMessage() {} func (x *HistoryShareResolveRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[47] + mi := &file_proto_v1_gateway_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4851,7 +5711,7 @@ func (x *HistoryShareResolveRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryShareResolveRequest.ProtoReflect.Descriptor instead. func (*HistoryShareResolveRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{47} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{54} } func (x *HistoryShareResolveRequest) GetToken() string { @@ -4874,7 +5734,7 @@ type HistoryShareResolveResponse struct { func (x *HistoryShareResolveResponse) Reset() { *x = HistoryShareResolveResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[48] + mi := &file_proto_v1_gateway_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4886,7 +5746,7 @@ func (x *HistoryShareResolveResponse) String() string { func (*HistoryShareResolveResponse) ProtoMessage() {} func (x *HistoryShareResolveResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[48] + mi := &file_proto_v1_gateway_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4899,7 +5759,7 @@ func (x *HistoryShareResolveResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryShareResolveResponse.ProtoReflect.Descriptor instead. func (*HistoryShareResolveResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{48} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{55} } func (x *HistoryShareResolveResponse) GetConversationId() string { @@ -4945,7 +5805,7 @@ type HistoryWorkdirsRequest struct { func (x *HistoryWorkdirsRequest) Reset() { *x = HistoryWorkdirsRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[49] + mi := &file_proto_v1_gateway_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4957,7 +5817,7 @@ func (x *HistoryWorkdirsRequest) String() string { func (*HistoryWorkdirsRequest) ProtoMessage() {} func (x *HistoryWorkdirsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[49] + mi := &file_proto_v1_gateway_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4970,7 +5830,7 @@ func (x *HistoryWorkdirsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryWorkdirsRequest.ProtoReflect.Descriptor instead. func (*HistoryWorkdirsRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{49} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{56} } type HistoryWorkdirSummary struct { @@ -4984,7 +5844,7 @@ type HistoryWorkdirSummary struct { func (x *HistoryWorkdirSummary) Reset() { *x = HistoryWorkdirSummary{} - mi := &file_proto_v1_gateway_proto_msgTypes[50] + mi := &file_proto_v1_gateway_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4996,7 +5856,7 @@ func (x *HistoryWorkdirSummary) String() string { func (*HistoryWorkdirSummary) ProtoMessage() {} func (x *HistoryWorkdirSummary) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[50] + mi := &file_proto_v1_gateway_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5009,7 +5869,7 @@ func (x *HistoryWorkdirSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryWorkdirSummary.ProtoReflect.Descriptor instead. func (*HistoryWorkdirSummary) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{50} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{57} } func (x *HistoryWorkdirSummary) GetPath() string { @@ -5042,7 +5902,7 @@ type HistoryWorkdirsResponse struct { func (x *HistoryWorkdirsResponse) Reset() { *x = HistoryWorkdirsResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[51] + mi := &file_proto_v1_gateway_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5054,7 +5914,7 @@ func (x *HistoryWorkdirsResponse) String() string { func (*HistoryWorkdirsResponse) ProtoMessage() {} func (x *HistoryWorkdirsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[51] + mi := &file_proto_v1_gateway_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5067,7 +5927,7 @@ func (x *HistoryWorkdirsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryWorkdirsResponse.ProtoReflect.Descriptor instead. func (*HistoryWorkdirsResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{51} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{58} } func (x *HistoryWorkdirsResponse) GetWorkdirs() []*HistoryWorkdirSummary { @@ -5086,7 +5946,7 @@ type HistoryDeleteRequest struct { func (x *HistoryDeleteRequest) Reset() { *x = HistoryDeleteRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[52] + mi := &file_proto_v1_gateway_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5098,7 +5958,7 @@ func (x *HistoryDeleteRequest) String() string { func (*HistoryDeleteRequest) ProtoMessage() {} func (x *HistoryDeleteRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[52] + mi := &file_proto_v1_gateway_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5111,7 +5971,7 @@ func (x *HistoryDeleteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryDeleteRequest.ProtoReflect.Descriptor instead. func (*HistoryDeleteRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{52} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{59} } func (x *HistoryDeleteRequest) GetConversationId() string { @@ -5129,7 +5989,7 @@ type HistoryDeleteResponse struct { func (x *HistoryDeleteResponse) Reset() { *x = HistoryDeleteResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[53] + mi := &file_proto_v1_gateway_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5141,7 +6001,7 @@ func (x *HistoryDeleteResponse) String() string { func (*HistoryDeleteResponse) ProtoMessage() {} func (x *HistoryDeleteResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[53] + mi := &file_proto_v1_gateway_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5154,7 +6014,7 @@ func (x *HistoryDeleteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryDeleteResponse.ProtoReflect.Descriptor instead. func (*HistoryDeleteResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{53} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{60} } type HistoryTruncateRequest struct { @@ -5169,7 +6029,7 @@ type HistoryTruncateRequest struct { func (x *HistoryTruncateRequest) Reset() { *x = HistoryTruncateRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[54] + mi := &file_proto_v1_gateway_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5181,7 +6041,7 @@ func (x *HistoryTruncateRequest) String() string { func (*HistoryTruncateRequest) ProtoMessage() {} func (x *HistoryTruncateRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[54] + mi := &file_proto_v1_gateway_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5194,7 +6054,7 @@ func (x *HistoryTruncateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryTruncateRequest.ProtoReflect.Descriptor instead. func (*HistoryTruncateRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{54} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{61} } func (x *HistoryTruncateRequest) GetConversationId() string { @@ -5236,7 +6096,7 @@ type HistoryTruncateResponse struct { func (x *HistoryTruncateResponse) Reset() { *x = HistoryTruncateResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[55] + mi := &file_proto_v1_gateway_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5248,7 +6108,7 @@ func (x *HistoryTruncateResponse) String() string { func (*HistoryTruncateResponse) ProtoMessage() {} func (x *HistoryTruncateResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[55] + mi := &file_proto_v1_gateway_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5261,7 +6121,7 @@ func (x *HistoryTruncateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryTruncateResponse.ProtoReflect.Descriptor instead. func (*HistoryTruncateResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{55} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{62} } func (x *HistoryTruncateResponse) GetConversationId() string { @@ -5296,7 +6156,7 @@ type HistorySyncEvent struct { func (x *HistorySyncEvent) Reset() { *x = HistorySyncEvent{} - mi := &file_proto_v1_gateway_proto_msgTypes[56] + mi := &file_proto_v1_gateway_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5308,7 +6168,7 @@ func (x *HistorySyncEvent) String() string { func (*HistorySyncEvent) ProtoMessage() {} func (x *HistorySyncEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[56] + mi := &file_proto_v1_gateway_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5321,7 +6181,7 @@ func (x *HistorySyncEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use HistorySyncEvent.ProtoReflect.Descriptor instead. func (*HistorySyncEvent) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{56} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{63} } func (x *HistorySyncEvent) GetKind() string { @@ -5353,7 +6213,7 @@ type ProviderListRequest struct { func (x *ProviderListRequest) Reset() { *x = ProviderListRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[57] + mi := &file_proto_v1_gateway_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5365,7 +6225,7 @@ func (x *ProviderListRequest) String() string { func (*ProviderListRequest) ProtoMessage() {} func (x *ProviderListRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[57] + mi := &file_proto_v1_gateway_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5378,7 +6238,7 @@ func (x *ProviderListRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderListRequest.ProtoReflect.Descriptor instead. func (*ProviderListRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{57} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{64} } type ProviderListResponse struct { @@ -5390,7 +6250,7 @@ type ProviderListResponse struct { func (x *ProviderListResponse) Reset() { *x = ProviderListResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[58] + mi := &file_proto_v1_gateway_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5402,7 +6262,7 @@ func (x *ProviderListResponse) String() string { func (*ProviderListResponse) ProtoMessage() {} func (x *ProviderListResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[58] + mi := &file_proto_v1_gateway_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5415,7 +6275,7 @@ func (x *ProviderListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderListResponse.ProtoReflect.Descriptor instead. func (*ProviderListResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{58} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{65} } func (x *ProviderListResponse) GetProvidersJson() string { @@ -5433,7 +6293,7 @@ type SettingsGetRequest struct { func (x *SettingsGetRequest) Reset() { *x = SettingsGetRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[59] + mi := &file_proto_v1_gateway_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5445,7 +6305,7 @@ func (x *SettingsGetRequest) String() string { func (*SettingsGetRequest) ProtoMessage() {} func (x *SettingsGetRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[59] + mi := &file_proto_v1_gateway_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5458,7 +6318,7 @@ func (x *SettingsGetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SettingsGetRequest.ProtoReflect.Descriptor instead. func (*SettingsGetRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{59} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{66} } type SettingsGetResponse struct { @@ -5470,7 +6330,7 @@ type SettingsGetResponse struct { func (x *SettingsGetResponse) Reset() { *x = SettingsGetResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[60] + mi := &file_proto_v1_gateway_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5482,7 +6342,7 @@ func (x *SettingsGetResponse) String() string { func (*SettingsGetResponse) ProtoMessage() {} func (x *SettingsGetResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[60] + mi := &file_proto_v1_gateway_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5495,7 +6355,7 @@ func (x *SettingsGetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SettingsGetResponse.ProtoReflect.Descriptor instead. func (*SettingsGetResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{60} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{67} } func (x *SettingsGetResponse) GetSettingsJson() string { @@ -5514,7 +6374,7 @@ type SettingsUpdateRequest struct { func (x *SettingsUpdateRequest) Reset() { *x = SettingsUpdateRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[61] + mi := &file_proto_v1_gateway_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5523,10 +6383,107 @@ func (x *SettingsUpdateRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*SettingsUpdateRequest) ProtoMessage() {} +func (*SettingsUpdateRequest) ProtoMessage() {} + +func (x *SettingsUpdateRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_v1_gateway_proto_msgTypes[68] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SettingsUpdateRequest.ProtoReflect.Descriptor instead. +func (*SettingsUpdateRequest) Descriptor() ([]byte, []int) { + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{68} +} + +func (x *SettingsUpdateRequest) GetSettingsJson() string { + if x != nil { + return x.SettingsJson + } + return "" +} + +type SettingsUpdateResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Accepted bool `protobuf:"varint,1,opt,name=accepted,proto3" json:"accepted,omitempty"` + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SettingsUpdateResponse) Reset() { + *x = SettingsUpdateResponse{} + mi := &file_proto_v1_gateway_proto_msgTypes[69] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SettingsUpdateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SettingsUpdateResponse) ProtoMessage() {} + +func (x *SettingsUpdateResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_v1_gateway_proto_msgTypes[69] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SettingsUpdateResponse.ProtoReflect.Descriptor instead. +func (*SettingsUpdateResponse) Descriptor() ([]byte, []int) { + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{69} +} + +func (x *SettingsUpdateResponse) GetAccepted() bool { + if x != nil { + return x.Accepted + } + return false +} + +func (x *SettingsUpdateResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +type SettingsResetSshKnownHostRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` + Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SettingsResetSshKnownHostRequest) Reset() { + *x = SettingsResetSshKnownHostRequest{} + mi := &file_proto_v1_gateway_proto_msgTypes[70] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SettingsResetSshKnownHostRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SettingsResetSshKnownHostRequest) ProtoMessage() {} -func (x *SettingsUpdateRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[61] +func (x *SettingsResetSshKnownHostRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_v1_gateway_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5537,41 +6494,47 @@ func (x *SettingsUpdateRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use SettingsUpdateRequest.ProtoReflect.Descriptor instead. -func (*SettingsUpdateRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{61} +// Deprecated: Use SettingsResetSshKnownHostRequest.ProtoReflect.Descriptor instead. +func (*SettingsResetSshKnownHostRequest) Descriptor() ([]byte, []int) { + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{70} } -func (x *SettingsUpdateRequest) GetSettingsJson() string { +func (x *SettingsResetSshKnownHostRequest) GetHost() string { if x != nil { - return x.SettingsJson + return x.Host } return "" } -type SettingsUpdateResponse struct { +func (x *SettingsResetSshKnownHostRequest) GetPort() uint32 { + if x != nil { + return x.Port + } + return 0 +} + +type SettingsResetSshKnownHostResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - Accepted bool `protobuf:"varint,1,opt,name=accepted,proto3" json:"accepted,omitempty"` - Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` + Deleted uint32 `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *SettingsUpdateResponse) Reset() { - *x = SettingsUpdateResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[62] +func (x *SettingsResetSshKnownHostResponse) Reset() { + *x = SettingsResetSshKnownHostResponse{} + mi := &file_proto_v1_gateway_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *SettingsUpdateResponse) String() string { +func (x *SettingsResetSshKnownHostResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*SettingsUpdateResponse) ProtoMessage() {} +func (*SettingsResetSshKnownHostResponse) ProtoMessage() {} -func (x *SettingsUpdateResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[62] +func (x *SettingsResetSshKnownHostResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_v1_gateway_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5582,23 +6545,16 @@ func (x *SettingsUpdateResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use SettingsUpdateResponse.ProtoReflect.Descriptor instead. -func (*SettingsUpdateResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{62} -} - -func (x *SettingsUpdateResponse) GetAccepted() bool { - if x != nil { - return x.Accepted - } - return false +// Deprecated: Use SettingsResetSshKnownHostResponse.ProtoReflect.Descriptor instead. +func (*SettingsResetSshKnownHostResponse) Descriptor() ([]byte, []int) { + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{71} } -func (x *SettingsUpdateResponse) GetMessage() string { +func (x *SettingsResetSshKnownHostResponse) GetDeleted() uint32 { if x != nil { - return x.Message + return x.Deleted } - return "" + return 0 } type SettingsSyncEvent struct { @@ -5610,7 +6566,7 @@ type SettingsSyncEvent struct { func (x *SettingsSyncEvent) Reset() { *x = SettingsSyncEvent{} - mi := &file_proto_v1_gateway_proto_msgTypes[63] + mi := &file_proto_v1_gateway_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5622,7 +6578,7 @@ func (x *SettingsSyncEvent) String() string { func (*SettingsSyncEvent) ProtoMessage() {} func (x *SettingsSyncEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[63] + mi := &file_proto_v1_gateway_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5635,7 +6591,7 @@ func (x *SettingsSyncEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use SettingsSyncEvent.ProtoReflect.Descriptor instead. func (*SettingsSyncEvent) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{63} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{72} } func (x *SettingsSyncEvent) GetSettingsJson() string { @@ -5653,7 +6609,7 @@ type SkillFilesListRequest struct { func (x *SkillFilesListRequest) Reset() { *x = SkillFilesListRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[64] + mi := &file_proto_v1_gateway_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5665,7 +6621,7 @@ func (x *SkillFilesListRequest) String() string { func (*SkillFilesListRequest) ProtoMessage() {} func (x *SkillFilesListRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[64] + mi := &file_proto_v1_gateway_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5678,7 +6634,7 @@ func (x *SkillFilesListRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SkillFilesListRequest.ProtoReflect.Descriptor instead. func (*SkillFilesListRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{64} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{73} } type SkillFilesListResponse struct { @@ -5692,7 +6648,7 @@ type SkillFilesListResponse struct { func (x *SkillFilesListResponse) Reset() { *x = SkillFilesListResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[65] + mi := &file_proto_v1_gateway_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5704,7 +6660,7 @@ func (x *SkillFilesListResponse) String() string { func (*SkillFilesListResponse) ProtoMessage() {} func (x *SkillFilesListResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[65] + mi := &file_proto_v1_gateway_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5717,7 +6673,7 @@ func (x *SkillFilesListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SkillFilesListResponse.ProtoReflect.Descriptor instead. func (*SkillFilesListResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{65} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{74} } func (x *SkillFilesListResponse) GetRootDir() string { @@ -5750,7 +6706,7 @@ type SkillMetadataReadRequest struct { func (x *SkillMetadataReadRequest) Reset() { *x = SkillMetadataReadRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[66] + mi := &file_proto_v1_gateway_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5762,7 +6718,7 @@ func (x *SkillMetadataReadRequest) String() string { func (*SkillMetadataReadRequest) ProtoMessage() {} func (x *SkillMetadataReadRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[66] + mi := &file_proto_v1_gateway_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5775,7 +6731,7 @@ func (x *SkillMetadataReadRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SkillMetadataReadRequest.ProtoReflect.Descriptor instead. func (*SkillMetadataReadRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{66} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{75} } func (x *SkillMetadataReadRequest) GetPath() string { @@ -5795,7 +6751,7 @@ type SkillMetadataReadResponse struct { func (x *SkillMetadataReadResponse) Reset() { *x = SkillMetadataReadResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[67] + mi := &file_proto_v1_gateway_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5807,7 +6763,7 @@ func (x *SkillMetadataReadResponse) String() string { func (*SkillMetadataReadResponse) ProtoMessage() {} func (x *SkillMetadataReadResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[67] + mi := &file_proto_v1_gateway_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5820,7 +6776,7 @@ func (x *SkillMetadataReadResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SkillMetadataReadResponse.ProtoReflect.Descriptor instead. func (*SkillMetadataReadResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{67} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{76} } func (x *SkillMetadataReadResponse) GetName() string { @@ -5848,7 +6804,7 @@ type SkillTextReadRequest struct { func (x *SkillTextReadRequest) Reset() { *x = SkillTextReadRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[68] + mi := &file_proto_v1_gateway_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5860,7 +6816,7 @@ func (x *SkillTextReadRequest) String() string { func (*SkillTextReadRequest) ProtoMessage() {} func (x *SkillTextReadRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[68] + mi := &file_proto_v1_gateway_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5873,7 +6829,7 @@ func (x *SkillTextReadRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SkillTextReadRequest.ProtoReflect.Descriptor instead. func (*SkillTextReadRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{68} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{77} } func (x *SkillTextReadRequest) GetPath() string { @@ -5907,7 +6863,7 @@ type SkillTextReadResponse struct { func (x *SkillTextReadResponse) Reset() { *x = SkillTextReadResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[69] + mi := &file_proto_v1_gateway_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5919,7 +6875,7 @@ func (x *SkillTextReadResponse) String() string { func (*SkillTextReadResponse) ProtoMessage() {} func (x *SkillTextReadResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[69] + mi := &file_proto_v1_gateway_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5932,7 +6888,7 @@ func (x *SkillTextReadResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SkillTextReadResponse.ProtoReflect.Descriptor instead. func (*SkillTextReadResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{69} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{78} } func (x *SkillTextReadResponse) GetContent() string { @@ -5958,7 +6914,7 @@ type SkillManageRequest struct { func (x *SkillManageRequest) Reset() { *x = SkillManageRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[70] + mi := &file_proto_v1_gateway_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5970,7 +6926,7 @@ func (x *SkillManageRequest) String() string { func (*SkillManageRequest) ProtoMessage() {} func (x *SkillManageRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[70] + mi := &file_proto_v1_gateway_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5983,7 +6939,7 @@ func (x *SkillManageRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SkillManageRequest.ProtoReflect.Descriptor instead. func (*SkillManageRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{70} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{79} } func (x *SkillManageRequest) GetPayloadJson() string { @@ -6002,7 +6958,7 @@ type SkillManageResponse struct { func (x *SkillManageResponse) Reset() { *x = SkillManageResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[71] + mi := &file_proto_v1_gateway_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6014,7 +6970,7 @@ func (x *SkillManageResponse) String() string { func (*SkillManageResponse) ProtoMessage() {} func (x *SkillManageResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[71] + mi := &file_proto_v1_gateway_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6027,7 +6983,7 @@ func (x *SkillManageResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SkillManageResponse.ProtoReflect.Descriptor instead. func (*SkillManageResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{71} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{80} } func (x *SkillManageResponse) GetResultJson() string { @@ -6048,7 +7004,7 @@ type FileMentionListRequest struct { func (x *FileMentionListRequest) Reset() { *x = FileMentionListRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[72] + mi := &file_proto_v1_gateway_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6060,7 +7016,7 @@ func (x *FileMentionListRequest) String() string { func (*FileMentionListRequest) ProtoMessage() {} func (x *FileMentionListRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[72] + mi := &file_proto_v1_gateway_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6073,7 +7029,7 @@ func (x *FileMentionListRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FileMentionListRequest.ProtoReflect.Descriptor instead. func (*FileMentionListRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{72} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{81} } func (x *FileMentionListRequest) GetWorkdir() string { @@ -6107,7 +7063,7 @@ type FileMentionEntry struct { func (x *FileMentionEntry) Reset() { *x = FileMentionEntry{} - mi := &file_proto_v1_gateway_proto_msgTypes[73] + mi := &file_proto_v1_gateway_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6119,7 +7075,7 @@ func (x *FileMentionEntry) String() string { func (*FileMentionEntry) ProtoMessage() {} func (x *FileMentionEntry) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[73] + mi := &file_proto_v1_gateway_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6132,7 +7088,7 @@ func (x *FileMentionEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use FileMentionEntry.ProtoReflect.Descriptor instead. func (*FileMentionEntry) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{73} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{82} } func (x *FileMentionEntry) GetPath() string { @@ -6159,7 +7115,7 @@ type FileMentionListResponse struct { func (x *FileMentionListResponse) Reset() { *x = FileMentionListResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[74] + mi := &file_proto_v1_gateway_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6171,7 +7127,7 @@ func (x *FileMentionListResponse) String() string { func (*FileMentionListResponse) ProtoMessage() {} func (x *FileMentionListResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[74] + mi := &file_proto_v1_gateway_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6184,7 +7140,7 @@ func (x *FileMentionListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FileMentionListResponse.ProtoReflect.Descriptor instead. func (*FileMentionListResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{74} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{83} } func (x *FileMentionListResponse) GetEntries() []*FileMentionEntry { @@ -6213,7 +7169,7 @@ type FsRoot struct { func (x *FsRoot) Reset() { *x = FsRoot{} - mi := &file_proto_v1_gateway_proto_msgTypes[75] + mi := &file_proto_v1_gateway_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6225,7 +7181,7 @@ func (x *FsRoot) String() string { func (*FsRoot) ProtoMessage() {} func (x *FsRoot) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[75] + mi := &file_proto_v1_gateway_proto_msgTypes[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6238,7 +7194,7 @@ func (x *FsRoot) ProtoReflect() protoreflect.Message { // Deprecated: Use FsRoot.ProtoReflect.Descriptor instead. func (*FsRoot) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{75} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{84} } func (x *FsRoot) GetId() string { @@ -6277,7 +7233,7 @@ type FsRootsRequest struct { func (x *FsRootsRequest) Reset() { *x = FsRootsRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[76] + mi := &file_proto_v1_gateway_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6289,7 +7245,7 @@ func (x *FsRootsRequest) String() string { func (*FsRootsRequest) ProtoMessage() {} func (x *FsRootsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[76] + mi := &file_proto_v1_gateway_proto_msgTypes[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6302,7 +7258,7 @@ func (x *FsRootsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsRootsRequest.ProtoReflect.Descriptor instead. func (*FsRootsRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{76} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{85} } type FsRootsResponse struct { @@ -6314,7 +7270,7 @@ type FsRootsResponse struct { func (x *FsRootsResponse) Reset() { *x = FsRootsResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[77] + mi := &file_proto_v1_gateway_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6326,7 +7282,7 @@ func (x *FsRootsResponse) String() string { func (*FsRootsResponse) ProtoMessage() {} func (x *FsRootsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[77] + mi := &file_proto_v1_gateway_proto_msgTypes[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6339,7 +7295,7 @@ func (x *FsRootsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsRootsResponse.ProtoReflect.Descriptor instead. func (*FsRootsResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{77} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{86} } func (x *FsRootsResponse) GetRoots() []*FsRoot { @@ -6359,7 +7315,7 @@ type FsListDirsRequest struct { func (x *FsListDirsRequest) Reset() { *x = FsListDirsRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[78] + mi := &file_proto_v1_gateway_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6371,7 +7327,7 @@ func (x *FsListDirsRequest) String() string { func (*FsListDirsRequest) ProtoMessage() {} func (x *FsListDirsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[78] + mi := &file_proto_v1_gateway_proto_msgTypes[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6384,7 +7340,7 @@ func (x *FsListDirsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsListDirsRequest.ProtoReflect.Descriptor instead. func (*FsListDirsRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{78} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{87} } func (x *FsListDirsRequest) GetPath() string { @@ -6411,7 +7367,7 @@ type FsDirEntry struct { func (x *FsDirEntry) Reset() { *x = FsDirEntry{} - mi := &file_proto_v1_gateway_proto_msgTypes[79] + mi := &file_proto_v1_gateway_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6423,7 +7379,7 @@ func (x *FsDirEntry) String() string { func (*FsDirEntry) ProtoMessage() {} func (x *FsDirEntry) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[79] + mi := &file_proto_v1_gateway_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6436,7 +7392,7 @@ func (x *FsDirEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use FsDirEntry.ProtoReflect.Descriptor instead. func (*FsDirEntry) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{79} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{88} } func (x *FsDirEntry) GetPath() string { @@ -6464,7 +7420,7 @@ type FsListDirsResponse struct { func (x *FsListDirsResponse) Reset() { *x = FsListDirsResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[80] + mi := &file_proto_v1_gateway_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6476,7 +7432,7 @@ func (x *FsListDirsResponse) String() string { func (*FsListDirsResponse) ProtoMessage() {} func (x *FsListDirsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[80] + mi := &file_proto_v1_gateway_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6489,7 +7445,7 @@ func (x *FsListDirsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsListDirsResponse.ProtoReflect.Descriptor instead. func (*FsListDirsResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{80} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{89} } func (x *FsListDirsResponse) GetPath() string { @@ -6523,7 +7479,7 @@ type FsCreateProjectFolderRequest struct { func (x *FsCreateProjectFolderRequest) Reset() { *x = FsCreateProjectFolderRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[81] + mi := &file_proto_v1_gateway_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6535,7 +7491,7 @@ func (x *FsCreateProjectFolderRequest) String() string { func (*FsCreateProjectFolderRequest) ProtoMessage() {} func (x *FsCreateProjectFolderRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[81] + mi := &file_proto_v1_gateway_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6548,7 +7504,7 @@ func (x *FsCreateProjectFolderRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsCreateProjectFolderRequest.ProtoReflect.Descriptor instead. func (*FsCreateProjectFolderRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{81} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{90} } func (x *FsCreateProjectFolderRequest) GetParent() string { @@ -6574,7 +7530,7 @@ type FsCreateProjectFolderResponse struct { func (x *FsCreateProjectFolderResponse) Reset() { *x = FsCreateProjectFolderResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[82] + mi := &file_proto_v1_gateway_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6586,7 +7542,7 @@ func (x *FsCreateProjectFolderResponse) String() string { func (*FsCreateProjectFolderResponse) ProtoMessage() {} func (x *FsCreateProjectFolderResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[82] + mi := &file_proto_v1_gateway_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6599,7 +7555,7 @@ func (x *FsCreateProjectFolderResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsCreateProjectFolderResponse.ProtoReflect.Descriptor instead. func (*FsCreateProjectFolderResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{82} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{91} } func (x *FsCreateProjectFolderResponse) GetPath() string { @@ -6622,7 +7578,7 @@ type FsListRequest struct { func (x *FsListRequest) Reset() { *x = FsListRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[83] + mi := &file_proto_v1_gateway_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6634,7 +7590,7 @@ func (x *FsListRequest) String() string { func (*FsListRequest) ProtoMessage() {} func (x *FsListRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[83] + mi := &file_proto_v1_gateway_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6647,7 +7603,7 @@ func (x *FsListRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsListRequest.ProtoReflect.Descriptor instead. func (*FsListRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{83} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{92} } func (x *FsListRequest) GetWorkdir() string { @@ -6695,7 +7651,7 @@ type FsListEntry struct { func (x *FsListEntry) Reset() { *x = FsListEntry{} - mi := &file_proto_v1_gateway_proto_msgTypes[84] + mi := &file_proto_v1_gateway_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6707,7 +7663,7 @@ func (x *FsListEntry) String() string { func (*FsListEntry) ProtoMessage() {} func (x *FsListEntry) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[84] + mi := &file_proto_v1_gateway_proto_msgTypes[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6720,7 +7676,7 @@ func (x *FsListEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use FsListEntry.ProtoReflect.Descriptor instead. func (*FsListEntry) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{84} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{93} } func (x *FsListEntry) GetPath() string { @@ -6753,7 +7709,7 @@ type FsListResponse struct { func (x *FsListResponse) Reset() { *x = FsListResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[85] + mi := &file_proto_v1_gateway_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6765,7 +7721,7 @@ func (x *FsListResponse) String() string { func (*FsListResponse) ProtoMessage() {} func (x *FsListResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[85] + mi := &file_proto_v1_gateway_proto_msgTypes[94] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6778,7 +7734,7 @@ func (x *FsListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsListResponse.ProtoReflect.Descriptor instead. func (*FsListResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{85} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{94} } func (x *FsListResponse) GetPath() string { @@ -6847,7 +7803,7 @@ type FsReadEditableTextRequest struct { func (x *FsReadEditableTextRequest) Reset() { *x = FsReadEditableTextRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[86] + mi := &file_proto_v1_gateway_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6859,7 +7815,7 @@ func (x *FsReadEditableTextRequest) String() string { func (*FsReadEditableTextRequest) ProtoMessage() {} func (x *FsReadEditableTextRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[86] + mi := &file_proto_v1_gateway_proto_msgTypes[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6872,7 +7828,7 @@ func (x *FsReadEditableTextRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsReadEditableTextRequest.ProtoReflect.Descriptor instead. func (*FsReadEditableTextRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{86} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{95} } func (x *FsReadEditableTextRequest) GetWorkdir() string { @@ -6903,7 +7859,7 @@ type FsReadEditableTextResponse struct { func (x *FsReadEditableTextResponse) Reset() { *x = FsReadEditableTextResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[87] + mi := &file_proto_v1_gateway_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6915,7 +7871,7 @@ func (x *FsReadEditableTextResponse) String() string { func (*FsReadEditableTextResponse) ProtoMessage() {} func (x *FsReadEditableTextResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[87] + mi := &file_proto_v1_gateway_proto_msgTypes[96] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6928,7 +7884,7 @@ func (x *FsReadEditableTextResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsReadEditableTextResponse.ProtoReflect.Descriptor instead. func (*FsReadEditableTextResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{87} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{96} } func (x *FsReadEditableTextResponse) GetPath() string { @@ -6983,7 +7939,7 @@ type FsReadWorkspaceImageRequest struct { func (x *FsReadWorkspaceImageRequest) Reset() { *x = FsReadWorkspaceImageRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[88] + mi := &file_proto_v1_gateway_proto_msgTypes[97] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6995,7 +7951,7 @@ func (x *FsReadWorkspaceImageRequest) String() string { func (*FsReadWorkspaceImageRequest) ProtoMessage() {} func (x *FsReadWorkspaceImageRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[88] + mi := &file_proto_v1_gateway_proto_msgTypes[97] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7008,7 +7964,7 @@ func (x *FsReadWorkspaceImageRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsReadWorkspaceImageRequest.ProtoReflect.Descriptor instead. func (*FsReadWorkspaceImageRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{88} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{97} } func (x *FsReadWorkspaceImageRequest) GetWorkdir() string { @@ -7039,7 +7995,7 @@ type FsReadWorkspaceImageResponse struct { func (x *FsReadWorkspaceImageResponse) Reset() { *x = FsReadWorkspaceImageResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[89] + mi := &file_proto_v1_gateway_proto_msgTypes[98] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7051,7 +8007,7 @@ func (x *FsReadWorkspaceImageResponse) String() string { func (*FsReadWorkspaceImageResponse) ProtoMessage() {} func (x *FsReadWorkspaceImageResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[89] + mi := &file_proto_v1_gateway_proto_msgTypes[98] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7064,7 +8020,7 @@ func (x *FsReadWorkspaceImageResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsReadWorkspaceImageResponse.ProtoReflect.Descriptor instead. func (*FsReadWorkspaceImageResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{89} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{98} } func (x *FsReadWorkspaceImageResponse) GetPath() string { @@ -7125,7 +8081,7 @@ type FsWriteTextRequest struct { func (x *FsWriteTextRequest) Reset() { *x = FsWriteTextRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[90] + mi := &file_proto_v1_gateway_proto_msgTypes[99] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7137,7 +8093,7 @@ func (x *FsWriteTextRequest) String() string { func (*FsWriteTextRequest) ProtoMessage() {} func (x *FsWriteTextRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[90] + mi := &file_proto_v1_gateway_proto_msgTypes[99] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7150,7 +8106,7 @@ func (x *FsWriteTextRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsWriteTextRequest.ProtoReflect.Descriptor instead. func (*FsWriteTextRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{90} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{99} } func (x *FsWriteTextRequest) GetWorkdir() string { @@ -7224,7 +8180,7 @@ type FsWriteTextResponse struct { func (x *FsWriteTextResponse) Reset() { *x = FsWriteTextResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[91] + mi := &file_proto_v1_gateway_proto_msgTypes[100] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7236,7 +8192,7 @@ func (x *FsWriteTextResponse) String() string { func (*FsWriteTextResponse) ProtoMessage() {} func (x *FsWriteTextResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[91] + mi := &file_proto_v1_gateway_proto_msgTypes[100] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7249,7 +8205,7 @@ func (x *FsWriteTextResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsWriteTextResponse.ProtoReflect.Descriptor instead. func (*FsWriteTextResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{91} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{100} } func (x *FsWriteTextResponse) GetPath() string { @@ -7311,7 +8267,7 @@ type FsCreateDirRequest struct { func (x *FsCreateDirRequest) Reset() { *x = FsCreateDirRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[92] + mi := &file_proto_v1_gateway_proto_msgTypes[101] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7323,7 +8279,7 @@ func (x *FsCreateDirRequest) String() string { func (*FsCreateDirRequest) ProtoMessage() {} func (x *FsCreateDirRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[92] + mi := &file_proto_v1_gateway_proto_msgTypes[101] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7336,7 +8292,7 @@ func (x *FsCreateDirRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsCreateDirRequest.ProtoReflect.Descriptor instead. func (*FsCreateDirRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{92} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{101} } func (x *FsCreateDirRequest) GetWorkdir() string { @@ -7363,7 +8319,7 @@ type FsCreateDirResponse struct { func (x *FsCreateDirResponse) Reset() { *x = FsCreateDirResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[93] + mi := &file_proto_v1_gateway_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7375,7 +8331,7 @@ func (x *FsCreateDirResponse) String() string { func (*FsCreateDirResponse) ProtoMessage() {} func (x *FsCreateDirResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[93] + mi := &file_proto_v1_gateway_proto_msgTypes[102] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7388,7 +8344,7 @@ func (x *FsCreateDirResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsCreateDirResponse.ProtoReflect.Descriptor instead. func (*FsCreateDirResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{93} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{102} } func (x *FsCreateDirResponse) GetPath() string { @@ -7416,7 +8372,7 @@ type FsRenameRequest struct { func (x *FsRenameRequest) Reset() { *x = FsRenameRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[94] + mi := &file_proto_v1_gateway_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7428,7 +8384,7 @@ func (x *FsRenameRequest) String() string { func (*FsRenameRequest) ProtoMessage() {} func (x *FsRenameRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[94] + mi := &file_proto_v1_gateway_proto_msgTypes[103] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7441,7 +8397,7 @@ func (x *FsRenameRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsRenameRequest.ProtoReflect.Descriptor instead. func (*FsRenameRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{94} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{103} } func (x *FsRenameRequest) GetWorkdir() string { @@ -7476,7 +8432,7 @@ type FsRenameResponse struct { func (x *FsRenameResponse) Reset() { *x = FsRenameResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[95] + mi := &file_proto_v1_gateway_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7488,7 +8444,7 @@ func (x *FsRenameResponse) String() string { func (*FsRenameResponse) ProtoMessage() {} func (x *FsRenameResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[95] + mi := &file_proto_v1_gateway_proto_msgTypes[104] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7501,7 +8457,7 @@ func (x *FsRenameResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsRenameResponse.ProtoReflect.Descriptor instead. func (*FsRenameResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{95} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{104} } func (x *FsRenameResponse) GetFromPath() string { @@ -7535,7 +8491,7 @@ type FsDeleteRequest struct { func (x *FsDeleteRequest) Reset() { *x = FsDeleteRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[96] + mi := &file_proto_v1_gateway_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7547,7 +8503,7 @@ func (x *FsDeleteRequest) String() string { func (*FsDeleteRequest) ProtoMessage() {} func (x *FsDeleteRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[96] + mi := &file_proto_v1_gateway_proto_msgTypes[105] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7560,7 +8516,7 @@ func (x *FsDeleteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsDeleteRequest.ProtoReflect.Descriptor instead. func (*FsDeleteRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{96} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{105} } func (x *FsDeleteRequest) GetWorkdir() string { @@ -7587,7 +8543,7 @@ type FsDeleteResponse struct { func (x *FsDeleteResponse) Reset() { *x = FsDeleteResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[97] + mi := &file_proto_v1_gateway_proto_msgTypes[106] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7599,7 +8555,7 @@ func (x *FsDeleteResponse) String() string { func (*FsDeleteResponse) ProtoMessage() {} func (x *FsDeleteResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[97] + mi := &file_proto_v1_gateway_proto_msgTypes[106] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7612,7 +8568,7 @@ func (x *FsDeleteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsDeleteResponse.ProtoReflect.Descriptor instead. func (*FsDeleteResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{97} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{106} } func (x *FsDeleteResponse) GetPath() string { @@ -7638,7 +8594,7 @@ type PingRequest struct { func (x *PingRequest) Reset() { *x = PingRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[98] + mi := &file_proto_v1_gateway_proto_msgTypes[107] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7650,7 +8606,7 @@ func (x *PingRequest) String() string { func (*PingRequest) ProtoMessage() {} func (x *PingRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[98] + mi := &file_proto_v1_gateway_proto_msgTypes[107] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7663,7 +8619,7 @@ func (x *PingRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PingRequest.ProtoReflect.Descriptor instead. func (*PingRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{98} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{107} } func (x *PingRequest) GetTimestamp() int64 { @@ -7682,7 +8638,7 @@ type PongResponse struct { func (x *PongResponse) Reset() { *x = PongResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[99] + mi := &file_proto_v1_gateway_proto_msgTypes[108] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7694,7 +8650,7 @@ func (x *PongResponse) String() string { func (*PongResponse) ProtoMessage() {} func (x *PongResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[99] + mi := &file_proto_v1_gateway_proto_msgTypes[108] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7707,7 +8663,7 @@ func (x *PongResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PongResponse.ProtoReflect.Descriptor instead. func (*PongResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{99} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{108} } func (x *PongResponse) GetTimestamp() int64 { @@ -7727,7 +8683,7 @@ type ErrorResponse struct { func (x *ErrorResponse) Reset() { *x = ErrorResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[100] + mi := &file_proto_v1_gateway_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7739,7 +8695,7 @@ func (x *ErrorResponse) String() string { func (*ErrorResponse) ProtoMessage() {} func (x *ErrorResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[100] + mi := &file_proto_v1_gateway_proto_msgTypes[109] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7752,7 +8708,7 @@ func (x *ErrorResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ErrorResponse.ProtoReflect.Descriptor instead. func (*ErrorResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{100} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{109} } func (x *ErrorResponse) GetCode() int32 { @@ -7782,7 +8738,7 @@ const file_proto_v1_gateway_proto_rawDesc = "" + "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x18\n" + "\amessage\x18\x02 \x01(\tR\amessage\x12\x1d\n" + "\n" + - "session_id\x18\x03 \x01(\tR\tsessionId\"\x85\x1b\n" + + "session_id\x18\x03 \x01(\tR\tsessionId\"\xc9\x1c\n" + "\x0fGatewayEnvelope\x12\x1d\n" + "\n" + "request_id\x18\x01 \x01(\tR\trequestId\x12\x1c\n" + @@ -7830,11 +8786,13 @@ const file_proto_v1_gateway_proto_rawDesc = "" + "\vgit_request\x18= \x01(\v2 .liveagent.gateway.v1.GitRequestH\x00R\n" + "gitRequest\x12d\n" + "\x15fs_read_editable_text\x18> \x01(\v2/.liveagent.gateway.v1.FsReadEditableTextRequestH\x00R\x12fsReadEditableText\x12j\n" + - "\x17fs_read_workspace_image\x18? \x01(\v21.liveagent.gateway.v1.FsReadWorkspaceImageRequestH\x00R\x14fsReadWorkspaceImage\x12S\n" + + "\x17fs_read_workspace_image\x18? \x01(\v21.liveagent.gateway.v1.FsReadWorkspaceImageRequestH\x00R\x14fsReadWorkspaceImage\x12F\n" + + "\fsftp_request\x18@ \x01(\v2!.liveagent.gateway.v1.SftpRequestH\x00R\vsftpRequest\x12S\n" + "\x0etunnel_control\x18C \x01(\v2*.liveagent.gateway.v1.TunnelControlRequestH\x00R\rtunnelControl\x12]\n" + "\x13tunnel_control_resp\x18D \x01(\v2+.liveagent.gateway.v1.TunnelControlResponseH\x00R\x11tunnelControlResp\x12F\n" + - "\ftunnel_frame\x18E \x01(\v2!.liveagent.gateway.v1.TunnelFrameH\x00R\vtunnelFrameB\t\n" + - "\apayload\"\xc0 \n" + + "\ftunnel_frame\x18E \x01(\v2!.liveagent.gateway.v1.TunnelFrameH\x00R\vtunnelFrame\x12z\n" + + "\x1dsettings_reset_ssh_known_host\x18H \x01(\v26.liveagent.gateway.v1.SettingsResetSshKnownHostRequestH\x00R\x19settingsResetSshKnownHostB\t\n" + + "\apayload\"\xd4\"\n" + "\rAgentEnvelope\x12\x1d\n" + "\n" + "request_id\x18\x01 \x01(\tR\trequestId\x12\x1c\n" + @@ -7880,12 +8838,16 @@ const file_proto_v1_gateway_proto_rawDesc = "" + "\x0efs_delete_resp\x18? \x01(\v2&.liveagent.gateway.v1.FsDeleteResponseH\x00R\ffsDeleteResp\x12F\n" + "\fgit_response\x18@ \x01(\v2!.liveagent.gateway.v1.GitResponseH\x00R\vgitResponse\x12n\n" + "\x1afs_read_editable_text_resp\x18A \x01(\v20.liveagent.gateway.v1.FsReadEditableTextResponseH\x00R\x16fsReadEditableTextResp\x12t\n" + - "\x1cfs_read_workspace_image_resp\x18B \x01(\v22.liveagent.gateway.v1.FsReadWorkspaceImageResponseH\x00R\x18fsReadWorkspaceImageResp\x12S\n" + + "\x1cfs_read_workspace_image_resp\x18B \x01(\v22.liveagent.gateway.v1.FsReadWorkspaceImageResponseH\x00R\x18fsReadWorkspaceImageResp\x12I\n" + + "\rsftp_response\x18I \x01(\v2\".liveagent.gateway.v1.SftpResponseH\x00R\fsftpResponse\x12@\n" + + "\n" + + "sftp_event\x18J \x01(\v2\x1f.liveagent.gateway.v1.SftpEventH\x00R\tsftpEvent\x12S\n" + "\x0etunnel_control\x18C \x01(\v2*.liveagent.gateway.v1.TunnelControlRequestH\x00R\rtunnelControl\x12]\n" + "\x13tunnel_control_resp\x18D \x01(\v2+.liveagent.gateway.v1.TunnelControlResponseH\x00R\x11tunnelControlResp\x12F\n" + "\ftunnel_frame\x18E \x01(\v2!.liveagent.gateway.v1.TunnelFrameH\x00R\vtunnelFrame\x12K\n" + "\fchat_control\x18F \x01(\v2&.liveagent.gateway.v1.ChatControlEventH\x00R\vchatControl\x12Q\n" + - "\x0eruntime_status\x18G \x01(\v2(.liveagent.gateway.v1.RuntimeStatusEventH\x00R\rruntimeStatus\x12;\n" + + "\x0eruntime_status\x18G \x01(\v2(.liveagent.gateway.v1.RuntimeStatusEventH\x00R\rruntimeStatus\x12\x84\x01\n" + + "\"settings_reset_ssh_known_host_resp\x18H \x01(\v27.liveagent.gateway.v1.SettingsResetSshKnownHostResponseH\x00R\x1dsettingsResetSshKnownHostResp\x12;\n" + "\x05error\x18c \x01(\v2#.liveagent.gateway.v1.ErrorResponseH\x00R\x05errorB\t\n" + "\apayload\"|\n" + "\x11ChatSelectedModel\x12,\n" + @@ -7982,7 +8944,7 @@ const file_proto_v1_gateway_proto_rawDesc = "" + "\targs_json\x18\x02 \x01(\tR\bargsJson\"7\n" + "\x14MemoryManageResponse\x12\x1f\n" + "\vresult_json\x18\x01 \x01(\tR\n" + - "resultJson\"\x89\x02\n" + + "resultJson\"\xb4\x03\n" + "\x0fTerminalRequest\x12\x16\n" + "\x06action\x18\x01 \x01(\tR\x06action\x12\x1d\n" + "\n" + @@ -7995,7 +8957,12 @@ const file_proto_v1_gateway_proto_rawDesc = "" + "\x04cols\x18\b \x01(\rR\x04cols\x12\x12\n" + "\x04rows\x18\t \x01(\rR\x04rows\x12\x1b\n" + "\tmax_bytes\x18\n" + - " \x01(\rR\bmaxBytes\"\xd9\x02\n" + + " \x01(\rR\bmaxBytes\x12\x1e\n" + + "\vssh_host_id\x18\v \x01(\tR\tsshHostId\x12\x1b\n" + + "\tprompt_id\x18\f \x01(\tR\bpromptId\x12#\n" + + "\rprompt_answer\x18\r \x01(\tR\fpromptAnswer\x12$\n" + + "\x0etrust_host_key\x18\x0e \x01(\bR\ftrustHostKey\x12!\n" + + "\fsftp_enabled\x18\x0f \x01(\bR\vsftpEnabled\"\xaa\x03\n" + "\x0fTerminalSession\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12(\n" + "\x10project_path_key\x18\x02 \x01(\tR\x0eprojectPathKey\x12\x10\n" + @@ -8013,11 +8980,94 @@ const file_proto_v1_gateway_proto_rawDesc = "" + "\vfinished_at\x18\v \x01(\x04R\n" + "finishedAt\x12\x1b\n" + "\texit_code\x18\f \x01(\x05R\bexitCode\x12\x18\n" + - "\arunning\x18\r \x01(\bR\arunning\"U\n" + + "\arunning\x18\r \x01(\bR\arunning\x12\x12\n" + + "\x04kind\x18\x0e \x01(\tR\x04kind\x12;\n" + + "\x03ssh\x18\x0f \x01(\v2).liveagent.gateway.v1.TerminalSshMetadataR\x03ssh\"\xca\x02\n" + + "\x13TerminalSshMetadata\x12\x17\n" + + "\ahost_id\x18\x01 \x01(\tR\x06hostId\x12\x1b\n" + + "\thost_name\x18\x02 \x01(\tR\bhostName\x12\x1a\n" + + "\busername\x18\x03 \x01(\tR\busername\x12\x12\n" + + "\x04host\x18\x04 \x01(\tR\x04host\x12\x12\n" + + "\x04port\x18\x05 \x01(\rR\x04port\x12\x1b\n" + + "\tauth_type\x18\x06 \x01(\tR\bauthType\x12\x16\n" + + "\x06status\x18\a \x01(\tR\x06status\x12+\n" + + "\x11reconnect_attempt\x18\b \x01(\rR\x10reconnectAttempt\x124\n" + + "\x16reconnect_max_attempts\x18\t \x01(\rR\x14reconnectMaxAttempts\x12!\n" + + "\fsftp_enabled\x18\n" + + " \x01(\bR\vsftpEnabled\"\xf9\x02\n" + + "\vSftpRequest\x12\x16\n" + + "\x06action\x18\x01 \x01(\tR\x06action\x12\x1d\n" + + "\n" + + "session_id\x18\x02 \x01(\tR\tsessionId\x12(\n" + + "\x10project_path_key\x18\x03 \x01(\tR\x0eprojectPathKey\x12\x18\n" + + "\aworkdir\x18\x04 \x01(\tR\aworkdir\x12\x1d\n" + + "\n" + + "local_path\x18\x05 \x01(\tR\tlocalPath\x12\x1f\n" + + "\vremote_path\x18\x06 \x01(\tR\n" + + "remotePath\x12\x1b\n" + + "\tfrom_path\x18\a \x01(\tR\bfromPath\x12\x17\n" + + "\ato_path\x18\b \x01(\tR\x06toPath\x12\x1c\n" + + "\tdirection\x18\t \x01(\tR\tdirection\x12\x1f\n" + + "\vtarget_path\x18\n" + + " \x01(\tR\n" + + "targetPath\x12\x1c\n" + + "\trecursive\x18\v \x01(\bR\trecursive\x12\x1c\n" + + "\toverwrite\x18\f \x01(\bR\toverwrite\"|\n" + + "\tSftpEntry\x12\x12\n" + + "\x04path\x18\x01 \x01(\tR\x04path\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x12\n" + + "\x04kind\x18\x03 \x01(\tR\x04kind\x12\x1d\n" + + "\n" + + "size_bytes\x18\x04 \x01(\x04R\tsizeBytes\x12\x14\n" + + "\x05mtime\x18\x05 \x01(\x04R\x05mtime\"\xee\x02\n" + + "\fSftpTransfer\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1d\n" + + "\n" + + "session_id\x18\x02 \x01(\tR\tsessionId\x12\x1c\n" + + "\tdirection\x18\x03 \x01(\tR\tdirection\x12\x16\n" + + "\x06status\x18\x04 \x01(\tR\x06status\x12\x1f\n" + + "\vsource_path\x18\x05 \x01(\tR\n" + + "sourcePath\x12\x1f\n" + + "\vtarget_path\x18\x06 \x01(\tR\n" + + "targetPath\x12!\n" + + "\fcurrent_path\x18\a \x01(\tR\vcurrentPath\x12\x1d\n" + + "\n" + + "bytes_done\x18\b \x01(\x04R\tbytesDone\x12\x1f\n" + + "\vbytes_total\x18\t \x01(\x04R\n" + + "bytesTotal\x12\x1d\n" + + "\n" + + "files_done\x18\n" + + " \x01(\rR\tfilesDone\x12\x1f\n" + + "\vfiles_total\x18\v \x01(\rR\n" + + "filesTotal\x12\x14\n" + + "\x05error\x18\f \x01(\tR\x05error\"\x84\x02\n" + + "\fSftpResponse\x12\x16\n" + + "\x06action\x18\x01 \x01(\tR\x06action\x12\x12\n" + + "\x04path\x18\x02 \x01(\tR\x04path\x129\n" + + "\aentries\x18\x03 \x03(\v2\x1f.liveagent.gateway.v1.SftpEntryR\aentries\x125\n" + + "\x05entry\x18\x04 \x01(\v2\x1f.liveagent.gateway.v1.SftpEntryR\x05entry\x12\x16\n" + + "\x06exists\x18\x05 \x01(\bR\x06exists\x12>\n" + + "\btransfer\x18\x06 \x01(\v2\".liveagent.gateway.v1.SftpTransferR\btransfer\"_\n" + + "\tSftpEvent\x12\x12\n" + + "\x04kind\x18\x01 \x01(\tR\x04kind\x12>\n" + + "\btransfer\x18\x02 \x01(\v2\".liveagent.gateway.v1.SftpTransferR\btransfer\"\x9a\x02\n" + + "\x11TerminalSshPrompt\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + + "\x04kind\x18\x02 \x01(\tR\x04kind\x12\x17\n" + + "\ahost_id\x18\x03 \x01(\tR\x06hostId\x12\x1b\n" + + "\thost_name\x18\x04 \x01(\tR\bhostName\x12\x12\n" + + "\x04host\x18\x05 \x01(\tR\x04host\x12\x12\n" + + "\x04port\x18\x06 \x01(\rR\x04port\x12\x18\n" + + "\amessage\x18\a \x01(\tR\amessage\x12-\n" + + "\x12fingerprint_sha256\x18\b \x01(\tR\x11fingerprintSha256\x12\x19\n" + + "\bkey_type\x18\t \x01(\tR\akeyType\x12\x1f\n" + + "\vanswer_echo\x18\n" + + " \x01(\bR\n" + + "answerEcho\"U\n" + "\x13TerminalShellOption\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x14\n" + "\x05label\x18\x02 \x01(\tR\x05label\x12\x18\n" + - "\acommand\x18\x03 \x01(\tR\acommand\"\xb5\x03\n" + + "\acommand\x18\x03 \x01(\tR\acommand\"\x9c\x04\n" + "\x10TerminalResponse\x12\x16\n" + "\x06action\x18\x01 \x01(\tR\x06action\x12A\n" + "\bsessions\x18\x02 \x03(\v2%.liveagent.gateway.v1.TerminalSessionR\bsessions\x12?\n" + @@ -8027,7 +9077,12 @@ const file_proto_v1_gateway_proto_rawDesc = "" + "\rshell_options\x18\x06 \x03(\v2).liveagent.gateway.v1.TerminalShellOptionR\fshellOptions\x12#\n" + "\rdefault_shell\x18\a \x01(\tR\fdefaultShell\x12.\n" + "\x13output_start_offset\x18\b \x01(\x04R\x11outputStartOffset\x12*\n" + - "\x11output_end_offset\x18\t \x01(\x04R\x0foutputEndOffset\"\x9d\x02\n" + + "\x11output_end_offset\x18\t \x01(\x04R\x0foutputEndOffset\x12F\n" + + "\n" + + "ssh_prompt\x18\n" + + " \x01(\v2'.liveagent.gateway.v1.TerminalSshPromptR\tsshPrompt\x12\x1d\n" + + "\n" + + "latency_ms\x18\v \x01(\rR\tlatencyMs\"\x9d\x02\n" + "\rTerminalEvent\x12\x12\n" + "\x04kind\x18\x01 \x01(\tR\x04kind\x12\x1d\n" + "\n" + @@ -8206,7 +9261,12 @@ const file_proto_v1_gateway_proto_rawDesc = "" + "\rsettings_json\x18\x01 \x01(\tR\fsettingsJson\"N\n" + "\x16SettingsUpdateResponse\x12\x1a\n" + "\baccepted\x18\x01 \x01(\bR\baccepted\x12\x18\n" + - "\amessage\x18\x02 \x01(\tR\amessage\"8\n" + + "\amessage\x18\x02 \x01(\tR\amessage\"J\n" + + " SettingsResetSshKnownHostRequest\x12\x12\n" + + "\x04host\x18\x01 \x01(\tR\x04host\x12\x12\n" + + "\x04port\x18\x02 \x01(\rR\x04port\"=\n" + + "!SettingsResetSshKnownHostResponse\x12\x18\n" + + "\adeleted\x18\x01 \x01(\rR\adeleted\"8\n" + "\x11SettingsSyncEvent\x12#\n" + "\rsettings_json\x18\x01 \x01(\tR\fsettingsJson\"\x17\n" + "\x15SkillFilesListRequest\"g\n" + @@ -8386,235 +9446,255 @@ func file_proto_v1_gateway_proto_rawDescGZIP() []byte { } var file_proto_v1_gateway_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_proto_v1_gateway_proto_msgTypes = make([]protoimpl.MessageInfo, 101) +var file_proto_v1_gateway_proto_msgTypes = make([]protoimpl.MessageInfo, 110) var file_proto_v1_gateway_proto_goTypes = []any{ - (TunnelFrameKind)(0), // 0: liveagent.gateway.v1.TunnelFrameKind - (ChatEvent_ChatEventType)(0), // 1: liveagent.gateway.v1.ChatEvent.ChatEventType - (*AuthRequest)(nil), // 2: liveagent.gateway.v1.AuthRequest - (*AuthResponse)(nil), // 3: liveagent.gateway.v1.AuthResponse - (*GatewayEnvelope)(nil), // 4: liveagent.gateway.v1.GatewayEnvelope - (*AgentEnvelope)(nil), // 5: liveagent.gateway.v1.AgentEnvelope - (*ChatSelectedModel)(nil), // 6: liveagent.gateway.v1.ChatSelectedModel - (*ChatRuntimeControls)(nil), // 7: liveagent.gateway.v1.ChatRuntimeControls - (*ChatUploadedFile)(nil), // 8: liveagent.gateway.v1.ChatUploadedFile - (*UploadReadableFile)(nil), // 9: liveagent.gateway.v1.UploadReadableFile - (*UploadReadableFilesRequest)(nil), // 10: liveagent.gateway.v1.UploadReadableFilesRequest - (*UploadReadableFilesResponse)(nil), // 11: liveagent.gateway.v1.UploadReadableFilesResponse - (*UploadedImagePreviewRequest)(nil), // 12: liveagent.gateway.v1.UploadedImagePreviewRequest - (*UploadedImagePreviewResponse)(nil), // 13: liveagent.gateway.v1.UploadedImagePreviewResponse - (*TunnelControlRequest)(nil), // 14: liveagent.gateway.v1.TunnelControlRequest - (*TunnelControlResponse)(nil), // 15: liveagent.gateway.v1.TunnelControlResponse - (*TunnelSummary)(nil), // 16: liveagent.gateway.v1.TunnelSummary - (*TunnelHeader)(nil), // 17: liveagent.gateway.v1.TunnelHeader - (*TunnelFrame)(nil), // 18: liveagent.gateway.v1.TunnelFrame - (*MemoryManageRequest)(nil), // 19: liveagent.gateway.v1.MemoryManageRequest - (*MemoryManageResponse)(nil), // 20: liveagent.gateway.v1.MemoryManageResponse - (*TerminalRequest)(nil), // 21: liveagent.gateway.v1.TerminalRequest - (*TerminalSession)(nil), // 22: liveagent.gateway.v1.TerminalSession - (*TerminalShellOption)(nil), // 23: liveagent.gateway.v1.TerminalShellOption - (*TerminalResponse)(nil), // 24: liveagent.gateway.v1.TerminalResponse - (*TerminalEvent)(nil), // 25: liveagent.gateway.v1.TerminalEvent - (*GitRequest)(nil), // 26: liveagent.gateway.v1.GitRequest - (*GitResponse)(nil), // 27: liveagent.gateway.v1.GitResponse - (*ChatRequest)(nil), // 28: liveagent.gateway.v1.ChatRequest - (*CancelChatRequest)(nil), // 29: liveagent.gateway.v1.CancelChatRequest - (*ChatEvent)(nil), // 30: liveagent.gateway.v1.ChatEvent - (*ChatControlEvent)(nil), // 31: liveagent.gateway.v1.ChatControlEvent - (*RuntimeStatusEvent)(nil), // 32: liveagent.gateway.v1.RuntimeStatusEvent - (*CronManageRequest)(nil), // 33: liveagent.gateway.v1.CronManageRequest - (*CronManageResponse)(nil), // 34: liveagent.gateway.v1.CronManageResponse - (*HistoryListRequest)(nil), // 35: liveagent.gateway.v1.HistoryListRequest - (*HistoryListResponse)(nil), // 36: liveagent.gateway.v1.HistoryListResponse - (*ConversationSummary)(nil), // 37: liveagent.gateway.v1.ConversationSummary - (*HistoryGetRequest)(nil), // 38: liveagent.gateway.v1.HistoryGetRequest - (*HistoryGetResponse)(nil), // 39: liveagent.gateway.v1.HistoryGetResponse - (*HistoryRenameRequest)(nil), // 40: liveagent.gateway.v1.HistoryRenameRequest - (*HistoryRenameResponse)(nil), // 41: liveagent.gateway.v1.HistoryRenameResponse - (*HistoryPinRequest)(nil), // 42: liveagent.gateway.v1.HistoryPinRequest - (*HistoryPinResponse)(nil), // 43: liveagent.gateway.v1.HistoryPinResponse - (*HistoryShareStatus)(nil), // 44: liveagent.gateway.v1.HistoryShareStatus - (*HistoryShareGetRequest)(nil), // 45: liveagent.gateway.v1.HistoryShareGetRequest - (*HistoryShareGetResponse)(nil), // 46: liveagent.gateway.v1.HistoryShareGetResponse - (*HistoryShareSetRequest)(nil), // 47: liveagent.gateway.v1.HistoryShareSetRequest - (*HistoryShareSetResponse)(nil), // 48: liveagent.gateway.v1.HistoryShareSetResponse - (*HistoryShareResolveRequest)(nil), // 49: liveagent.gateway.v1.HistoryShareResolveRequest - (*HistoryShareResolveResponse)(nil), // 50: liveagent.gateway.v1.HistoryShareResolveResponse - (*HistoryWorkdirsRequest)(nil), // 51: liveagent.gateway.v1.HistoryWorkdirsRequest - (*HistoryWorkdirSummary)(nil), // 52: liveagent.gateway.v1.HistoryWorkdirSummary - (*HistoryWorkdirsResponse)(nil), // 53: liveagent.gateway.v1.HistoryWorkdirsResponse - (*HistoryDeleteRequest)(nil), // 54: liveagent.gateway.v1.HistoryDeleteRequest - (*HistoryDeleteResponse)(nil), // 55: liveagent.gateway.v1.HistoryDeleteResponse - (*HistoryTruncateRequest)(nil), // 56: liveagent.gateway.v1.HistoryTruncateRequest - (*HistoryTruncateResponse)(nil), // 57: liveagent.gateway.v1.HistoryTruncateResponse - (*HistorySyncEvent)(nil), // 58: liveagent.gateway.v1.HistorySyncEvent - (*ProviderListRequest)(nil), // 59: liveagent.gateway.v1.ProviderListRequest - (*ProviderListResponse)(nil), // 60: liveagent.gateway.v1.ProviderListResponse - (*SettingsGetRequest)(nil), // 61: liveagent.gateway.v1.SettingsGetRequest - (*SettingsGetResponse)(nil), // 62: liveagent.gateway.v1.SettingsGetResponse - (*SettingsUpdateRequest)(nil), // 63: liveagent.gateway.v1.SettingsUpdateRequest - (*SettingsUpdateResponse)(nil), // 64: liveagent.gateway.v1.SettingsUpdateResponse - (*SettingsSyncEvent)(nil), // 65: liveagent.gateway.v1.SettingsSyncEvent - (*SkillFilesListRequest)(nil), // 66: liveagent.gateway.v1.SkillFilesListRequest - (*SkillFilesListResponse)(nil), // 67: liveagent.gateway.v1.SkillFilesListResponse - (*SkillMetadataReadRequest)(nil), // 68: liveagent.gateway.v1.SkillMetadataReadRequest - (*SkillMetadataReadResponse)(nil), // 69: liveagent.gateway.v1.SkillMetadataReadResponse - (*SkillTextReadRequest)(nil), // 70: liveagent.gateway.v1.SkillTextReadRequest - (*SkillTextReadResponse)(nil), // 71: liveagent.gateway.v1.SkillTextReadResponse - (*SkillManageRequest)(nil), // 72: liveagent.gateway.v1.SkillManageRequest - (*SkillManageResponse)(nil), // 73: liveagent.gateway.v1.SkillManageResponse - (*FileMentionListRequest)(nil), // 74: liveagent.gateway.v1.FileMentionListRequest - (*FileMentionEntry)(nil), // 75: liveagent.gateway.v1.FileMentionEntry - (*FileMentionListResponse)(nil), // 76: liveagent.gateway.v1.FileMentionListResponse - (*FsRoot)(nil), // 77: liveagent.gateway.v1.FsRoot - (*FsRootsRequest)(nil), // 78: liveagent.gateway.v1.FsRootsRequest - (*FsRootsResponse)(nil), // 79: liveagent.gateway.v1.FsRootsResponse - (*FsListDirsRequest)(nil), // 80: liveagent.gateway.v1.FsListDirsRequest - (*FsDirEntry)(nil), // 81: liveagent.gateway.v1.FsDirEntry - (*FsListDirsResponse)(nil), // 82: liveagent.gateway.v1.FsListDirsResponse - (*FsCreateProjectFolderRequest)(nil), // 83: liveagent.gateway.v1.FsCreateProjectFolderRequest - (*FsCreateProjectFolderResponse)(nil), // 84: liveagent.gateway.v1.FsCreateProjectFolderResponse - (*FsListRequest)(nil), // 85: liveagent.gateway.v1.FsListRequest - (*FsListEntry)(nil), // 86: liveagent.gateway.v1.FsListEntry - (*FsListResponse)(nil), // 87: liveagent.gateway.v1.FsListResponse - (*FsReadEditableTextRequest)(nil), // 88: liveagent.gateway.v1.FsReadEditableTextRequest - (*FsReadEditableTextResponse)(nil), // 89: liveagent.gateway.v1.FsReadEditableTextResponse - (*FsReadWorkspaceImageRequest)(nil), // 90: liveagent.gateway.v1.FsReadWorkspaceImageRequest - (*FsReadWorkspaceImageResponse)(nil), // 91: liveagent.gateway.v1.FsReadWorkspaceImageResponse - (*FsWriteTextRequest)(nil), // 92: liveagent.gateway.v1.FsWriteTextRequest - (*FsWriteTextResponse)(nil), // 93: liveagent.gateway.v1.FsWriteTextResponse - (*FsCreateDirRequest)(nil), // 94: liveagent.gateway.v1.FsCreateDirRequest - (*FsCreateDirResponse)(nil), // 95: liveagent.gateway.v1.FsCreateDirResponse - (*FsRenameRequest)(nil), // 96: liveagent.gateway.v1.FsRenameRequest - (*FsRenameResponse)(nil), // 97: liveagent.gateway.v1.FsRenameResponse - (*FsDeleteRequest)(nil), // 98: liveagent.gateway.v1.FsDeleteRequest - (*FsDeleteResponse)(nil), // 99: liveagent.gateway.v1.FsDeleteResponse - (*PingRequest)(nil), // 100: liveagent.gateway.v1.PingRequest - (*PongResponse)(nil), // 101: liveagent.gateway.v1.PongResponse - (*ErrorResponse)(nil), // 102: liveagent.gateway.v1.ErrorResponse + (TunnelFrameKind)(0), // 0: liveagent.gateway.v1.TunnelFrameKind + (ChatEvent_ChatEventType)(0), // 1: liveagent.gateway.v1.ChatEvent.ChatEventType + (*AuthRequest)(nil), // 2: liveagent.gateway.v1.AuthRequest + (*AuthResponse)(nil), // 3: liveagent.gateway.v1.AuthResponse + (*GatewayEnvelope)(nil), // 4: liveagent.gateway.v1.GatewayEnvelope + (*AgentEnvelope)(nil), // 5: liveagent.gateway.v1.AgentEnvelope + (*ChatSelectedModel)(nil), // 6: liveagent.gateway.v1.ChatSelectedModel + (*ChatRuntimeControls)(nil), // 7: liveagent.gateway.v1.ChatRuntimeControls + (*ChatUploadedFile)(nil), // 8: liveagent.gateway.v1.ChatUploadedFile + (*UploadReadableFile)(nil), // 9: liveagent.gateway.v1.UploadReadableFile + (*UploadReadableFilesRequest)(nil), // 10: liveagent.gateway.v1.UploadReadableFilesRequest + (*UploadReadableFilesResponse)(nil), // 11: liveagent.gateway.v1.UploadReadableFilesResponse + (*UploadedImagePreviewRequest)(nil), // 12: liveagent.gateway.v1.UploadedImagePreviewRequest + (*UploadedImagePreviewResponse)(nil), // 13: liveagent.gateway.v1.UploadedImagePreviewResponse + (*TunnelControlRequest)(nil), // 14: liveagent.gateway.v1.TunnelControlRequest + (*TunnelControlResponse)(nil), // 15: liveagent.gateway.v1.TunnelControlResponse + (*TunnelSummary)(nil), // 16: liveagent.gateway.v1.TunnelSummary + (*TunnelHeader)(nil), // 17: liveagent.gateway.v1.TunnelHeader + (*TunnelFrame)(nil), // 18: liveagent.gateway.v1.TunnelFrame + (*MemoryManageRequest)(nil), // 19: liveagent.gateway.v1.MemoryManageRequest + (*MemoryManageResponse)(nil), // 20: liveagent.gateway.v1.MemoryManageResponse + (*TerminalRequest)(nil), // 21: liveagent.gateway.v1.TerminalRequest + (*TerminalSession)(nil), // 22: liveagent.gateway.v1.TerminalSession + (*TerminalSshMetadata)(nil), // 23: liveagent.gateway.v1.TerminalSshMetadata + (*SftpRequest)(nil), // 24: liveagent.gateway.v1.SftpRequest + (*SftpEntry)(nil), // 25: liveagent.gateway.v1.SftpEntry + (*SftpTransfer)(nil), // 26: liveagent.gateway.v1.SftpTransfer + (*SftpResponse)(nil), // 27: liveagent.gateway.v1.SftpResponse + (*SftpEvent)(nil), // 28: liveagent.gateway.v1.SftpEvent + (*TerminalSshPrompt)(nil), // 29: liveagent.gateway.v1.TerminalSshPrompt + (*TerminalShellOption)(nil), // 30: liveagent.gateway.v1.TerminalShellOption + (*TerminalResponse)(nil), // 31: liveagent.gateway.v1.TerminalResponse + (*TerminalEvent)(nil), // 32: liveagent.gateway.v1.TerminalEvent + (*GitRequest)(nil), // 33: liveagent.gateway.v1.GitRequest + (*GitResponse)(nil), // 34: liveagent.gateway.v1.GitResponse + (*ChatRequest)(nil), // 35: liveagent.gateway.v1.ChatRequest + (*CancelChatRequest)(nil), // 36: liveagent.gateway.v1.CancelChatRequest + (*ChatEvent)(nil), // 37: liveagent.gateway.v1.ChatEvent + (*ChatControlEvent)(nil), // 38: liveagent.gateway.v1.ChatControlEvent + (*RuntimeStatusEvent)(nil), // 39: liveagent.gateway.v1.RuntimeStatusEvent + (*CronManageRequest)(nil), // 40: liveagent.gateway.v1.CronManageRequest + (*CronManageResponse)(nil), // 41: liveagent.gateway.v1.CronManageResponse + (*HistoryListRequest)(nil), // 42: liveagent.gateway.v1.HistoryListRequest + (*HistoryListResponse)(nil), // 43: liveagent.gateway.v1.HistoryListResponse + (*ConversationSummary)(nil), // 44: liveagent.gateway.v1.ConversationSummary + (*HistoryGetRequest)(nil), // 45: liveagent.gateway.v1.HistoryGetRequest + (*HistoryGetResponse)(nil), // 46: liveagent.gateway.v1.HistoryGetResponse + (*HistoryRenameRequest)(nil), // 47: liveagent.gateway.v1.HistoryRenameRequest + (*HistoryRenameResponse)(nil), // 48: liveagent.gateway.v1.HistoryRenameResponse + (*HistoryPinRequest)(nil), // 49: liveagent.gateway.v1.HistoryPinRequest + (*HistoryPinResponse)(nil), // 50: liveagent.gateway.v1.HistoryPinResponse + (*HistoryShareStatus)(nil), // 51: liveagent.gateway.v1.HistoryShareStatus + (*HistoryShareGetRequest)(nil), // 52: liveagent.gateway.v1.HistoryShareGetRequest + (*HistoryShareGetResponse)(nil), // 53: liveagent.gateway.v1.HistoryShareGetResponse + (*HistoryShareSetRequest)(nil), // 54: liveagent.gateway.v1.HistoryShareSetRequest + (*HistoryShareSetResponse)(nil), // 55: liveagent.gateway.v1.HistoryShareSetResponse + (*HistoryShareResolveRequest)(nil), // 56: liveagent.gateway.v1.HistoryShareResolveRequest + (*HistoryShareResolveResponse)(nil), // 57: liveagent.gateway.v1.HistoryShareResolveResponse + (*HistoryWorkdirsRequest)(nil), // 58: liveagent.gateway.v1.HistoryWorkdirsRequest + (*HistoryWorkdirSummary)(nil), // 59: liveagent.gateway.v1.HistoryWorkdirSummary + (*HistoryWorkdirsResponse)(nil), // 60: liveagent.gateway.v1.HistoryWorkdirsResponse + (*HistoryDeleteRequest)(nil), // 61: liveagent.gateway.v1.HistoryDeleteRequest + (*HistoryDeleteResponse)(nil), // 62: liveagent.gateway.v1.HistoryDeleteResponse + (*HistoryTruncateRequest)(nil), // 63: liveagent.gateway.v1.HistoryTruncateRequest + (*HistoryTruncateResponse)(nil), // 64: liveagent.gateway.v1.HistoryTruncateResponse + (*HistorySyncEvent)(nil), // 65: liveagent.gateway.v1.HistorySyncEvent + (*ProviderListRequest)(nil), // 66: liveagent.gateway.v1.ProviderListRequest + (*ProviderListResponse)(nil), // 67: liveagent.gateway.v1.ProviderListResponse + (*SettingsGetRequest)(nil), // 68: liveagent.gateway.v1.SettingsGetRequest + (*SettingsGetResponse)(nil), // 69: liveagent.gateway.v1.SettingsGetResponse + (*SettingsUpdateRequest)(nil), // 70: liveagent.gateway.v1.SettingsUpdateRequest + (*SettingsUpdateResponse)(nil), // 71: liveagent.gateway.v1.SettingsUpdateResponse + (*SettingsResetSshKnownHostRequest)(nil), // 72: liveagent.gateway.v1.SettingsResetSshKnownHostRequest + (*SettingsResetSshKnownHostResponse)(nil), // 73: liveagent.gateway.v1.SettingsResetSshKnownHostResponse + (*SettingsSyncEvent)(nil), // 74: liveagent.gateway.v1.SettingsSyncEvent + (*SkillFilesListRequest)(nil), // 75: liveagent.gateway.v1.SkillFilesListRequest + (*SkillFilesListResponse)(nil), // 76: liveagent.gateway.v1.SkillFilesListResponse + (*SkillMetadataReadRequest)(nil), // 77: liveagent.gateway.v1.SkillMetadataReadRequest + (*SkillMetadataReadResponse)(nil), // 78: liveagent.gateway.v1.SkillMetadataReadResponse + (*SkillTextReadRequest)(nil), // 79: liveagent.gateway.v1.SkillTextReadRequest + (*SkillTextReadResponse)(nil), // 80: liveagent.gateway.v1.SkillTextReadResponse + (*SkillManageRequest)(nil), // 81: liveagent.gateway.v1.SkillManageRequest + (*SkillManageResponse)(nil), // 82: liveagent.gateway.v1.SkillManageResponse + (*FileMentionListRequest)(nil), // 83: liveagent.gateway.v1.FileMentionListRequest + (*FileMentionEntry)(nil), // 84: liveagent.gateway.v1.FileMentionEntry + (*FileMentionListResponse)(nil), // 85: liveagent.gateway.v1.FileMentionListResponse + (*FsRoot)(nil), // 86: liveagent.gateway.v1.FsRoot + (*FsRootsRequest)(nil), // 87: liveagent.gateway.v1.FsRootsRequest + (*FsRootsResponse)(nil), // 88: liveagent.gateway.v1.FsRootsResponse + (*FsListDirsRequest)(nil), // 89: liveagent.gateway.v1.FsListDirsRequest + (*FsDirEntry)(nil), // 90: liveagent.gateway.v1.FsDirEntry + (*FsListDirsResponse)(nil), // 91: liveagent.gateway.v1.FsListDirsResponse + (*FsCreateProjectFolderRequest)(nil), // 92: liveagent.gateway.v1.FsCreateProjectFolderRequest + (*FsCreateProjectFolderResponse)(nil), // 93: liveagent.gateway.v1.FsCreateProjectFolderResponse + (*FsListRequest)(nil), // 94: liveagent.gateway.v1.FsListRequest + (*FsListEntry)(nil), // 95: liveagent.gateway.v1.FsListEntry + (*FsListResponse)(nil), // 96: liveagent.gateway.v1.FsListResponse + (*FsReadEditableTextRequest)(nil), // 97: liveagent.gateway.v1.FsReadEditableTextRequest + (*FsReadEditableTextResponse)(nil), // 98: liveagent.gateway.v1.FsReadEditableTextResponse + (*FsReadWorkspaceImageRequest)(nil), // 99: liveagent.gateway.v1.FsReadWorkspaceImageRequest + (*FsReadWorkspaceImageResponse)(nil), // 100: liveagent.gateway.v1.FsReadWorkspaceImageResponse + (*FsWriteTextRequest)(nil), // 101: liveagent.gateway.v1.FsWriteTextRequest + (*FsWriteTextResponse)(nil), // 102: liveagent.gateway.v1.FsWriteTextResponse + (*FsCreateDirRequest)(nil), // 103: liveagent.gateway.v1.FsCreateDirRequest + (*FsCreateDirResponse)(nil), // 104: liveagent.gateway.v1.FsCreateDirResponse + (*FsRenameRequest)(nil), // 105: liveagent.gateway.v1.FsRenameRequest + (*FsRenameResponse)(nil), // 106: liveagent.gateway.v1.FsRenameResponse + (*FsDeleteRequest)(nil), // 107: liveagent.gateway.v1.FsDeleteRequest + (*FsDeleteResponse)(nil), // 108: liveagent.gateway.v1.FsDeleteResponse + (*PingRequest)(nil), // 109: liveagent.gateway.v1.PingRequest + (*PongResponse)(nil), // 110: liveagent.gateway.v1.PongResponse + (*ErrorResponse)(nil), // 111: liveagent.gateway.v1.ErrorResponse } var file_proto_v1_gateway_proto_depIdxs = []int32{ - 28, // 0: liveagent.gateway.v1.GatewayEnvelope.chat_request:type_name -> liveagent.gateway.v1.ChatRequest - 29, // 1: liveagent.gateway.v1.GatewayEnvelope.cancel_chat:type_name -> liveagent.gateway.v1.CancelChatRequest - 33, // 2: liveagent.gateway.v1.GatewayEnvelope.cron_manage:type_name -> liveagent.gateway.v1.CronManageRequest - 35, // 3: liveagent.gateway.v1.GatewayEnvelope.history_list:type_name -> liveagent.gateway.v1.HistoryListRequest - 38, // 4: liveagent.gateway.v1.GatewayEnvelope.history_get:type_name -> liveagent.gateway.v1.HistoryGetRequest - 40, // 5: liveagent.gateway.v1.GatewayEnvelope.history_rename:type_name -> liveagent.gateway.v1.HistoryRenameRequest - 54, // 6: liveagent.gateway.v1.GatewayEnvelope.history_delete:type_name -> liveagent.gateway.v1.HistoryDeleteRequest - 56, // 7: liveagent.gateway.v1.GatewayEnvelope.history_truncate:type_name -> liveagent.gateway.v1.HistoryTruncateRequest - 42, // 8: liveagent.gateway.v1.GatewayEnvelope.history_pin:type_name -> liveagent.gateway.v1.HistoryPinRequest - 45, // 9: liveagent.gateway.v1.GatewayEnvelope.history_share_get:type_name -> liveagent.gateway.v1.HistoryShareGetRequest - 47, // 10: liveagent.gateway.v1.GatewayEnvelope.history_share_set:type_name -> liveagent.gateway.v1.HistoryShareSetRequest - 49, // 11: liveagent.gateway.v1.GatewayEnvelope.history_share_resolve:type_name -> liveagent.gateway.v1.HistoryShareResolveRequest - 51, // 12: liveagent.gateway.v1.GatewayEnvelope.history_workdirs:type_name -> liveagent.gateway.v1.HistoryWorkdirsRequest - 59, // 13: liveagent.gateway.v1.GatewayEnvelope.provider_list:type_name -> liveagent.gateway.v1.ProviderListRequest - 61, // 14: liveagent.gateway.v1.GatewayEnvelope.settings_get:type_name -> liveagent.gateway.v1.SettingsGetRequest - 63, // 15: liveagent.gateway.v1.GatewayEnvelope.settings_update:type_name -> liveagent.gateway.v1.SettingsUpdateRequest - 66, // 16: liveagent.gateway.v1.GatewayEnvelope.skill_files_list:type_name -> liveagent.gateway.v1.SkillFilesListRequest - 68, // 17: liveagent.gateway.v1.GatewayEnvelope.skill_metadata_read:type_name -> liveagent.gateway.v1.SkillMetadataReadRequest - 70, // 18: liveagent.gateway.v1.GatewayEnvelope.skill_text_read:type_name -> liveagent.gateway.v1.SkillTextReadRequest - 74, // 19: liveagent.gateway.v1.GatewayEnvelope.file_mention_list:type_name -> liveagent.gateway.v1.FileMentionListRequest + 35, // 0: liveagent.gateway.v1.GatewayEnvelope.chat_request:type_name -> liveagent.gateway.v1.ChatRequest + 36, // 1: liveagent.gateway.v1.GatewayEnvelope.cancel_chat:type_name -> liveagent.gateway.v1.CancelChatRequest + 40, // 2: liveagent.gateway.v1.GatewayEnvelope.cron_manage:type_name -> liveagent.gateway.v1.CronManageRequest + 42, // 3: liveagent.gateway.v1.GatewayEnvelope.history_list:type_name -> liveagent.gateway.v1.HistoryListRequest + 45, // 4: liveagent.gateway.v1.GatewayEnvelope.history_get:type_name -> liveagent.gateway.v1.HistoryGetRequest + 47, // 5: liveagent.gateway.v1.GatewayEnvelope.history_rename:type_name -> liveagent.gateway.v1.HistoryRenameRequest + 61, // 6: liveagent.gateway.v1.GatewayEnvelope.history_delete:type_name -> liveagent.gateway.v1.HistoryDeleteRequest + 63, // 7: liveagent.gateway.v1.GatewayEnvelope.history_truncate:type_name -> liveagent.gateway.v1.HistoryTruncateRequest + 49, // 8: liveagent.gateway.v1.GatewayEnvelope.history_pin:type_name -> liveagent.gateway.v1.HistoryPinRequest + 52, // 9: liveagent.gateway.v1.GatewayEnvelope.history_share_get:type_name -> liveagent.gateway.v1.HistoryShareGetRequest + 54, // 10: liveagent.gateway.v1.GatewayEnvelope.history_share_set:type_name -> liveagent.gateway.v1.HistoryShareSetRequest + 56, // 11: liveagent.gateway.v1.GatewayEnvelope.history_share_resolve:type_name -> liveagent.gateway.v1.HistoryShareResolveRequest + 58, // 12: liveagent.gateway.v1.GatewayEnvelope.history_workdirs:type_name -> liveagent.gateway.v1.HistoryWorkdirsRequest + 66, // 13: liveagent.gateway.v1.GatewayEnvelope.provider_list:type_name -> liveagent.gateway.v1.ProviderListRequest + 68, // 14: liveagent.gateway.v1.GatewayEnvelope.settings_get:type_name -> liveagent.gateway.v1.SettingsGetRequest + 70, // 15: liveagent.gateway.v1.GatewayEnvelope.settings_update:type_name -> liveagent.gateway.v1.SettingsUpdateRequest + 75, // 16: liveagent.gateway.v1.GatewayEnvelope.skill_files_list:type_name -> liveagent.gateway.v1.SkillFilesListRequest + 77, // 17: liveagent.gateway.v1.GatewayEnvelope.skill_metadata_read:type_name -> liveagent.gateway.v1.SkillMetadataReadRequest + 79, // 18: liveagent.gateway.v1.GatewayEnvelope.skill_text_read:type_name -> liveagent.gateway.v1.SkillTextReadRequest + 83, // 19: liveagent.gateway.v1.GatewayEnvelope.file_mention_list:type_name -> liveagent.gateway.v1.FileMentionListRequest 10, // 20: liveagent.gateway.v1.GatewayEnvelope.upload_readable_files:type_name -> liveagent.gateway.v1.UploadReadableFilesRequest - 78, // 21: liveagent.gateway.v1.GatewayEnvelope.fs_roots:type_name -> liveagent.gateway.v1.FsRootsRequest - 80, // 22: liveagent.gateway.v1.GatewayEnvelope.fs_list_dirs:type_name -> liveagent.gateway.v1.FsListDirsRequest - 100, // 23: liveagent.gateway.v1.GatewayEnvelope.ping:type_name -> liveagent.gateway.v1.PingRequest + 87, // 21: liveagent.gateway.v1.GatewayEnvelope.fs_roots:type_name -> liveagent.gateway.v1.FsRootsRequest + 89, // 22: liveagent.gateway.v1.GatewayEnvelope.fs_list_dirs:type_name -> liveagent.gateway.v1.FsListDirsRequest + 109, // 23: liveagent.gateway.v1.GatewayEnvelope.ping:type_name -> liveagent.gateway.v1.PingRequest 12, // 24: liveagent.gateway.v1.GatewayEnvelope.uploaded_image_preview:type_name -> liveagent.gateway.v1.UploadedImagePreviewRequest 19, // 25: liveagent.gateway.v1.GatewayEnvelope.memory_manage:type_name -> liveagent.gateway.v1.MemoryManageRequest - 72, // 26: liveagent.gateway.v1.GatewayEnvelope.skill_manage:type_name -> liveagent.gateway.v1.SkillManageRequest - 83, // 27: liveagent.gateway.v1.GatewayEnvelope.fs_create_project_folder:type_name -> liveagent.gateway.v1.FsCreateProjectFolderRequest + 81, // 26: liveagent.gateway.v1.GatewayEnvelope.skill_manage:type_name -> liveagent.gateway.v1.SkillManageRequest + 92, // 27: liveagent.gateway.v1.GatewayEnvelope.fs_create_project_folder:type_name -> liveagent.gateway.v1.FsCreateProjectFolderRequest 21, // 28: liveagent.gateway.v1.GatewayEnvelope.terminal_request:type_name -> liveagent.gateway.v1.TerminalRequest - 85, // 29: liveagent.gateway.v1.GatewayEnvelope.fs_list:type_name -> liveagent.gateway.v1.FsListRequest - 92, // 30: liveagent.gateway.v1.GatewayEnvelope.fs_write_text:type_name -> liveagent.gateway.v1.FsWriteTextRequest - 94, // 31: liveagent.gateway.v1.GatewayEnvelope.fs_create_dir:type_name -> liveagent.gateway.v1.FsCreateDirRequest - 96, // 32: liveagent.gateway.v1.GatewayEnvelope.fs_rename:type_name -> liveagent.gateway.v1.FsRenameRequest - 98, // 33: liveagent.gateway.v1.GatewayEnvelope.fs_delete:type_name -> liveagent.gateway.v1.FsDeleteRequest - 26, // 34: liveagent.gateway.v1.GatewayEnvelope.git_request:type_name -> liveagent.gateway.v1.GitRequest - 88, // 35: liveagent.gateway.v1.GatewayEnvelope.fs_read_editable_text:type_name -> liveagent.gateway.v1.FsReadEditableTextRequest - 90, // 36: liveagent.gateway.v1.GatewayEnvelope.fs_read_workspace_image:type_name -> liveagent.gateway.v1.FsReadWorkspaceImageRequest - 14, // 37: liveagent.gateway.v1.GatewayEnvelope.tunnel_control:type_name -> liveagent.gateway.v1.TunnelControlRequest - 15, // 38: liveagent.gateway.v1.GatewayEnvelope.tunnel_control_resp:type_name -> liveagent.gateway.v1.TunnelControlResponse - 18, // 39: liveagent.gateway.v1.GatewayEnvelope.tunnel_frame:type_name -> liveagent.gateway.v1.TunnelFrame - 30, // 40: liveagent.gateway.v1.AgentEnvelope.chat_event:type_name -> liveagent.gateway.v1.ChatEvent - 34, // 41: liveagent.gateway.v1.AgentEnvelope.cron_manage_resp:type_name -> liveagent.gateway.v1.CronManageResponse - 36, // 42: liveagent.gateway.v1.AgentEnvelope.history_list_resp:type_name -> liveagent.gateway.v1.HistoryListResponse - 39, // 43: liveagent.gateway.v1.AgentEnvelope.history_get_resp:type_name -> liveagent.gateway.v1.HistoryGetResponse - 41, // 44: liveagent.gateway.v1.AgentEnvelope.history_rename_resp:type_name -> liveagent.gateway.v1.HistoryRenameResponse - 55, // 45: liveagent.gateway.v1.AgentEnvelope.history_delete_resp:type_name -> liveagent.gateway.v1.HistoryDeleteResponse - 58, // 46: liveagent.gateway.v1.AgentEnvelope.history_sync:type_name -> liveagent.gateway.v1.HistorySyncEvent - 57, // 47: liveagent.gateway.v1.AgentEnvelope.history_truncate_resp:type_name -> liveagent.gateway.v1.HistoryTruncateResponse - 43, // 48: liveagent.gateway.v1.AgentEnvelope.history_pin_resp:type_name -> liveagent.gateway.v1.HistoryPinResponse - 46, // 49: liveagent.gateway.v1.AgentEnvelope.history_share_get_resp:type_name -> liveagent.gateway.v1.HistoryShareGetResponse - 48, // 50: liveagent.gateway.v1.AgentEnvelope.history_share_set_resp:type_name -> liveagent.gateway.v1.HistoryShareSetResponse - 50, // 51: liveagent.gateway.v1.AgentEnvelope.history_share_resolve_resp:type_name -> liveagent.gateway.v1.HistoryShareResolveResponse - 53, // 52: liveagent.gateway.v1.AgentEnvelope.history_workdirs_resp:type_name -> liveagent.gateway.v1.HistoryWorkdirsResponse - 60, // 53: liveagent.gateway.v1.AgentEnvelope.provider_list_resp:type_name -> liveagent.gateway.v1.ProviderListResponse - 62, // 54: liveagent.gateway.v1.AgentEnvelope.settings_get_resp:type_name -> liveagent.gateway.v1.SettingsGetResponse - 64, // 55: liveagent.gateway.v1.AgentEnvelope.settings_update_resp:type_name -> liveagent.gateway.v1.SettingsUpdateResponse - 65, // 56: liveagent.gateway.v1.AgentEnvelope.settings_sync:type_name -> liveagent.gateway.v1.SettingsSyncEvent - 67, // 57: liveagent.gateway.v1.AgentEnvelope.skill_files_list_resp:type_name -> liveagent.gateway.v1.SkillFilesListResponse - 69, // 58: liveagent.gateway.v1.AgentEnvelope.skill_metadata_read_resp:type_name -> liveagent.gateway.v1.SkillMetadataReadResponse - 71, // 59: liveagent.gateway.v1.AgentEnvelope.skill_text_read_resp:type_name -> liveagent.gateway.v1.SkillTextReadResponse - 76, // 60: liveagent.gateway.v1.AgentEnvelope.file_mention_list_resp:type_name -> liveagent.gateway.v1.FileMentionListResponse - 11, // 61: liveagent.gateway.v1.AgentEnvelope.upload_readable_files_resp:type_name -> liveagent.gateway.v1.UploadReadableFilesResponse - 79, // 62: liveagent.gateway.v1.AgentEnvelope.fs_roots_resp:type_name -> liveagent.gateway.v1.FsRootsResponse - 101, // 63: liveagent.gateway.v1.AgentEnvelope.pong:type_name -> liveagent.gateway.v1.PongResponse - 82, // 64: liveagent.gateway.v1.AgentEnvelope.fs_list_dirs_resp:type_name -> liveagent.gateway.v1.FsListDirsResponse - 13, // 65: liveagent.gateway.v1.AgentEnvelope.uploaded_image_preview_resp:type_name -> liveagent.gateway.v1.UploadedImagePreviewResponse - 20, // 66: liveagent.gateway.v1.AgentEnvelope.memory_manage_resp:type_name -> liveagent.gateway.v1.MemoryManageResponse - 73, // 67: liveagent.gateway.v1.AgentEnvelope.skill_manage_resp:type_name -> liveagent.gateway.v1.SkillManageResponse - 84, // 68: liveagent.gateway.v1.AgentEnvelope.fs_create_project_folder_resp:type_name -> liveagent.gateway.v1.FsCreateProjectFolderResponse - 24, // 69: liveagent.gateway.v1.AgentEnvelope.terminal_response:type_name -> liveagent.gateway.v1.TerminalResponse - 25, // 70: liveagent.gateway.v1.AgentEnvelope.terminal_event:type_name -> liveagent.gateway.v1.TerminalEvent - 87, // 71: liveagent.gateway.v1.AgentEnvelope.fs_list_resp:type_name -> liveagent.gateway.v1.FsListResponse - 93, // 72: liveagent.gateway.v1.AgentEnvelope.fs_write_text_resp:type_name -> liveagent.gateway.v1.FsWriteTextResponse - 95, // 73: liveagent.gateway.v1.AgentEnvelope.fs_create_dir_resp:type_name -> liveagent.gateway.v1.FsCreateDirResponse - 97, // 74: liveagent.gateway.v1.AgentEnvelope.fs_rename_resp:type_name -> liveagent.gateway.v1.FsRenameResponse - 99, // 75: liveagent.gateway.v1.AgentEnvelope.fs_delete_resp:type_name -> liveagent.gateway.v1.FsDeleteResponse - 27, // 76: liveagent.gateway.v1.AgentEnvelope.git_response:type_name -> liveagent.gateway.v1.GitResponse - 89, // 77: liveagent.gateway.v1.AgentEnvelope.fs_read_editable_text_resp:type_name -> liveagent.gateway.v1.FsReadEditableTextResponse - 91, // 78: liveagent.gateway.v1.AgentEnvelope.fs_read_workspace_image_resp:type_name -> liveagent.gateway.v1.FsReadWorkspaceImageResponse - 14, // 79: liveagent.gateway.v1.AgentEnvelope.tunnel_control:type_name -> liveagent.gateway.v1.TunnelControlRequest - 15, // 80: liveagent.gateway.v1.AgentEnvelope.tunnel_control_resp:type_name -> liveagent.gateway.v1.TunnelControlResponse - 18, // 81: liveagent.gateway.v1.AgentEnvelope.tunnel_frame:type_name -> liveagent.gateway.v1.TunnelFrame - 31, // 82: liveagent.gateway.v1.AgentEnvelope.chat_control:type_name -> liveagent.gateway.v1.ChatControlEvent - 32, // 83: liveagent.gateway.v1.AgentEnvelope.runtime_status:type_name -> liveagent.gateway.v1.RuntimeStatusEvent - 102, // 84: liveagent.gateway.v1.AgentEnvelope.error:type_name -> liveagent.gateway.v1.ErrorResponse - 9, // 85: liveagent.gateway.v1.UploadReadableFilesRequest.files:type_name -> liveagent.gateway.v1.UploadReadableFile - 8, // 86: liveagent.gateway.v1.UploadReadableFilesResponse.files:type_name -> liveagent.gateway.v1.ChatUploadedFile - 16, // 87: liveagent.gateway.v1.TunnelControlResponse.tunnels:type_name -> liveagent.gateway.v1.TunnelSummary - 16, // 88: liveagent.gateway.v1.TunnelControlResponse.tunnel:type_name -> liveagent.gateway.v1.TunnelSummary - 0, // 89: liveagent.gateway.v1.TunnelFrame.kind:type_name -> liveagent.gateway.v1.TunnelFrameKind - 17, // 90: liveagent.gateway.v1.TunnelFrame.headers:type_name -> liveagent.gateway.v1.TunnelHeader - 22, // 91: liveagent.gateway.v1.TerminalResponse.sessions:type_name -> liveagent.gateway.v1.TerminalSession - 22, // 92: liveagent.gateway.v1.TerminalResponse.session:type_name -> liveagent.gateway.v1.TerminalSession - 23, // 93: liveagent.gateway.v1.TerminalResponse.shell_options:type_name -> liveagent.gateway.v1.TerminalShellOption - 22, // 94: liveagent.gateway.v1.TerminalEvent.session:type_name -> liveagent.gateway.v1.TerminalSession - 6, // 95: liveagent.gateway.v1.ChatRequest.selected_model:type_name -> liveagent.gateway.v1.ChatSelectedModel - 8, // 96: liveagent.gateway.v1.ChatRequest.uploaded_files:type_name -> liveagent.gateway.v1.ChatUploadedFile - 7, // 97: liveagent.gateway.v1.ChatRequest.runtime_controls:type_name -> liveagent.gateway.v1.ChatRuntimeControls - 1, // 98: liveagent.gateway.v1.ChatEvent.type:type_name -> liveagent.gateway.v1.ChatEvent.ChatEventType - 37, // 99: liveagent.gateway.v1.HistoryListResponse.conversations:type_name -> liveagent.gateway.v1.ConversationSummary - 37, // 100: liveagent.gateway.v1.HistoryGetResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary - 37, // 101: liveagent.gateway.v1.HistoryRenameResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary - 37, // 102: liveagent.gateway.v1.HistoryPinResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary - 44, // 103: liveagent.gateway.v1.HistoryShareGetResponse.share:type_name -> liveagent.gateway.v1.HistoryShareStatus - 44, // 104: liveagent.gateway.v1.HistoryShareSetResponse.share:type_name -> liveagent.gateway.v1.HistoryShareStatus - 37, // 105: liveagent.gateway.v1.HistoryShareResolveResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary - 52, // 106: liveagent.gateway.v1.HistoryWorkdirsResponse.workdirs:type_name -> liveagent.gateway.v1.HistoryWorkdirSummary - 37, // 107: liveagent.gateway.v1.HistoryTruncateResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary - 37, // 108: liveagent.gateway.v1.HistorySyncEvent.conversation:type_name -> liveagent.gateway.v1.ConversationSummary - 75, // 109: liveagent.gateway.v1.FileMentionListResponse.entries:type_name -> liveagent.gateway.v1.FileMentionEntry - 77, // 110: liveagent.gateway.v1.FsRootsResponse.roots:type_name -> liveagent.gateway.v1.FsRoot - 81, // 111: liveagent.gateway.v1.FsListDirsResponse.entries:type_name -> liveagent.gateway.v1.FsDirEntry - 86, // 112: liveagent.gateway.v1.FsListResponse.entries:type_name -> liveagent.gateway.v1.FsListEntry - 5, // 113: liveagent.gateway.v1.AgentGateway.AgentConnect:input_type -> liveagent.gateway.v1.AgentEnvelope - 2, // 114: liveagent.gateway.v1.AgentGateway.Authenticate:input_type -> liveagent.gateway.v1.AuthRequest - 4, // 115: liveagent.gateway.v1.AgentGateway.AgentConnect:output_type -> liveagent.gateway.v1.GatewayEnvelope - 3, // 116: liveagent.gateway.v1.AgentGateway.Authenticate:output_type -> liveagent.gateway.v1.AuthResponse - 115, // [115:117] is the sub-list for method output_type - 113, // [113:115] is the sub-list for method input_type - 113, // [113:113] is the sub-list for extension type_name - 113, // [113:113] is the sub-list for extension extendee - 0, // [0:113] is the sub-list for field type_name + 94, // 29: liveagent.gateway.v1.GatewayEnvelope.fs_list:type_name -> liveagent.gateway.v1.FsListRequest + 101, // 30: liveagent.gateway.v1.GatewayEnvelope.fs_write_text:type_name -> liveagent.gateway.v1.FsWriteTextRequest + 103, // 31: liveagent.gateway.v1.GatewayEnvelope.fs_create_dir:type_name -> liveagent.gateway.v1.FsCreateDirRequest + 105, // 32: liveagent.gateway.v1.GatewayEnvelope.fs_rename:type_name -> liveagent.gateway.v1.FsRenameRequest + 107, // 33: liveagent.gateway.v1.GatewayEnvelope.fs_delete:type_name -> liveagent.gateway.v1.FsDeleteRequest + 33, // 34: liveagent.gateway.v1.GatewayEnvelope.git_request:type_name -> liveagent.gateway.v1.GitRequest + 97, // 35: liveagent.gateway.v1.GatewayEnvelope.fs_read_editable_text:type_name -> liveagent.gateway.v1.FsReadEditableTextRequest + 99, // 36: liveagent.gateway.v1.GatewayEnvelope.fs_read_workspace_image:type_name -> liveagent.gateway.v1.FsReadWorkspaceImageRequest + 24, // 37: liveagent.gateway.v1.GatewayEnvelope.sftp_request:type_name -> liveagent.gateway.v1.SftpRequest + 14, // 38: liveagent.gateway.v1.GatewayEnvelope.tunnel_control:type_name -> liveagent.gateway.v1.TunnelControlRequest + 15, // 39: liveagent.gateway.v1.GatewayEnvelope.tunnel_control_resp:type_name -> liveagent.gateway.v1.TunnelControlResponse + 18, // 40: liveagent.gateway.v1.GatewayEnvelope.tunnel_frame:type_name -> liveagent.gateway.v1.TunnelFrame + 72, // 41: liveagent.gateway.v1.GatewayEnvelope.settings_reset_ssh_known_host:type_name -> liveagent.gateway.v1.SettingsResetSshKnownHostRequest + 37, // 42: liveagent.gateway.v1.AgentEnvelope.chat_event:type_name -> liveagent.gateway.v1.ChatEvent + 41, // 43: liveagent.gateway.v1.AgentEnvelope.cron_manage_resp:type_name -> liveagent.gateway.v1.CronManageResponse + 43, // 44: liveagent.gateway.v1.AgentEnvelope.history_list_resp:type_name -> liveagent.gateway.v1.HistoryListResponse + 46, // 45: liveagent.gateway.v1.AgentEnvelope.history_get_resp:type_name -> liveagent.gateway.v1.HistoryGetResponse + 48, // 46: liveagent.gateway.v1.AgentEnvelope.history_rename_resp:type_name -> liveagent.gateway.v1.HistoryRenameResponse + 62, // 47: liveagent.gateway.v1.AgentEnvelope.history_delete_resp:type_name -> liveagent.gateway.v1.HistoryDeleteResponse + 65, // 48: liveagent.gateway.v1.AgentEnvelope.history_sync:type_name -> liveagent.gateway.v1.HistorySyncEvent + 64, // 49: liveagent.gateway.v1.AgentEnvelope.history_truncate_resp:type_name -> liveagent.gateway.v1.HistoryTruncateResponse + 50, // 50: liveagent.gateway.v1.AgentEnvelope.history_pin_resp:type_name -> liveagent.gateway.v1.HistoryPinResponse + 53, // 51: liveagent.gateway.v1.AgentEnvelope.history_share_get_resp:type_name -> liveagent.gateway.v1.HistoryShareGetResponse + 55, // 52: liveagent.gateway.v1.AgentEnvelope.history_share_set_resp:type_name -> liveagent.gateway.v1.HistoryShareSetResponse + 57, // 53: liveagent.gateway.v1.AgentEnvelope.history_share_resolve_resp:type_name -> liveagent.gateway.v1.HistoryShareResolveResponse + 60, // 54: liveagent.gateway.v1.AgentEnvelope.history_workdirs_resp:type_name -> liveagent.gateway.v1.HistoryWorkdirsResponse + 67, // 55: liveagent.gateway.v1.AgentEnvelope.provider_list_resp:type_name -> liveagent.gateway.v1.ProviderListResponse + 69, // 56: liveagent.gateway.v1.AgentEnvelope.settings_get_resp:type_name -> liveagent.gateway.v1.SettingsGetResponse + 71, // 57: liveagent.gateway.v1.AgentEnvelope.settings_update_resp:type_name -> liveagent.gateway.v1.SettingsUpdateResponse + 74, // 58: liveagent.gateway.v1.AgentEnvelope.settings_sync:type_name -> liveagent.gateway.v1.SettingsSyncEvent + 76, // 59: liveagent.gateway.v1.AgentEnvelope.skill_files_list_resp:type_name -> liveagent.gateway.v1.SkillFilesListResponse + 78, // 60: liveagent.gateway.v1.AgentEnvelope.skill_metadata_read_resp:type_name -> liveagent.gateway.v1.SkillMetadataReadResponse + 80, // 61: liveagent.gateway.v1.AgentEnvelope.skill_text_read_resp:type_name -> liveagent.gateway.v1.SkillTextReadResponse + 85, // 62: liveagent.gateway.v1.AgentEnvelope.file_mention_list_resp:type_name -> liveagent.gateway.v1.FileMentionListResponse + 11, // 63: liveagent.gateway.v1.AgentEnvelope.upload_readable_files_resp:type_name -> liveagent.gateway.v1.UploadReadableFilesResponse + 88, // 64: liveagent.gateway.v1.AgentEnvelope.fs_roots_resp:type_name -> liveagent.gateway.v1.FsRootsResponse + 110, // 65: liveagent.gateway.v1.AgentEnvelope.pong:type_name -> liveagent.gateway.v1.PongResponse + 91, // 66: liveagent.gateway.v1.AgentEnvelope.fs_list_dirs_resp:type_name -> liveagent.gateway.v1.FsListDirsResponse + 13, // 67: liveagent.gateway.v1.AgentEnvelope.uploaded_image_preview_resp:type_name -> liveagent.gateway.v1.UploadedImagePreviewResponse + 20, // 68: liveagent.gateway.v1.AgentEnvelope.memory_manage_resp:type_name -> liveagent.gateway.v1.MemoryManageResponse + 82, // 69: liveagent.gateway.v1.AgentEnvelope.skill_manage_resp:type_name -> liveagent.gateway.v1.SkillManageResponse + 93, // 70: liveagent.gateway.v1.AgentEnvelope.fs_create_project_folder_resp:type_name -> liveagent.gateway.v1.FsCreateProjectFolderResponse + 31, // 71: liveagent.gateway.v1.AgentEnvelope.terminal_response:type_name -> liveagent.gateway.v1.TerminalResponse + 32, // 72: liveagent.gateway.v1.AgentEnvelope.terminal_event:type_name -> liveagent.gateway.v1.TerminalEvent + 96, // 73: liveagent.gateway.v1.AgentEnvelope.fs_list_resp:type_name -> liveagent.gateway.v1.FsListResponse + 102, // 74: liveagent.gateway.v1.AgentEnvelope.fs_write_text_resp:type_name -> liveagent.gateway.v1.FsWriteTextResponse + 104, // 75: liveagent.gateway.v1.AgentEnvelope.fs_create_dir_resp:type_name -> liveagent.gateway.v1.FsCreateDirResponse + 106, // 76: liveagent.gateway.v1.AgentEnvelope.fs_rename_resp:type_name -> liveagent.gateway.v1.FsRenameResponse + 108, // 77: liveagent.gateway.v1.AgentEnvelope.fs_delete_resp:type_name -> liveagent.gateway.v1.FsDeleteResponse + 34, // 78: liveagent.gateway.v1.AgentEnvelope.git_response:type_name -> liveagent.gateway.v1.GitResponse + 98, // 79: liveagent.gateway.v1.AgentEnvelope.fs_read_editable_text_resp:type_name -> liveagent.gateway.v1.FsReadEditableTextResponse + 100, // 80: liveagent.gateway.v1.AgentEnvelope.fs_read_workspace_image_resp:type_name -> liveagent.gateway.v1.FsReadWorkspaceImageResponse + 27, // 81: liveagent.gateway.v1.AgentEnvelope.sftp_response:type_name -> liveagent.gateway.v1.SftpResponse + 28, // 82: liveagent.gateway.v1.AgentEnvelope.sftp_event:type_name -> liveagent.gateway.v1.SftpEvent + 14, // 83: liveagent.gateway.v1.AgentEnvelope.tunnel_control:type_name -> liveagent.gateway.v1.TunnelControlRequest + 15, // 84: liveagent.gateway.v1.AgentEnvelope.tunnel_control_resp:type_name -> liveagent.gateway.v1.TunnelControlResponse + 18, // 85: liveagent.gateway.v1.AgentEnvelope.tunnel_frame:type_name -> liveagent.gateway.v1.TunnelFrame + 38, // 86: liveagent.gateway.v1.AgentEnvelope.chat_control:type_name -> liveagent.gateway.v1.ChatControlEvent + 39, // 87: liveagent.gateway.v1.AgentEnvelope.runtime_status:type_name -> liveagent.gateway.v1.RuntimeStatusEvent + 73, // 88: liveagent.gateway.v1.AgentEnvelope.settings_reset_ssh_known_host_resp:type_name -> liveagent.gateway.v1.SettingsResetSshKnownHostResponse + 111, // 89: liveagent.gateway.v1.AgentEnvelope.error:type_name -> liveagent.gateway.v1.ErrorResponse + 9, // 90: liveagent.gateway.v1.UploadReadableFilesRequest.files:type_name -> liveagent.gateway.v1.UploadReadableFile + 8, // 91: liveagent.gateway.v1.UploadReadableFilesResponse.files:type_name -> liveagent.gateway.v1.ChatUploadedFile + 16, // 92: liveagent.gateway.v1.TunnelControlResponse.tunnels:type_name -> liveagent.gateway.v1.TunnelSummary + 16, // 93: liveagent.gateway.v1.TunnelControlResponse.tunnel:type_name -> liveagent.gateway.v1.TunnelSummary + 0, // 94: liveagent.gateway.v1.TunnelFrame.kind:type_name -> liveagent.gateway.v1.TunnelFrameKind + 17, // 95: liveagent.gateway.v1.TunnelFrame.headers:type_name -> liveagent.gateway.v1.TunnelHeader + 23, // 96: liveagent.gateway.v1.TerminalSession.ssh:type_name -> liveagent.gateway.v1.TerminalSshMetadata + 25, // 97: liveagent.gateway.v1.SftpResponse.entries:type_name -> liveagent.gateway.v1.SftpEntry + 25, // 98: liveagent.gateway.v1.SftpResponse.entry:type_name -> liveagent.gateway.v1.SftpEntry + 26, // 99: liveagent.gateway.v1.SftpResponse.transfer:type_name -> liveagent.gateway.v1.SftpTransfer + 26, // 100: liveagent.gateway.v1.SftpEvent.transfer:type_name -> liveagent.gateway.v1.SftpTransfer + 22, // 101: liveagent.gateway.v1.TerminalResponse.sessions:type_name -> liveagent.gateway.v1.TerminalSession + 22, // 102: liveagent.gateway.v1.TerminalResponse.session:type_name -> liveagent.gateway.v1.TerminalSession + 30, // 103: liveagent.gateway.v1.TerminalResponse.shell_options:type_name -> liveagent.gateway.v1.TerminalShellOption + 29, // 104: liveagent.gateway.v1.TerminalResponse.ssh_prompt:type_name -> liveagent.gateway.v1.TerminalSshPrompt + 22, // 105: liveagent.gateway.v1.TerminalEvent.session:type_name -> liveagent.gateway.v1.TerminalSession + 6, // 106: liveagent.gateway.v1.ChatRequest.selected_model:type_name -> liveagent.gateway.v1.ChatSelectedModel + 8, // 107: liveagent.gateway.v1.ChatRequest.uploaded_files:type_name -> liveagent.gateway.v1.ChatUploadedFile + 7, // 108: liveagent.gateway.v1.ChatRequest.runtime_controls:type_name -> liveagent.gateway.v1.ChatRuntimeControls + 1, // 109: liveagent.gateway.v1.ChatEvent.type:type_name -> liveagent.gateway.v1.ChatEvent.ChatEventType + 44, // 110: liveagent.gateway.v1.HistoryListResponse.conversations:type_name -> liveagent.gateway.v1.ConversationSummary + 44, // 111: liveagent.gateway.v1.HistoryGetResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary + 44, // 112: liveagent.gateway.v1.HistoryRenameResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary + 44, // 113: liveagent.gateway.v1.HistoryPinResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary + 51, // 114: liveagent.gateway.v1.HistoryShareGetResponse.share:type_name -> liveagent.gateway.v1.HistoryShareStatus + 51, // 115: liveagent.gateway.v1.HistoryShareSetResponse.share:type_name -> liveagent.gateway.v1.HistoryShareStatus + 44, // 116: liveagent.gateway.v1.HistoryShareResolveResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary + 59, // 117: liveagent.gateway.v1.HistoryWorkdirsResponse.workdirs:type_name -> liveagent.gateway.v1.HistoryWorkdirSummary + 44, // 118: liveagent.gateway.v1.HistoryTruncateResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary + 44, // 119: liveagent.gateway.v1.HistorySyncEvent.conversation:type_name -> liveagent.gateway.v1.ConversationSummary + 84, // 120: liveagent.gateway.v1.FileMentionListResponse.entries:type_name -> liveagent.gateway.v1.FileMentionEntry + 86, // 121: liveagent.gateway.v1.FsRootsResponse.roots:type_name -> liveagent.gateway.v1.FsRoot + 90, // 122: liveagent.gateway.v1.FsListDirsResponse.entries:type_name -> liveagent.gateway.v1.FsDirEntry + 95, // 123: liveagent.gateway.v1.FsListResponse.entries:type_name -> liveagent.gateway.v1.FsListEntry + 5, // 124: liveagent.gateway.v1.AgentGateway.AgentConnect:input_type -> liveagent.gateway.v1.AgentEnvelope + 2, // 125: liveagent.gateway.v1.AgentGateway.Authenticate:input_type -> liveagent.gateway.v1.AuthRequest + 4, // 126: liveagent.gateway.v1.AgentGateway.AgentConnect:output_type -> liveagent.gateway.v1.GatewayEnvelope + 3, // 127: liveagent.gateway.v1.AgentGateway.Authenticate:output_type -> liveagent.gateway.v1.AuthResponse + 126, // [126:128] is the sub-list for method output_type + 124, // [124:126] is the sub-list for method input_type + 124, // [124:124] is the sub-list for extension type_name + 124, // [124:124] is the sub-list for extension extendee + 0, // [0:124] is the sub-list for field type_name } func init() { file_proto_v1_gateway_proto_init() } @@ -8660,9 +9740,11 @@ func file_proto_v1_gateway_proto_init() { (*GatewayEnvelope_GitRequest)(nil), (*GatewayEnvelope_FsReadEditableText)(nil), (*GatewayEnvelope_FsReadWorkspaceImage)(nil), + (*GatewayEnvelope_SftpRequest)(nil), (*GatewayEnvelope_TunnelControl)(nil), (*GatewayEnvelope_TunnelControlResp)(nil), (*GatewayEnvelope_TunnelFrame)(nil), + (*GatewayEnvelope_SettingsResetSshKnownHost)(nil), } file_proto_v1_gateway_proto_msgTypes[3].OneofWrappers = []any{ (*AgentEnvelope_ChatEvent)(nil), @@ -8704,21 +9786,24 @@ func file_proto_v1_gateway_proto_init() { (*AgentEnvelope_GitResponse)(nil), (*AgentEnvelope_FsReadEditableTextResp)(nil), (*AgentEnvelope_FsReadWorkspaceImageResp)(nil), + (*AgentEnvelope_SftpResponse)(nil), + (*AgentEnvelope_SftpEvent)(nil), (*AgentEnvelope_TunnelControl)(nil), (*AgentEnvelope_TunnelControlResp)(nil), (*AgentEnvelope_TunnelFrame)(nil), (*AgentEnvelope_ChatControl)(nil), (*AgentEnvelope_RuntimeStatus)(nil), + (*AgentEnvelope_SettingsResetSshKnownHostResp)(nil), (*AgentEnvelope_Error)(nil), } - file_proto_v1_gateway_proto_msgTypes[45].OneofWrappers = []any{} + file_proto_v1_gateway_proto_msgTypes[52].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_v1_gateway_proto_rawDesc), len(file_proto_v1_gateway_proto_rawDesc)), NumEnums: 2, - NumMessages: 101, + NumMessages: 110, NumExtensions: 0, NumServices: 1, }, diff --git a/crates/agent-gateway/internal/server/websocket.go b/crates/agent-gateway/internal/server/websocket.go index 844e291ad..b0033f37c 100644 --- a/crates/agent-gateway/internal/server/websocket.go +++ b/crates/agent-gateway/internal/server/websocket.go @@ -59,6 +59,41 @@ type websocketTerminalRequestPayload struct { Cols *int `json:"cols"` Rows *int `json:"rows"` MaxBytes *int `json:"max_bytes"` + SshHostID string `json:"ssh_host_id"` + PromptID string `json:"prompt_id"` + PromptAnswer string `json:"prompt_answer"` + TrustHostKey bool `json:"trust_host_key"` + SftpEnabled bool `json:"sftp_enabled"` +} + +type websocketSshKnownHostResetPayload struct { + Host string `json:"host"` + Port *int `json:"port"` +} + +type websocketSftpRequestPayload struct { + SessionID string `json:"session_id"` + SessionIDCamel string `json:"sessionId"` + ProjectPathKey string `json:"project_path_key"` + ProjectPathKeyCamel string `json:"projectPathKey"` + Workdir string `json:"workdir"` + Side string `json:"side"` + LocalPath string `json:"local_path"` + LocalPathCamel string `json:"localPath"` + RemotePath string `json:"remote_path"` + RemotePathCamel string `json:"remotePath"` + FromPath string `json:"from_path"` + FromPathCamel string `json:"fromPath"` + SourcePathCamel string `json:"sourcePath"` + ToPath string `json:"to_path"` + ToPathCamel string `json:"toPath"` + Direction string `json:"direction"` + TargetPath string `json:"target_path"` + TargetPathCamel string `json:"targetPath"` + TransferID string `json:"transfer_id"` + TransferIDCamel string `json:"transferId"` + Recursive bool `json:"recursive"` + Overwrite bool `json:"overwrite"` } type websocketGitRequestPayload struct { @@ -89,6 +124,8 @@ type websocketConnection struct { settingsEventsCleanup func() terminalEvents <-chan *gatewayv1.TerminalEvent terminalEventsCleanup func() + sftpEvents <-chan *gatewayv1.SftpEvent + sftpEventsCleanup func() chatEvents <-chan *session.ChatBroadcastEvent chatEventsCleanup func() heartbeatOnce sync.Once @@ -177,6 +214,10 @@ func (c *websocketConnection) close() { c.terminalEventsCleanup() c.terminalEventsCleanup = nil } + if c.sftpEventsCleanup != nil { + c.sftpEventsCleanup() + c.sftpEventsCleanup = nil + } if c.chatEventsCleanup != nil { c.chatEventsCleanup() c.chatEventsCleanup = nil @@ -212,6 +253,7 @@ func (c *websocketConnection) handleAuth(req websocketRequest) { c.startHistorySyncForwarder() c.startSettingsSyncForwarder() c.startTerminalEventForwarder() + c.startSftpEventForwarder() c.startChatEventForwarder() c.startWebSocketHeartbeat() if err := c.writeResponse(req.ID, map[string]any{"ok": true}); err != nil { @@ -337,9 +379,6 @@ func (c *websocketConnection) startTerminalEventForwarder() { if !ok { return } - if !c.sm.WebTerminalEnabled() { - continue - } if !c.shouldForwardTerminalEvent(event) { continue } @@ -352,11 +391,44 @@ func (c *websocketConnection) startTerminalEventForwarder() { }() } +func (c *websocketConnection) startSftpEventForwarder() { + if c.sftpEvents != nil || c.sftpEventsCleanup != nil { + return + } + + sftpEvents, cleanup := c.sm.SubscribeSftpEvents() + c.sftpEvents = sftpEvents + c.sftpEventsCleanup = cleanup + + go func() { + for { + select { + case <-c.done: + return + case event, ok := <-sftpEvents: + if !ok { + return + } + if !c.sm.WebSshTerminalEnabled() { + continue + } + if err := c.writeSftpEvent(websocketSftpEventPayload(event)); err != nil { + c.close() + return + } + } + } + }() +} + func (c *websocketConnection) replayTerminalSessionSnapshot() { - if !c.sm.WebTerminalEnabled() { + if !c.terminalFeaturesEnabled() { return } for _, terminalSession := range c.sm.TerminalSessionSnapshot("") { + if !c.terminalSessionAllowed(terminalSession) { + continue + } if err := c.writeTerminalEvent(websocketTerminalEventPayload(&gatewayv1.TerminalEvent{ Kind: "created", SessionId: terminalSession.GetId(), @@ -382,7 +454,7 @@ func (c *websocketConnection) forgetTerminalInterest(sessionID string, projectPa } func (c *websocketConnection) shouldForwardTerminalEvent(event *gatewayv1.TerminalEvent) bool { - return c.terminalInterest.shouldForward(event) + return c.terminalEventAllowed(event) && c.terminalInterest.shouldForward(event) } func (c *websocketConnection) startWebSocketHeartbeat() { @@ -536,6 +608,13 @@ func (c *websocketConnection) writeTerminalEvent(payload any) error { }) } +func (c *websocketConnection) writeSftpEvent(payload any) error { + return c.writeEnvelope(websocketEnvelope{ + Type: "sftp.event", + Payload: payload, + }) +} + func (c *websocketConnection) writeEnvelope(envelope websocketEnvelope) error { return c.writer.write(envelope) } diff --git a/crates/agent-gateway/internal/server/websocket_chat_handlers.go b/crates/agent-gateway/internal/server/websocket_chat_handlers.go index 63abbea85..bd30d4a8f 100644 --- a/crates/agent-gateway/internal/server/websocket_chat_handlers.go +++ b/crates/agent-gateway/internal/server/websocket_chat_handlers.go @@ -40,10 +40,6 @@ func (c *websocketConnection) handleChatStart(req websocketRequest) { _ = c.writeError(req.ID, "agent offline") return } - if !status.ChatRuntimeReady { - _ = c.writeError(req.ID, "Desktop chat runtime is not ready. Please retry.") - return - } snapshot, created, err := c.sm.StartPendingChatRunWithClientRequest( req.ID, diff --git a/crates/agent-gateway/internal/server/websocket_connection_state_test.go b/crates/agent-gateway/internal/server/websocket_connection_state_test.go index 86f8d9741..9ba139d81 100644 --- a/crates/agent-gateway/internal/server/websocket_connection_state_test.go +++ b/crates/agent-gateway/internal/server/websocket_connection_state_test.go @@ -4,6 +4,7 @@ import ( "testing" gatewayv1 "github.com/liveagent/agent-gateway/internal/proto/v1" + "github.com/liveagent/agent-gateway/internal/session" ) func TestWebsocketTerminalInterestTrackerFiltersOutputBySession(t *testing.T) { @@ -39,6 +40,103 @@ func TestWebsocketTerminalInterestTrackerFiltersOutputBySession(t *testing.T) { } } +func TestWebsocketTerminalPermissionsSeparateLocalAndSshSessions(t *testing.T) { + t.Parallel() + + manager := session.NewManager() + manager.ApplySettingsJSON(`{"remote":{"enableWebTerminal":false,"enableWebSshTerminal":true}}`) + conn := &websocketConnection{sm: manager} + localSession := &gatewayv1.TerminalSession{ + Id: "local-1", + Kind: "local", + } + sshSession := &gatewayv1.TerminalSession{ + Id: "ssh-1", + Kind: "ssh", + Ssh: &gatewayv1.TerminalSshMetadata{ + Status: "connected", + }, + } + + if conn.terminalSessionAllowed(localSession) { + t.Fatal("local terminal session should not be allowed when only web SSH terminal is enabled") + } + if !conn.terminalSessionAllowed(sshSession) { + t.Fatal("SSH terminal session should be allowed when web SSH terminal is enabled") + } + + manager.ApplySettingsJSON(`{"remote":{"enableWebTerminal":true,"enableWebSshTerminal":false}}`) + if !conn.terminalSessionAllowed(localSession) { + t.Fatal("local terminal session should be allowed when web terminal is enabled") + } + if conn.terminalSessionAllowed(sshSession) { + t.Fatal("SSH terminal session should not be allowed when web SSH terminal is disabled") + } +} + +func TestWebsocketTerminalSessionPayloadIncludesSftpEnabled(t *testing.T) { + t.Parallel() + + payload := websocketTerminalSessionPayload(&gatewayv1.TerminalSession{ + Id: "ssh-1", + Kind: "ssh", + Ssh: &gatewayv1.TerminalSshMetadata{ + SftpEnabled: true, + }, + }) + ssh, ok := payload["ssh"].(map[string]any) + if !ok { + t.Fatalf("ssh payload missing: %#v", payload["ssh"]) + } + if got := ssh["sftp_enabled"]; got != true { + t.Fatalf("sftp_enabled = %#v, want true", got) + } + if got := ssh["sftpEnabled"]; got != true { + t.Fatalf("sftpEnabled = %#v, want true", got) + } +} + +func TestWebsocketTerminalEventForwardingAllowsSshOnlyStatusEvents(t *testing.T) { + t.Parallel() + + manager := session.NewManager() + manager.ApplySettingsJSON(`{"remote":{"enableWebTerminal":false,"enableWebSshTerminal":true}}`) + conn := &websocketConnection{ + sm: manager, + terminalInterest: newWebsocketTerminalInterestTracker(), + } + + if !conn.shouldForwardTerminalEvent(&gatewayv1.TerminalEvent{ + Kind: "reconnecting", + SessionId: "ssh-1", + ProjectPathKey: "project-1", + Session: &gatewayv1.TerminalSession{ + Id: "ssh-1", + ProjectPathKey: "project-1", + Kind: "ssh", + Ssh: &gatewayv1.TerminalSshMetadata{ + Status: "reconnecting", + ReconnectAttempt: 1, + }, + }, + }) { + t.Fatal("SSH metadata event should forward when only web SSH terminal is enabled") + } + + if conn.shouldForwardTerminalEvent(&gatewayv1.TerminalEvent{ + Kind: "created", + SessionId: "local-1", + ProjectPathKey: "project-1", + Session: &gatewayv1.TerminalSession{ + Id: "local-1", + ProjectPathKey: "project-1", + Kind: "local", + }, + }) { + t.Fatal("local metadata event should not forward when web terminal is disabled") + } +} + func TestWebsocketChatTrackerKeepsRecentReleasedRequests(t *testing.T) { t.Parallel() diff --git a/crates/agent-gateway/internal/server/websocket_payloads.go b/crates/agent-gateway/internal/server/websocket_payloads.go index b54b98bb2..3b1f232c9 100644 --- a/crates/agent-gateway/internal/server/websocket_payloads.go +++ b/crates/agent-gateway/internal/server/websocket_payloads.go @@ -98,12 +98,14 @@ func websocketTerminalSessionPayload(session *gatewayv1.TerminalSession) map[str if session == nil { return nil } + kind := terminalSessionKind(session) payload := map[string]any{ "id": strings.TrimSpace(session.GetId()), "project_path_key": strings.TrimSpace(session.GetProjectPathKey()), "cwd": strings.TrimSpace(session.GetCwd()), "shell": strings.TrimSpace(session.GetShell()), "title": strings.TrimSpace(session.GetTitle()), + "kind": kind, "pid": session.GetPid(), "cols": session.GetCols(), "rows": session.GetRows(), @@ -119,9 +121,35 @@ func websocketTerminalSessionPayload(session *gatewayv1.TerminalSession) map[str if session.GetFinishedAt() == 0 { payload["finished_at"] = nil } + if kind == "ssh" { + payload["pid"] = nil + } + if ssh := session.GetSsh(); ssh != nil { + payload["ssh"] = map[string]any{ + "host_id": strings.TrimSpace(ssh.GetHostId()), + "host_name": strings.TrimSpace(ssh.GetHostName()), + "username": strings.TrimSpace(ssh.GetUsername()), + "host": strings.TrimSpace(ssh.GetHost()), + "port": ssh.GetPort(), + "auth_type": strings.TrimSpace(ssh.GetAuthType()), + "status": strings.TrimSpace(ssh.GetStatus()), + "reconnect_attempt": ssh.GetReconnectAttempt(), + "reconnect_max_attempts": ssh.GetReconnectMaxAttempts(), + "sftp_enabled": ssh.GetSftpEnabled(), + "sftpEnabled": ssh.GetSftpEnabled(), + } + } return payload } +func terminalSessionKind(session *gatewayv1.TerminalSession) string { + kind := strings.TrimSpace(session.GetKind()) + if kind == "ssh" { + return "ssh" + } + return "local" +} + func websocketTerminalShellOptionPayload(option *gatewayv1.TerminalShellOption) map[string]any { if option == nil { return nil @@ -158,9 +186,26 @@ func websocketTerminalResponsePayload(resp *gatewayv1.TerminalResponse) map[stri payload["output_start_offset"] = resp.GetOutputStartOffset() payload["output_end_offset"] = resp.GetOutputEndOffset() } + if resp.GetLatencyMs() > 0 { + payload["latency_ms"] = resp.GetLatencyMs() + } if session := websocketTerminalSessionPayload(resp.GetSession()); session != nil { payload["session"] = session } + if prompt := resp.GetSshPrompt(); prompt != nil { + payload["ssh_prompt"] = map[string]any{ + "id": strings.TrimSpace(prompt.GetId()), + "kind": strings.TrimSpace(prompt.GetKind()), + "host_id": strings.TrimSpace(prompt.GetHostId()), + "host_name": strings.TrimSpace(prompt.GetHostName()), + "host": strings.TrimSpace(prompt.GetHost()), + "port": prompt.GetPort(), + "message": strings.TrimSpace(prompt.GetMessage()), + "fingerprint_sha256": strings.TrimSpace(prompt.GetFingerprintSha256()), + "key_type": strings.TrimSpace(prompt.GetKeyType()), + "answer_echo": prompt.GetAnswerEcho(), + } + } return payload } diff --git a/crates/agent-gateway/internal/server/websocket_routes.go b/crates/agent-gateway/internal/server/websocket_routes.go index 60ad324dd..878d7c50d 100644 --- a/crates/agent-gateway/internal/server/websocket_routes.go +++ b/crates/agent-gateway/internal/server/websocket_routes.go @@ -29,6 +29,7 @@ var websocketRequestHandlers = map[string]websocketRequestHandler{ "providers.list": (*websocketConnection).handleProviderList, "settings.get": (*websocketConnection).handleSettingsGet, "settings.update": (*websocketConnection).handleSettingsUpdate, + "settings.ssh_known_host.reset": (*websocketConnection).handleSettingsResetSshKnownHost, "skills.list": (*websocketConnection).handleSkillFilesList, "mentions.list": (*websocketConnection).handleFileMentionList, "skills.read-metadata": (*websocketConnection).handleSkillMetadataRead, @@ -44,6 +45,10 @@ var websocketRequestHandlers = map[string]websocketRequestHandler{ "terminal.shell_options": (*websocketConnection).handleTerminalRequest, "terminal.list": (*websocketConnection).handleTerminalRequest, "terminal.create": (*websocketConnection).handleTerminalRequest, + "terminal.create_ssh": (*websocketConnection).handleTerminalRequest, + "terminal.answer_ssh_prompt": (*websocketConnection).handleTerminalRequest, + "terminal.cancel_ssh_prompt": (*websocketConnection).handleTerminalRequest, + "terminal.ssh_latency": (*websocketConnection).handleTerminalRequest, "terminal.attach": (*websocketConnection).handleTerminalRequest, "terminal.input": (*websocketConnection).handleTerminalRequest, "terminal.resize": (*websocketConnection).handleTerminalRequest, @@ -51,6 +56,13 @@ var websocketRequestHandlers = map[string]websocketRequestHandler{ "terminal.close": (*websocketConnection).handleTerminalRequest, "terminal.close_project": (*websocketConnection).handleTerminalRequest, "terminal.detach": (*websocketConnection).handleTerminalDetach, + "sftp.list": (*websocketConnection).handleSftpRequest, + "sftp.stat": (*websocketConnection).handleSftpRequest, + "sftp.mkdir": (*websocketConnection).handleSftpRequest, + "sftp.rename": (*websocketConnection).handleSftpRequest, + "sftp.delete": (*websocketConnection).handleSftpRequest, + "sftp.transfer": (*websocketConnection).handleSftpRequest, + "sftp.cancel": (*websocketConnection).handleSftpRequest, "tunnel.list": (*websocketConnection).handleTunnelList, "tunnel.create": (*websocketConnection).handleTunnelCreate, "tunnel.update": (*websocketConnection).handleTunnelUpdate, diff --git a/crates/agent-gateway/internal/server/websocket_routes_test.go b/crates/agent-gateway/internal/server/websocket_routes_test.go index 3b275b626..d918328af 100644 --- a/crates/agent-gateway/internal/server/websocket_routes_test.go +++ b/crates/agent-gateway/internal/server/websocket_routes_test.go @@ -33,6 +33,7 @@ func TestWebsocketRequestHandlersCoverKnownProtocolTypes(t *testing.T) { "providers.list", "settings.get", "settings.update", + "settings.ssh_known_host.reset", "skills.list", "mentions.list", "skills.read-metadata", @@ -48,6 +49,10 @@ func TestWebsocketRequestHandlersCoverKnownProtocolTypes(t *testing.T) { "terminal.shell_options", "terminal.list", "terminal.create", + "terminal.create_ssh", + "terminal.answer_ssh_prompt", + "terminal.cancel_ssh_prompt", + "terminal.ssh_latency", "terminal.attach", "terminal.input", "terminal.resize", @@ -55,6 +60,13 @@ func TestWebsocketRequestHandlersCoverKnownProtocolTypes(t *testing.T) { "terminal.close", "terminal.close_project", "terminal.detach", + "sftp.list", + "sftp.stat", + "sftp.mkdir", + "sftp.rename", + "sftp.delete", + "sftp.transfer", + "sftp.cancel", "tunnel.list", "tunnel.create", "tunnel.update", diff --git a/crates/agent-gateway/internal/server/websocket_settings_handlers.go b/crates/agent-gateway/internal/server/websocket_settings_handlers.go index 8276f623a..86eda3da3 100644 --- a/crates/agent-gateway/internal/server/websocket_settings_handlers.go +++ b/crates/agent-gateway/internal/server/websocket_settings_handlers.go @@ -79,3 +79,60 @@ func (c *websocketConnection) handleSettingsUpdate(req websocketRequest) { "message": strings.TrimSpace(settingsResp.GetMessage()), }) } + +func (c *websocketConnection) handleSettingsResetSshKnownHost(req websocketRequest) { + if !c.sm.WebSshTerminalEnabled() { + _ = c.writeError(req.ID, "web SSH terminal is disabled in desktop Remote settings") + return + } + + var body websocketSshKnownHostResetPayload + if err := decodeWebSocketPayload(req.Payload, &body); err != nil { + _ = c.writeError(req.ID, "invalid settings.ssh_known_host.reset payload") + return + } + + host := strings.TrimSpace(body.Host) + if host == "" { + _ = c.writeError(req.ID, "SSH host is required") + return + } + if body.Port == nil || *body.Port <= 0 { + _ = c.writeError(req.ID, "SSH port is required") + return + } + if *body.Port > 65535 { + _ = c.writeError(req.ID, "SSH port must be <= 65535") + return + } + port := uint32(*body.Port) + + response, err := c.awaitAgentResponse(req.ID, &gatewayv1.GatewayEnvelope{ + RequestId: req.ID, + Timestamp: time.Now().Unix(), + Payload: &gatewayv1.GatewayEnvelope_SettingsResetSshKnownHost{ + SettingsResetSshKnownHost: &gatewayv1.SettingsResetSshKnownHostRequest{ + Host: host, + Port: port, + }, + }, + }) + if err != nil { + _ = c.writeError(req.ID, websocketErrorMessage(err)) + return + } + if errResp := response.GetError(); errResp != nil { + _ = c.writeError(req.ID, errResp.GetMessage()) + return + } + + settingsResp := response.GetSettingsResetSshKnownHostResp() + if settingsResp == nil { + _ = c.writeError(req.ID, "unexpected agent response") + return + } + + _ = c.writeResponse(req.ID, map[string]any{ + "deleted": settingsResp.GetDeleted(), + }) +} diff --git a/crates/agent-gateway/internal/server/websocket_sftp_handlers.go b/crates/agent-gateway/internal/server/websocket_sftp_handlers.go new file mode 100644 index 000000000..4849b8556 --- /dev/null +++ b/crates/agent-gateway/internal/server/websocket_sftp_handlers.go @@ -0,0 +1,179 @@ +package server + +import ( + "strings" + "time" + + gatewayv1 "github.com/liveagent/agent-gateway/internal/proto/v1" +) + +func (c *websocketConnection) handleSftpRequest(req websocketRequest) { + action := sftpActionFromRequestType(req.Type) + + var body websocketSftpRequestPayload + if err := decodeWebSocketPayload(req.Payload, &body); err != nil { + _ = c.writeError(req.ID, "invalid "+req.Type+" payload") + return + } + if !c.sm.WebSshTerminalEnabled() { + _ = c.writeError(req.ID, "web SSH SFTP is disabled in desktop Remote settings") + return + } + + side := strings.TrimSpace(body.Side) + if side == "" { + side = strings.TrimSpace(body.Direction) + } + direction := strings.TrimSpace(body.Direction) + if direction == "" { + direction = side + } + sessionID := firstNonEmptyTrimmed(body.SessionID, body.SessionIDCamel) + projectPathKey := firstNonEmptyTrimmed(body.ProjectPathKey, body.ProjectPathKeyCamel) + localPath := firstNonEmptyRaw(body.LocalPath, body.LocalPathCamel) + remotePath := firstNonEmptyRaw(body.RemotePath, body.RemotePathCamel) + fromPath := firstNonEmptyRaw( + body.FromPath, + body.FromPathCamel, + body.SourcePathCamel, + body.TransferID, + body.TransferIDCamel, + ) + toPath := firstNonEmptyRaw(body.ToPath, body.ToPathCamel) + targetPath := firstNonEmptyRaw(body.TargetPath, body.TargetPathCamel) + + response, err := c.awaitAgentResponse(req.ID, &gatewayv1.GatewayEnvelope{ + RequestId: req.ID, + Timestamp: time.Now().Unix(), + Payload: &gatewayv1.GatewayEnvelope_SftpRequest{ + SftpRequest: &gatewayv1.SftpRequest{ + Action: action, + SessionId: sessionID, + ProjectPathKey: projectPathKey, + Workdir: strings.TrimSpace(body.Workdir), + LocalPath: localPath, + RemotePath: remotePath, + FromPath: fromPath, + ToPath: toPath, + Direction: direction, + TargetPath: targetPath, + Recursive: body.Recursive, + Overwrite: body.Overwrite, + }, + }, + }) + if err != nil { + _ = c.writeError(req.ID, websocketErrorMessage(err)) + return + } + if errResp := response.GetError(); errResp != nil { + _ = c.writeError(req.ID, errResp.GetMessage()) + return + } + + resp := response.GetSftpResponse() + if resp == nil { + _ = c.writeError(req.ID, "unexpected agent response") + return + } + _ = c.writeResponse(req.ID, websocketSftpResponsePayload(resp)) +} + +func sftpActionFromRequestType(requestType string) string { + const prefix = "sftp." + if strings.HasPrefix(requestType, prefix) { + return strings.TrimSpace(strings.TrimPrefix(requestType, prefix)) + } + return strings.TrimSpace(requestType) +} + +func firstNonEmptyTrimmed(values ...string) string { + for _, value := range values { + if trimmed := strings.TrimSpace(value); trimmed != "" { + return trimmed + } + } + return "" +} + +func firstNonEmptyRaw(values ...string) string { + for _, value := range values { + if value != "" { + return value + } + } + return "" +} + +func websocketSftpResponsePayload(resp *gatewayv1.SftpResponse) map[string]any { + payload := map[string]any{ + "action": strings.TrimSpace(resp.GetAction()), + "path": resp.GetPath(), + "exists": resp.GetExists(), + "entries": websocketSftpEntriesPayload(resp.GetEntries()), + } + if entry := resp.GetEntry(); entry != nil { + payload["entry"] = websocketSftpEntryPayload(entry) + } + if transfer := resp.GetTransfer(); transfer != nil { + payload["transfer"] = websocketSftpTransferPayload(transfer) + } + return payload +} + +func websocketSftpEventPayload(event *gatewayv1.SftpEvent) map[string]any { + payload := map[string]any{ + "kind": strings.TrimSpace(event.GetKind()), + } + if transfer := event.GetTransfer(); transfer != nil { + payload["transfer"] = websocketSftpTransferPayload(transfer) + } + return payload +} + +func websocketSftpEntriesPayload(entries []*gatewayv1.SftpEntry) []map[string]any { + payload := make([]map[string]any, 0, len(entries)) + for _, entry := range entries { + if entry == nil { + continue + } + payload = append(payload, websocketSftpEntryPayload(entry)) + } + return payload +} + +func websocketSftpEntryPayload(entry *gatewayv1.SftpEntry) map[string]any { + return map[string]any{ + "path": entry.GetPath(), + "name": entry.GetName(), + "kind": strings.TrimSpace(entry.GetKind()), + "sizeBytes": entry.GetSizeBytes(), + "size_bytes": entry.GetSizeBytes(), + "mtime": entry.GetMtime(), + } +} + +func websocketSftpTransferPayload(transfer *gatewayv1.SftpTransfer) map[string]any { + return map[string]any{ + "id": strings.TrimSpace(transfer.GetId()), + "sessionId": strings.TrimSpace(transfer.GetSessionId()), + "session_id": strings.TrimSpace(transfer.GetSessionId()), + "direction": strings.TrimSpace(transfer.GetDirection()), + "status": strings.TrimSpace(transfer.GetStatus()), + "sourcePath": transfer.GetSourcePath(), + "source_path": transfer.GetSourcePath(), + "targetPath": transfer.GetTargetPath(), + "target_path": transfer.GetTargetPath(), + "currentPath": transfer.GetCurrentPath(), + "current_path": transfer.GetCurrentPath(), + "bytesDone": transfer.GetBytesDone(), + "bytes_done": transfer.GetBytesDone(), + "bytesTotal": transfer.GetBytesTotal(), + "bytes_total": transfer.GetBytesTotal(), + "filesDone": transfer.GetFilesDone(), + "files_done": transfer.GetFilesDone(), + "filesTotal": transfer.GetFilesTotal(), + "files_total": transfer.GetFilesTotal(), + "error": strings.TrimSpace(transfer.GetError()), + } +} diff --git a/crates/agent-gateway/internal/server/websocket_sftp_handlers_test.go b/crates/agent-gateway/internal/server/websocket_sftp_handlers_test.go new file mode 100644 index 000000000..502955182 --- /dev/null +++ b/crates/agent-gateway/internal/server/websocket_sftp_handlers_test.go @@ -0,0 +1,75 @@ +package server + +import ( + "testing" + + gatewayv1 "github.com/liveagent/agent-gateway/internal/proto/v1" +) + +func TestFirstNonEmptyRawPreservesSftpPathWhitespace(t *testing.T) { + t.Parallel() + + if got := firstNonEmptyRaw("", " spaced path "); got != " spaced path " { + t.Fatalf("firstNonEmptyRaw preserved path = %q", got) + } + if got := firstNonEmptyRaw(" ", "fallback"); got != " " { + t.Fatalf("firstNonEmptyRaw all-space path = %q", got) + } +} + +func TestWebsocketSftpPayloadPreservesPathWhitespace(t *testing.T) { + t.Parallel() + + payload := websocketSftpResponsePayload(&gatewayv1.SftpResponse{ + Action: " list ", + Path: " remote dir ", + Entries: []*gatewayv1.SftpEntry{ + { + Path: " remote dir/file ", + Name: " file ", + Kind: " file ", + }, + }, + Transfer: &gatewayv1.SftpTransfer{ + Id: " transfer-1 ", + SessionId: " session-1 ", + Direction: " upload ", + Status: " running ", + SourcePath: " local file ", + TargetPath: " remote dir ", + CurrentPath: " remote dir/file ", + }, + }) + + if got := payload["action"]; got != "list" { + t.Fatalf("action = %#v", got) + } + if got := payload["path"]; got != " remote dir " { + t.Fatalf("path = %#v", got) + } + + entries := payload["entries"].([]map[string]any) + if got := entries[0]["path"]; got != " remote dir/file " { + t.Fatalf("entry path = %#v", got) + } + if got := entries[0]["name"]; got != " file " { + t.Fatalf("entry name = %#v", got) + } + if got := entries[0]["kind"]; got != "file" { + t.Fatalf("entry kind = %#v", got) + } + + transfer := payload["transfer"].(map[string]any) + if got := transfer["id"]; got != "transfer-1" { + t.Fatalf("transfer id = %#v", got) + } + if got := transfer["sourcePath"]; got != " local file " { + t.Fatalf("sourcePath = %#v", got) + } + if got := transfer["targetPath"]; got != " remote dir " { + t.Fatalf("targetPath = %#v", got) + } + if got := transfer["currentPath"]; got != " remote dir/file " { + t.Fatalf("currentPath = %#v", got) + } +} diff --git a/crates/agent-gateway/internal/server/websocket_terminal_handlers.go b/crates/agent-gateway/internal/server/websocket_terminal_handlers.go index fd32b7e1a..3951abb09 100644 --- a/crates/agent-gateway/internal/server/websocket_terminal_handlers.go +++ b/crates/agent-gateway/internal/server/websocket_terminal_handlers.go @@ -11,18 +11,46 @@ func terminalActionFromRequestType(requestType string) string { return strings.TrimPrefix(strings.TrimSpace(requestType), "terminal.") } +func (c *websocketConnection) terminalFeaturesEnabled() bool { + return c.sm.WebTerminalEnabled() || c.sm.WebSshTerminalEnabled() +} + +func (c *websocketConnection) terminalSessionAllowed(session *gatewayv1.TerminalSession) bool { + if session == nil { + return false + } + if terminalSessionKind(session) == "ssh" { + return c.sm.WebSshTerminalEnabled() + } + return c.sm.WebTerminalEnabled() +} + +func (c *websocketConnection) terminalEventAllowed(event *gatewayv1.TerminalEvent) bool { + if event == nil { + return false + } + if session := event.GetSession(); session != nil { + return c.terminalSessionAllowed(session) + } + sessionID := strings.TrimSpace(event.GetSessionId()) + if sessionID != "" && c.sm.TerminalSessionKind(sessionID) == "ssh" { + return c.sm.WebSshTerminalEnabled() + } + return c.sm.WebTerminalEnabled() +} + func (c *websocketConnection) handleTerminalRequest(req websocketRequest) { action := terminalActionFromRequestType(req.Type) - if !c.sm.WebTerminalEnabled() { - _ = c.writeError(req.ID, "web terminal is disabled in desktop Remote settings") - return - } var body websocketTerminalRequestPayload if err := decodeWebSocketPayload(req.Payload, &body); err != nil { _ = c.writeError(req.ID, "invalid "+req.Type+" payload") return } + if !c.terminalRequestAllowed(action, body) { + _ = c.writeError(req.ID, terminalPermissionError(action)) + return + } cols, err := websocketOptionalUint32(body.Cols, "cols") if err != nil { @@ -59,6 +87,11 @@ func (c *websocketConnection) handleTerminalRequest(req websocketRequest) { Cols: cols, Rows: rows, MaxBytes: maxBytes, + SshHostId: strings.TrimSpace(body.SshHostID), + PromptId: strings.TrimSpace(body.PromptID), + PromptAnswer: body.PromptAnswer, + TrustHostKey: body.TrustHostKey, + SftpEnabled: body.SftpEnabled, }, }, }) @@ -76,10 +109,75 @@ func (c *websocketConnection) handleTerminalRequest(req websocketRequest) { _ = c.writeError(req.ID, "unexpected agent response") return } + resp = c.mergeTerminalListWithCachedSnapshot(action, projectPathKey, resp) c.sm.ApplyTerminalResponseSnapshot(action, projectPathKey, resp) - c.rememberTerminalInterest(action, body, resp) + filteredResp := c.filterTerminalResponseForPermissions(action, resp) + c.rememberTerminalInterest(action, body, filteredResp) + + _ = c.writeResponse(req.ID, websocketTerminalResponsePayload(filteredResp)) +} + +func (c *websocketConnection) mergeTerminalListWithCachedSnapshot( + action string, + projectPathKey string, + resp *gatewayv1.TerminalResponse, +) *gatewayv1.TerminalResponse { + if resp == nil || strings.TrimSpace(action) != "list" { + return resp + } + cachedSessions := c.sm.TerminalSessionSnapshot(projectPathKey) + if len(cachedSessions) == 0 { + return resp + } + seen := make(map[string]struct{}, len(resp.GetSessions())) + for _, session := range resp.GetSessions() { + id := strings.TrimSpace(session.GetId()) + if id != "" { + seen[id] = struct{}{} + } + } + merged := make([]*gatewayv1.TerminalSession, 0, len(resp.GetSessions())+len(cachedSessions)) + merged = append(merged, resp.GetSessions()...) + changed := false + for _, session := range cachedSessions { + id := strings.TrimSpace(session.GetId()) + if id == "" { + continue + } + if _, ok := seen[id]; ok { + continue + } + seen[id] = struct{}{} + merged = append(merged, session) + changed = true + } + if !changed { + return resp + } + clone := *resp + clone.Sessions = merged + return &clone +} - _ = c.writeResponse(req.ID, websocketTerminalResponsePayload(resp)) +func (c *websocketConnection) filterTerminalResponseForPermissions(action string, resp *gatewayv1.TerminalResponse) *gatewayv1.TerminalResponse { + if resp == nil || action != "list" { + return resp + } + filtered := make([]*gatewayv1.TerminalSession, 0, len(resp.GetSessions())) + changed := false + for _, session := range resp.GetSessions() { + if c.terminalSessionAllowed(session) { + filtered = append(filtered, session) + } else { + changed = true + } + } + if !changed { + return resp + } + clone := *resp + clone.Sessions = filtered + return &clone } func (c *websocketConnection) rememberTerminalInterest(action string, body websocketTerminalRequestPayload, resp *gatewayv1.TerminalResponse) { @@ -95,23 +193,50 @@ func (c *websocketConnection) rememberTerminalInterest(action string, body webso } switch action { - case "list", "create", "close_project": + case "list", "create", "create_ssh", "answer_ssh_prompt", "close_project": c.rememberTerminalProject(projectPathKey) case "attach", "snapshot": c.rememberTerminalSession(sessionID, projectPathKey) } } -func (c *websocketConnection) handleTerminalDetach(req websocketRequest) { - if !c.sm.WebTerminalEnabled() { - _ = c.writeError(req.ID, "web terminal is disabled in desktop Remote settings") - return +func (c *websocketConnection) terminalRequestAllowed(action string, body websocketTerminalRequestPayload) bool { + switch action { + case "create_ssh", "answer_ssh_prompt", "cancel_ssh_prompt", "ssh_latency": + return c.sm.WebSshTerminalEnabled() + case "list": + return c.sm.WebTerminalEnabled() || c.sm.WebSshTerminalEnabled() + case "close_project": + return c.sm.WebTerminalEnabled() || c.sm.WebSshTerminalEnabled() + case "attach", "snapshot", "input", "resize", "rename", "close", "detach": + if c.sm.TerminalSessionKind(body.SessionID) == "ssh" { + return c.sm.WebSshTerminalEnabled() + } + return c.sm.WebTerminalEnabled() + default: + return c.sm.WebTerminalEnabled() + } +} + +func terminalPermissionError(action string) string { + switch action { + case "create_ssh", "answer_ssh_prompt", "cancel_ssh_prompt", "ssh_latency": + return "web SSH terminal is disabled in desktop Remote settings" + default: + return "web terminal is disabled in desktop Remote settings" } +} + +func (c *websocketConnection) handleTerminalDetach(req websocketRequest) { var body websocketTerminalRequestPayload if err := decodeWebSocketPayload(req.Payload, &body); err != nil { _ = c.writeError(req.ID, "invalid terminal.detach payload") return } + if !c.terminalRequestAllowed("detach", body) { + _ = c.writeError(req.ID, terminalPermissionError("detach")) + return + } c.forgetTerminalInterest(body.SessionID, body.ProjectPathKey) _ = c.writeResponse(req.ID, map[string]any{"action": "detach"}) } diff --git a/crates/agent-gateway/internal/session/manager_chat_runs.go b/crates/agent-gateway/internal/session/manager_chat_runs.go index 1cf07a609..145ec2cf3 100644 --- a/crates/agent-gateway/internal/session/manager_chat_runs.go +++ b/crates/agent-gateway/internal/session/manager_chat_runs.go @@ -815,6 +815,11 @@ func (m *Manager) dispatchFromAgent(expected *AgentSession, env *gatewayv1.Agent return } + if sftpEvent := env.GetSftpEvent(); sftpEvent != nil { + m.broadcastSftpEvent(sftpEvent) + return + } + if tunnelFrame := env.GetTunnelFrame(); tunnelFrame != nil { m.dispatchTunnelFrame(tunnelFrame) return diff --git a/crates/agent-gateway/internal/session/manager_settings_sync.go b/crates/agent-gateway/internal/session/manager_settings_sync.go index 1651d55d5..09d132842 100644 --- a/crates/agent-gateway/internal/session/manager_settings_sync.go +++ b/crates/agent-gateway/internal/session/manager_settings_sync.go @@ -41,6 +41,18 @@ func (m *Manager) WebTerminalEnabled() bool { return ok && enabled } +func (m *Manager) WebSshTerminalEnabled() bool { + m.syncHub.settingsSnapshotMu.RLock() + defer m.syncHub.settingsSnapshotMu.RUnlock() + + remote, ok := m.syncHub.settingsSnapshot["remote"].(map[string]any) + if !ok { + return false + } + enabled, ok := remote["enableWebSshTerminal"].(bool) + return ok && enabled +} + func (m *Manager) WebGitEnabled() bool { m.syncHub.settingsSnapshotMu.RLock() defer m.syncHub.settingsSnapshotMu.RUnlock() diff --git a/crates/agent-gateway/internal/session/manager_sftp.go b/crates/agent-gateway/internal/session/manager_sftp.go new file mode 100644 index 000000000..8fad643b8 --- /dev/null +++ b/crates/agent-gateway/internal/session/manager_sftp.go @@ -0,0 +1,49 @@ +package session + +import ( + "time" + + gatewayv1 "github.com/liveagent/agent-gateway/internal/proto/v1" +) + +func (m *Manager) SubscribeSftpEvents() (<-chan *gatewayv1.SftpEvent, func()) { + ch := make(chan *gatewayv1.SftpEvent, 4096) + + m.syncHub.sftpMu.Lock() + subID := m.syncHub.nextSftpSubID + m.syncHub.nextSftpSubID += 1 + m.syncHub.sftpSubscribers[subID] = ch + m.syncHub.sftpMu.Unlock() + + cleanup := func() { + m.syncHub.sftpMu.Lock() + if _, ok := m.syncHub.sftpSubscribers[subID]; ok { + // Do not close the channel here: broadcastSftpEvent sends after + // copying subscribers, so closing can race with an in-flight send. + delete(m.syncHub.sftpSubscribers, subID) + } + m.syncHub.sftpMu.Unlock() + } + + return ch, cleanup +} + +func (m *Manager) broadcastSftpEvent(event *gatewayv1.SftpEvent) { + if event == nil { + return + } + + m.syncHub.sftpMu.Lock() + subscribers := make([]chan *gatewayv1.SftpEvent, 0, len(m.syncHub.sftpSubscribers)) + for _, ch := range m.syncHub.sftpSubscribers { + subscribers = append(subscribers, ch) + } + m.syncHub.sftpMu.Unlock() + + for _, ch := range subscribers { + select { + case ch <- event: + case <-time.After(50 * time.Millisecond): + } + } +} diff --git a/crates/agent-gateway/internal/session/manager_state.go b/crates/agent-gateway/internal/session/manager_state.go index 55c780bbe..e90d89a2d 100644 --- a/crates/agent-gateway/internal/session/manager_state.go +++ b/crates/agent-gateway/internal/session/manager_state.go @@ -40,6 +40,10 @@ type syncHub struct { nextTerminalSubID int terminalSubscribers map[int]chan *gatewayv1.TerminalEvent terminalSessions map[string]*gatewayv1.TerminalSession + + sftpMu sync.Mutex + nextSftpSubID int + sftpSubscribers map[int]chan *gatewayv1.SftpEvent } func newSyncHub() *syncHub { @@ -48,6 +52,7 @@ func newSyncHub() *syncHub { settingsSubscribers: make(map[int]chan *gatewayv1.SettingsSyncEvent), terminalSubscribers: make(map[int]chan *gatewayv1.TerminalEvent), terminalSessions: make(map[string]*gatewayv1.TerminalSession), + sftpSubscribers: make(map[int]chan *gatewayv1.SftpEvent), } } diff --git a/crates/agent-gateway/internal/session/manager_terminal.go b/crates/agent-gateway/internal/session/manager_terminal.go index 54416c741..971a60d05 100644 --- a/crates/agent-gateway/internal/session/manager_terminal.go +++ b/crates/agent-gateway/internal/session/manager_terminal.go @@ -48,9 +48,46 @@ func cloneTerminalSession(session *gatewayv1.TerminalSession) *gatewayv1.Termina FinishedAt: session.GetFinishedAt(), ExitCode: session.GetExitCode(), Running: session.GetRunning(), + Kind: session.GetKind(), + Ssh: cloneTerminalSshMetadata(session.GetSsh()), } } +func cloneTerminalSshMetadata(ssh *gatewayv1.TerminalSshMetadata) *gatewayv1.TerminalSshMetadata { + if ssh == nil { + return nil + } + return &gatewayv1.TerminalSshMetadata{ + HostId: ssh.GetHostId(), + HostName: ssh.GetHostName(), + Username: ssh.GetUsername(), + Host: ssh.GetHost(), + Port: ssh.GetPort(), + AuthType: ssh.GetAuthType(), + Status: ssh.GetStatus(), + ReconnectAttempt: ssh.GetReconnectAttempt(), + ReconnectMaxAttempts: ssh.GetReconnectMaxAttempts(), + SftpEnabled: ssh.GetSftpEnabled(), + } +} + +func (m *Manager) TerminalSessionKind(sessionID string) string { + sessionID = strings.TrimSpace(sessionID) + if sessionID == "" { + return "" + } + m.syncHub.terminalMu.Lock() + defer m.syncHub.terminalMu.Unlock() + session := m.syncHub.terminalSessions[sessionID] + if session == nil { + return "" + } + if strings.TrimSpace(session.GetKind()) == "ssh" { + return "ssh" + } + return "local" +} + func terminalSessionSortKey(session *gatewayv1.TerminalSession) (string, uint64, string) { if session == nil { return "", 0, "" @@ -153,7 +190,7 @@ func (m *Manager) ApplyTerminalResponseSnapshot( delete(m.syncHub.terminalSessions, sessionID) m.syncHub.terminalMu.Unlock() } - case "create", "attach", "snapshot", "input", "resize", "rename": + case "create", "create_ssh", "answer_ssh_prompt", "attach", "snapshot", "input", "resize", "rename": session := resp.GetSession() sessionID := strings.TrimSpace(session.GetId()) if sessionID == "" { diff --git a/crates/agent-gateway/internal/session/manager_test.go b/crates/agent-gateway/internal/session/manager_test.go index d707d6dd9..352ef2144 100644 --- a/crates/agent-gateway/internal/session/manager_test.go +++ b/crates/agent-gateway/internal/session/manager_test.go @@ -1,37 +1,134 @@ package session -import "testing" +import ( + "testing" + + gatewayv1 "github.com/liveagent/agent-gateway/internal/proto/v1" +) func TestApplySettingsJSONPreservingRemoteKeepsDesktopTerminalSetting(t *testing.T) { manager := NewManager() - manager.ApplySettingsJSON(`{"remote":{"enableWebTerminal":true},"theme":"dark"}`) + manager.ApplySettingsJSON(`{"remote":{"enableWebTerminal":true,"enableWebSshTerminal":true},"theme":"dark"}`) if !manager.WebTerminalEnabled() { t.Fatal("expected desktop settings sync to enable web terminal") } + if !manager.WebSshTerminalEnabled() { + t.Fatal("expected desktop settings sync to enable web SSH terminal") + } - manager.ApplySettingsJSONPreservingRemote(`{"remote":{"enableWebTerminal":false},"theme":"light"}`) + manager.ApplySettingsJSONPreservingRemote(`{"remote":{"enableWebTerminal":false,"enableWebSshTerminal":false},"theme":"light"}`) if !manager.WebTerminalEnabled() { t.Fatal("settings.update must not disable the desktop-owned web terminal setting") } + if !manager.WebSshTerminalEnabled() { + t.Fatal("settings.update must not disable the desktop-owned web SSH terminal setting") + } } func TestApplySettingsJSONKeepsRemoteWhenPublicSettingsEventOmitsIt(t *testing.T) { manager := NewManager() - manager.ApplySettingsJSON(`{"remote":{"enableWebTerminal":true},"theme":"dark"}`) + manager.ApplySettingsJSON(`{"remote":{"enableWebTerminal":true,"enableWebSshTerminal":true},"theme":"dark"}`) if !manager.WebTerminalEnabled() { t.Fatal("expected desktop settings sync to enable web terminal") } + if !manager.WebSshTerminalEnabled() { + t.Fatal("expected desktop settings sync to enable web SSH terminal") + } manager.ApplySettingsJSON(`{"theme":"light"}`) if !manager.WebTerminalEnabled() { t.Fatal("public settings events without remote must not clear the desktop web terminal setting") } + if !manager.WebSshTerminalEnabled() { + t.Fatal("public settings events without remote must not clear the desktop web SSH terminal setting") + } } func TestApplySettingsJSONPreservingRemoteDoesNotTrustIncomingRemote(t *testing.T) { manager := NewManager() - manager.ApplySettingsJSONPreservingRemote(`{"remote":{"enableWebTerminal":true}}`) + manager.ApplySettingsJSONPreservingRemote(`{"remote":{"enableWebTerminal":true,"enableWebSshTerminal":true}}`) if manager.WebTerminalEnabled() { t.Fatal("settings.update must not enable web terminal without a desktop settings snapshot") } + if manager.WebSshTerminalEnabled() { + t.Fatal("settings.update must not enable web SSH terminal without a desktop settings snapshot") + } +} + +func TestTerminalSessionSnapshotPreservesSshMetadataAndSorts(t *testing.T) { + manager := NewManager() + manager.ReplaceTerminalSessionSnapshot("", []*gatewayv1.TerminalSession{ + { + Id: "ssh-2", + ProjectPathKey: "/workspace/b", + Cwd: "/workspace/b", + Shell: "ssh", + Title: "Production 2", + Kind: "ssh", + CreatedAt: 2, + UpdatedAt: 2, + Running: true, + Ssh: &gatewayv1.TerminalSshMetadata{ + HostId: "prod-2", + HostName: "Production 2", + Username: "deploy", + Host: "prod-2.example.com", + Port: 22, + AuthType: "privateKey", + }, + }, + { + Id: "local-1", + ProjectPathKey: "/workspace/a", + Cwd: "/workspace/a", + Shell: "zsh", + Title: "Local", + Kind: "local", + CreatedAt: 2, + UpdatedAt: 2, + Running: true, + }, + { + Id: "ssh-1", + ProjectPathKey: "/workspace/a", + Cwd: "/workspace/a", + Shell: "ssh", + Title: "Production", + Kind: "ssh", + CreatedAt: 1, + UpdatedAt: 1, + Running: true, + Ssh: &gatewayv1.TerminalSshMetadata{ + HostId: "prod", + HostName: "Production", + Username: "deploy", + Host: "prod.example.com", + Port: 22, + AuthType: "password", + }, + }, + }) + + sessions := manager.TerminalSessionSnapshot("") + if len(sessions) != 3 { + t.Fatalf("terminal sessions = %d, want 3", len(sessions)) + } + if got := []string{sessions[0].GetId(), sessions[1].GetId(), sessions[2].GetId()}; got[0] != "ssh-1" || got[1] != "local-1" || got[2] != "ssh-2" { + t.Fatalf("terminal session order = %#v", got) + } + if manager.TerminalSessionKind("ssh-1") != "ssh" { + t.Fatalf("TerminalSessionKind(ssh-1) = %q, want ssh", manager.TerminalSessionKind("ssh-1")) + } + if sessions[0].GetSsh().GetHostId() != "prod" || sessions[0].GetSsh().GetAuthType() != "password" { + t.Fatalf("ssh metadata = %#v", sessions[0].GetSsh()) + } + + sessions[0].Ssh.HostId = "mutated" + fresh := manager.TerminalSessionSnapshot("/workspace/a") + if len(fresh) != 2 { + t.Fatalf("filtered terminal sessions = %d, want 2", len(fresh)) + } + if fresh[0].GetSsh().GetHostId() != "prod" { + t.Fatalf("terminal snapshot should be immutable, got ssh host id %q", fresh[0].GetSsh().GetHostId()) + } } diff --git a/crates/agent-gateway/proto/v1/gateway.proto b/crates/agent-gateway/proto/v1/gateway.proto index d15952d75..82944bca0 100644 --- a/crates/agent-gateway/proto/v1/gateway.proto +++ b/crates/agent-gateway/proto/v1/gateway.proto @@ -63,9 +63,11 @@ message GatewayEnvelope { GitRequest git_request = 61; FsReadEditableTextRequest fs_read_editable_text = 62; FsReadWorkspaceImageRequest fs_read_workspace_image = 63; + SftpRequest sftp_request = 64; TunnelControlRequest tunnel_control = 67; TunnelControlResponse tunnel_control_resp = 68; TunnelFrame tunnel_frame = 69; + SettingsResetSshKnownHostRequest settings_reset_ssh_known_host = 72; } } @@ -113,11 +115,14 @@ message AgentEnvelope { GitResponse git_response = 64; FsReadEditableTextResponse fs_read_editable_text_resp = 65; FsReadWorkspaceImageResponse fs_read_workspace_image_resp = 66; + SftpResponse sftp_response = 73; + SftpEvent sftp_event = 74; TunnelControlRequest tunnel_control = 67; TunnelControlResponse tunnel_control_resp = 68; TunnelFrame tunnel_frame = 69; ChatControlEvent chat_control = 70; RuntimeStatusEvent runtime_status = 71; + SettingsResetSshKnownHostResponse settings_reset_ssh_known_host_resp = 72; ErrorResponse error = 99; } } @@ -257,6 +262,11 @@ message TerminalRequest { uint32 cols = 8; uint32 rows = 9; uint32 max_bytes = 10; + string ssh_host_id = 11; + string prompt_id = 12; + string prompt_answer = 13; + bool trust_host_key = 14; + bool sftp_enabled = 15; } message TerminalSession { @@ -273,6 +283,86 @@ message TerminalSession { uint64 finished_at = 11; int32 exit_code = 12; bool running = 13; + string kind = 14; + TerminalSshMetadata ssh = 15; +} + +message TerminalSshMetadata { + string host_id = 1; + string host_name = 2; + string username = 3; + string host = 4; + uint32 port = 5; + string auth_type = 6; + string status = 7; + uint32 reconnect_attempt = 8; + uint32 reconnect_max_attempts = 9; + bool sftp_enabled = 10; +} + +message SftpRequest { + string action = 1; + string session_id = 2; + string project_path_key = 3; + string workdir = 4; + string local_path = 5; + string remote_path = 6; + string from_path = 7; + string to_path = 8; + string direction = 9; + string target_path = 10; + bool recursive = 11; + bool overwrite = 12; +} + +message SftpEntry { + string path = 1; + string name = 2; + string kind = 3; + uint64 size_bytes = 4; + uint64 mtime = 5; +} + +message SftpTransfer { + string id = 1; + string session_id = 2; + string direction = 3; + string status = 4; + string source_path = 5; + string target_path = 6; + string current_path = 7; + uint64 bytes_done = 8; + uint64 bytes_total = 9; + uint32 files_done = 10; + uint32 files_total = 11; + string error = 12; +} + +message SftpResponse { + string action = 1; + string path = 2; + repeated SftpEntry entries = 3; + SftpEntry entry = 4; + bool exists = 5; + SftpTransfer transfer = 6; +} + +message SftpEvent { + string kind = 1; + SftpTransfer transfer = 2; +} + +message TerminalSshPrompt { + string id = 1; + string kind = 2; + string host_id = 3; + string host_name = 4; + string host = 5; + uint32 port = 6; + string message = 7; + string fingerprint_sha256 = 8; + string key_type = 9; + bool answer_echo = 10; } message TerminalShellOption { @@ -291,6 +381,8 @@ message TerminalResponse { string default_shell = 7; uint64 output_start_offset = 8; uint64 output_end_offset = 9; + TerminalSshPrompt ssh_prompt = 10; + uint32 latency_ms = 11; } message TerminalEvent { @@ -534,6 +626,15 @@ message SettingsUpdateResponse { string message = 2; } +message SettingsResetSshKnownHostRequest { + string host = 1; + uint32 port = 2; +} + +message SettingsResetSshKnownHostResponse { + uint32 deleted = 1; +} + message SettingsSyncEvent { string settings_json = 1; } diff --git a/crates/agent-gateway/test/README.md b/crates/agent-gateway/test/README.md index 541e5dc01..a58eac37a 100644 --- a/crates/agent-gateway/test/README.md +++ b/crates/agent-gateway/test/README.md @@ -9,6 +9,7 @@ All project-level gateway tests live under `crates/agent-gateway/test` and are s | `upload/` | `/api/files/import` validation, multipart parsing, and agent forwarding | | `websocket/` | WebSocket auth, request forwarding, chat streaming, and cancellation-facing events | | `webui/` | Browser-side WebUI helpers, auth, upload normalization, history state, live stream state, and WebSocket client behavior | +| `../web/test/` | WebUI source-adjacent module tests for chat transcript, history scope, and live conversation commit helpers | | `helpers/` | Shared Node test module loader for WebUI TypeScript modules | Run Go-side tests from `crates/agent-gateway`: @@ -20,7 +21,7 @@ go test ./... Run WebUI Node tests from `crates/agent-gateway`: ```sh -node --test test/webui/*.test.mjs +node --test test/webui/*.test.mjs web/test/*.test.mjs ``` Run the WebUI type/build gate separately from `crates/agent-gateway/web`: diff --git a/crates/agent-gateway/test/websocket/chat_bridge_test.go b/crates/agent-gateway/test/websocket/chat_bridge_test.go index f644ad2c3..b6e9e192e 100644 --- a/crates/agent-gateway/test/websocket/chat_bridge_test.go +++ b/crates/agent-gateway/test/websocket/chat_bridge_test.go @@ -318,7 +318,7 @@ func TestWebSocketChatStartForwardsNormalizedRequestAndStreamsEvents(t *testing. } } -func TestWebSocketChatStartRequiresRuntimeReady(t *testing.T) { +func TestWebSocketChatStartWakesRuntimeWhenHeartbeatIsStale(t *testing.T) { t.Parallel() sm := session.NewManager() @@ -339,19 +339,29 @@ func TestWebSocketChatStartRequiresRuntimeReady(t *testing.T) { "message": "hello gateway", }) - env := receiveEnvelope(t, conn) - if env.ID != "chat-not-ready" || - env.Type != "error" || - !strings.Contains(env.Error, "Desktop chat runtime is not ready") { - t.Fatalf("not-ready response = %#v, want chat runtime readiness error", env) + outbound := readOutboundEnvelope(t, agentSession) + if outbound.GetRequestId() != "chat-not-ready" { + t.Fatalf("outbound request id = %q, want chat-not-ready", outbound.GetRequestId()) } - select { - case outbound := <-agentSession.Outbound(): - t.Fatalf("unexpected outbound request while runtime not ready: %#v", outbound) - case <-time.After(100 * time.Millisecond): + request := outbound.GetChatRequest() + if request == nil || request.GetMessage() != "hello gateway" { + t.Fatalf("outbound chat request = %#v", request) } - if got := sm.ActiveChatRunConversationIDs(); len(got) != 0 { - t.Fatalf("active chat runs after not-ready request = %#v, want empty", got) + + sm.DispatchFromAgent(&gatewayv1.AgentEnvelope{ + RequestId: "chat-not-ready", + Timestamp: time.Now().Unix(), + Payload: &gatewayv1.AgentEnvelope_ChatEvent{ + ChatEvent: &gatewayv1.ChatEvent{ + Type: gatewayv1.ChatEvent_DONE, + ConversationId: "conversation-not-ready", + Data: `{"type":"done"}`, + }, + }, + }) + done := receiveEnvelopeWithID(t, conn, "chat-not-ready") + if done.Type != "chat.event" { + t.Fatalf("done envelope = %#v, want chat.event", done) } } diff --git a/crates/agent-gateway/test/websocket/terminal_test.go b/crates/agent-gateway/test/websocket/terminal_test.go index a895d15b3..75c800a9b 100644 --- a/crates/agent-gateway/test/websocket/terminal_test.go +++ b/crates/agent-gateway/test/websocket/terminal_test.go @@ -42,13 +42,26 @@ func newTerminalWebSocketTest( webTerminalEnabled bool, ) (*session.Manager, *session.AgentSession, *websocket.Conn, func()) { t.Helper() + return newTerminalWebSocketTestWithPermissions(t, webTerminalEnabled, false) +} + +func newTerminalWebSocketTestWithPermissions( + t *testing.T, + webTerminalEnabled bool, + webSshTerminalEnabled bool, +) (*session.Manager, *session.AgentSession, *websocket.Conn, func()) { + t.Helper() sm := session.NewManager() webTerminalSetting := "false" if webTerminalEnabled { webTerminalSetting = "true" } - sm.ApplySettingsJSON(`{"remote":{"enableWebTerminal":` + webTerminalSetting + `}}`) + webSshTerminalSetting := "false" + if webSshTerminalEnabled { + webSshTerminalSetting = "true" + } + sm.ApplySettingsJSON(`{"remote":{"enableWebTerminal":` + webTerminalSetting + `,"enableWebSshTerminal":` + webSshTerminalSetting + `}}`) sm.RecordAuthentication("desktop-agent", "0.9.0", "session-1") agentSession := session.NewAgentSession(sm.LatestAuthSnapshot()) sm.SetSession(agentSession) @@ -62,6 +75,281 @@ func newTerminalWebSocketTest( return sm, agentSession, conn, cleanup } +func TestWebSocketSshTerminalPermissionIsIndependentFromLocalTerminal(t *testing.T) { + t.Parallel() + + sm, agentSession, conn, cleanup := newTerminalWebSocketTestWithPermissions(t, false, true) + defer cleanup() + + sendEnvelope(t, conn, "terminal-create-local-disabled", "terminal.create", map[string]any{ + "cwd": "/workspace/project", + "project_path_key": "/workspace/project", + }) + localResponse := receiveEnvelope(t, conn) + if localResponse.ID != "terminal-create-local-disabled" || localResponse.Type != "error" { + t.Fatalf("local terminal disabled response = %#v, want error", localResponse) + } + if !strings.Contains(localResponse.Error, "web terminal is disabled") { + t.Fatalf("local terminal disabled error = %q", localResponse.Error) + } + + sendEnvelope(t, conn, "terminal-create-ssh-enabled", "terminal.create_ssh", map[string]any{ + "cwd": " /workspace/project ", + "project_path_key": " /workspace/project ", + "ssh_host_id": " prod ", + "title": " Prod SSH ", + "cols": 120, + "rows": 32, + }) + outbound := readOutboundEnvelope(t, agentSession) + req := outbound.GetTerminalRequest() + if req == nil { + t.Fatalf("terminal.create_ssh outbound payload = %T, want TerminalRequest", outbound.GetPayload()) + } + if req.GetAction() != "create_ssh" || + req.GetCwd() != "/workspace/project" || + req.GetProjectPathKey() != "/workspace/project" || + req.GetSshHostId() != "prod" || + req.GetTitle() != "Prod SSH" || + req.GetCols() != 120 || + req.GetRows() != 32 { + t.Fatalf("terminal create_ssh request = %#v", req) + } + + sm.DispatchFromAgent(&gatewayv1.AgentEnvelope{ + RequestId: outbound.GetRequestId(), + Timestamp: time.Now().Unix(), + Payload: &gatewayv1.AgentEnvelope_TerminalResponse{ + TerminalResponse: &gatewayv1.TerminalResponse{ + Action: "create_ssh", + Session: &gatewayv1.TerminalSession{ + Id: "ssh-1", + ProjectPathKey: "/workspace/project", + Cwd: "/workspace/project", + Shell: "ssh", + Title: "Prod SSH", + Kind: "ssh", + Cols: 120, + Rows: 32, + CreatedAt: 1, + UpdatedAt: 2, + Running: true, + Ssh: &gatewayv1.TerminalSshMetadata{ + HostId: "prod", + HostName: "Production", + Username: "deploy", + Host: "prod.example.com", + Port: 22, + AuthType: "privateKey", + }, + }, + }, + }, + }) + createResponse := receiveEnvelopeWithID(t, conn, "terminal-create-ssh-enabled") + if createResponse.Type != "response" { + t.Fatalf("terminal create_ssh response = %#v, want response", createResponse) + } + var createPayload struct { + Session map[string]any `json:"session"` + } + if err := json.Unmarshal(createResponse.Payload, &createPayload); err != nil { + t.Fatalf("decode create_ssh response: %v", err) + } + if createPayload.Session["kind"] != "ssh" || createPayload.Session["pid"] != nil { + t.Fatalf("create_ssh session payload = %#v, want ssh with nil pid", createPayload.Session) + } + sshPayload, ok := createPayload.Session["ssh"].(map[string]any) + if !ok || sshPayload["host_id"] != "prod" || sshPayload["auth_type"] != "privateKey" { + t.Fatalf("create_ssh ssh metadata = %#v", createPayload.Session["ssh"]) + } + + sendEnvelope(t, conn, "terminal-input-ssh-enabled", "terminal.input", map[string]any{ + "session_id": " ssh-1 ", + "project_path_key": " /workspace/project ", + "data": "pwd\n", + }) + inputOutbound := readOutboundEnvelope(t, agentSession) + inputReq := inputOutbound.GetTerminalRequest() + if inputReq == nil { + t.Fatalf("ssh terminal input outbound payload = %T, want TerminalRequest", inputOutbound.GetPayload()) + } + if inputReq.GetAction() != "input" || + inputReq.GetSessionId() != "ssh-1" || + inputReq.GetData() != "pwd\n" { + t.Fatalf("ssh terminal input request = %#v", inputReq) + } +} + +func TestWebSocketSshTerminalCreateRejectedWithoutSshPermission(t *testing.T) { + t.Parallel() + + _, _, conn, cleanup := newTerminalWebSocketTestWithPermissions(t, true, false) + defer cleanup() + + sendEnvelope(t, conn, "terminal-create-ssh-disabled", "terminal.create_ssh", map[string]any{ + "cwd": "/workspace/project", + "project_path_key": "/workspace/project", + "ssh_host_id": "prod", + }) + + env := receiveEnvelope(t, conn) + if env.ID != "terminal-create-ssh-disabled" || env.Type != "error" { + t.Fatalf("ssh terminal disabled response = %#v, want error", env) + } + if !strings.Contains(env.Error, "web SSH terminal is disabled") { + t.Fatalf("ssh terminal disabled error = %q", env.Error) + } +} + +func TestWebSocketTerminalListFiltersLocalSessionsWhenOnlySshEnabled(t *testing.T) { + t.Parallel() + + sm, agentSession, conn, cleanup := newTerminalWebSocketTestWithPermissions(t, false, true) + defer cleanup() + + sendEnvelope(t, conn, "terminal-list-ssh-only", "terminal.list", map[string]any{ + "project_path_key": "/workspace/project", + }) + outbound := readOutboundEnvelope(t, agentSession) + req := outbound.GetTerminalRequest() + if req == nil || req.GetAction() != "list" { + t.Fatalf("terminal list outbound payload = %#v, want list request", outbound.GetPayload()) + } + + sm.DispatchFromAgent(&gatewayv1.AgentEnvelope{ + RequestId: outbound.GetRequestId(), + Timestamp: time.Now().Unix(), + Payload: &gatewayv1.AgentEnvelope_TerminalResponse{ + TerminalResponse: &gatewayv1.TerminalResponse{ + Action: "list", + Sessions: []*gatewayv1.TerminalSession{ + { + Id: "local-1", + ProjectPathKey: "/workspace/project", + Cwd: "/workspace/project", + Title: "Local", + Kind: "local", + CreatedAt: 1, + UpdatedAt: 1, + Running: true, + }, + { + Id: "ssh-1", + ProjectPathKey: "/workspace/project", + Cwd: "/workspace/project", + Shell: "ssh", + Title: "Production", + Kind: "ssh", + CreatedAt: 2, + UpdatedAt: 2, + Running: true, + Ssh: &gatewayv1.TerminalSshMetadata{ + HostId: "prod", + HostName: "Production", + Username: "deploy", + Host: "prod.example.com", + Port: 22, + AuthType: "password", + }, + }, + }, + }, + }, + }) + response := receiveEnvelopeWithID(t, conn, "terminal-list-ssh-only") + if response.Type != "response" { + t.Fatalf("terminal ssh-only list response = %#v, want response", response) + } + var payload struct { + Sessions []map[string]any `json:"sessions"` + } + if err := json.Unmarshal(response.Payload, &payload); err != nil { + t.Fatalf("decode ssh-only list response: %v", err) + } + if len(payload.Sessions) != 1 || + payload.Sessions[0]["id"] != "ssh-1" || + payload.Sessions[0]["kind"] != "ssh" { + t.Fatalf("ssh-only list sessions = %#v, want only ssh-1", payload.Sessions) + } +} + +func TestWebSocketTerminalListMergesCachedSshSessions(t *testing.T) { + t.Parallel() + + sm, agentSession, conn, cleanup := newTerminalWebSocketTestWithPermissions(t, false, true) + defer cleanup() + + sm.DispatchFromAgent(&gatewayv1.AgentEnvelope{ + RequestId: "event-created-cached-ssh", + Timestamp: time.Now().Unix(), + Payload: &gatewayv1.AgentEnvelope_TerminalEvent{ + TerminalEvent: &gatewayv1.TerminalEvent{ + Kind: "created", + SessionId: "ssh-1", + ProjectPathKey: "/workspace/project", + Session: &gatewayv1.TerminalSession{ + Id: "ssh-1", + ProjectPathKey: "/workspace/project", + Cwd: "/workspace/project", + Shell: "ssh", + Title: "Production", + Kind: "ssh", + CreatedAt: 2, + UpdatedAt: 2, + Running: true, + Ssh: &gatewayv1.TerminalSshMetadata{ + HostId: "prod", + HostName: "Production", + Username: "deploy", + Host: "prod.example.com", + Port: 22, + AuthType: "password", + Status: "connected", + }, + }, + }, + }, + }) + createdEvent := receiveEnvelope(t, conn) + if createdEvent.Type != "terminal.event" { + t.Fatalf("cached ssh created event = %#v, want terminal.event", createdEvent) + } + + sendEnvelope(t, conn, "terminal-list-merge-cached-ssh", "terminal.list", map[string]any{}) + outbound := readOutboundEnvelope(t, agentSession) + req := outbound.GetTerminalRequest() + if req == nil || req.GetAction() != "list" { + t.Fatalf("terminal list outbound payload = %#v, want list request", outbound.GetPayload()) + } + + sm.DispatchFromAgent(&gatewayv1.AgentEnvelope{ + RequestId: outbound.GetRequestId(), + Timestamp: time.Now().Unix(), + Payload: &gatewayv1.AgentEnvelope_TerminalResponse{ + TerminalResponse: &gatewayv1.TerminalResponse{ + Action: "list", + Sessions: []*gatewayv1.TerminalSession{}, + }, + }, + }) + response := receiveEnvelopeWithID(t, conn, "terminal-list-merge-cached-ssh") + if response.Type != "response" { + t.Fatalf("terminal cached ssh list response = %#v, want response", response) + } + var payload struct { + Sessions []map[string]any `json:"sessions"` + } + if err := json.Unmarshal(response.Payload, &payload); err != nil { + t.Fatalf("decode cached ssh list response: %v", err) + } + if len(payload.Sessions) != 1 || + payload.Sessions[0]["id"] != "ssh-1" || + payload.Sessions[0]["kind"] != "ssh" { + t.Fatalf("cached ssh list sessions = %#v, want ssh-1", payload.Sessions) + } +} + func TestWebSocketTerminalRejectsInteractiveRequestsWhenDisabled(t *testing.T) { t.Parallel() diff --git a/crates/agent-gateway/test/webui/gateway-socket-client.test.mjs b/crates/agent-gateway/test/webui/gateway-socket-client.test.mjs index 7905c433e..b72dde0a0 100644 --- a/crates/agent-gateway/test/webui/gateway-socket-client.test.mjs +++ b/crates/agent-gateway/test/webui/gateway-socket-client.test.mjs @@ -331,6 +331,89 @@ test("SharedWorker gateway client accepts terminal list sessions from worker pay resetGatewayWebSocketClient(); }); +test("SharedWorker gateway client keeps SSH create snapshots from worker payload", async () => { + installBrowser(); + FakeSharedWorker.instances = []; + globalThis.SharedWorker = FakeSharedWorker; + const loader = createWebModuleLoader(); + const { getGatewayWebSocketClient, resetGatewayWebSocketClient } = loader.loadModule("src/lib/gatewaySocket.ts"); + resetGatewayWebSocketClient(); + + const client = getGatewayWebSocketClient(" token "); + const port = FakeSharedWorker.instances[0].port; + const connect = port.messages.find((message) => message.type === "connect"); + port.emit({ + type: "ready", + connection_id: connect.connection_id, + payload: { status: { online: true }, error: null }, + }); + + const createPromise = client.createSshTerminal({ + cwd: "/workspace/project", + projectPathKey: "project-key", + hostId: "host-1", + }); + await waitFor( + () => port.messages.some((message) => message.type === "request" && message.method === "terminal.create_ssh"), + "terminal.create_ssh worker request", + ); + const request = port.messages.find((message) => message.type === "request" && message.method === "terminal.create_ssh"); + assert.ok(request); + assert.deepEqual(request.payload, { + cwd: "/workspace/project", + project_path_key: "project-key", + ssh_host_id: "host-1", + title: undefined, + cols: undefined, + rows: undefined, + sftp_enabled: false, + }); + port.emit({ + type: "response", + connection_id: connect.connection_id, + request_id: request.request_id, + payload: { + snapshot: { + session: { + id: "ssh-1", + projectPathKey: "project-key", + cwd: "/workspace/project", + shell: "ssh", + title: "Claw-SG", + kind: "ssh", + ssh: { + hostId: "host-1", + hostName: "Claw-SG", + username: "root", + host: "8.219.204.112", + port: 22, + authType: "privateKey", + status: "connected", + reconnectAttempt: 0, + reconnectMaxAttempts: 3, + }, + pid: null, + cols: 80, + rows: 24, + createdAt: 10, + updatedAt: 10, + running: true, + }, + output: "root@s878169:~# ", + truncated: false, + outputStartOffset: 0, + outputEndOffset: 18, + }, + }, + }); + + const result = await createPromise; + assert.equal(result.snapshot?.session.id, "ssh-1"); + assert.equal(result.snapshot?.session.ssh?.hostId, "host-1"); + assert.equal(result.snapshot?.output, "root@s878169:~# "); + resetGatewayWebSocketClient(); +}); + test("SharedWorker gateway client forwards chat runtime controls to the worker", async () => { installBrowser(); FakeSharedWorker.instances = []; @@ -444,6 +527,7 @@ test("Gateway SharedWorker broadcasts events with each port connection id", asyn historyListeners = []; conversationListeners = []; settingsListeners = []; + sftpTransferListeners = []; constructor(token) { this.token = token; @@ -474,6 +558,11 @@ test("Gateway SharedWorker broadcasts events with each port connection id", asyn return () => {}; } + subscribeSftpTransfers(listener) { + this.sftpTransferListeners.push(listener); + return () => {}; + } + dispose() {} } @@ -526,6 +615,31 @@ test("Gateway SharedWorker broadcasts events with each port connection id", asyn payload: historyEvent, }); + const sftpEvent = { + kind: "progress", + transfer: { + id: "transfer-1", + sessionId: "ssh-1", + status: "running", + bytesTransferred: 12, + totalBytes: 24, + }, + }; + clientInstances[0].sftpTransferListeners[0](sftpEvent); + + assert.deepEqual(firstPort.messages.at(-1), { + type: "event", + event_type: "sftp", + connection_id: "connection-1", + payload: sftpEvent, + }); + assert.deepEqual(secondPort.messages.at(-1), { + type: "event", + event_type: "sftp", + connection_id: "connection-2", + payload: sftpEvent, + }); + globalThis.onconnect = previousOnConnect; }); @@ -563,6 +677,10 @@ test("Gateway SharedWorker applies foreground wakeups to the managed socket clie return () => {}; } + subscribeSftpTransfers() { + return () => {}; + } + noteForegroundWakeup() { this.wakeups += 1; } @@ -628,6 +746,10 @@ test("Gateway SharedWorker terminal metadata reaches every page while output sta return () => {}; } + subscribeSftpTransfers() { + return () => {}; + } + async listTerminals(projectPathKey) { this.calls.push(["listTerminals", projectPathKey ?? ""]); return [ @@ -772,6 +894,10 @@ test("Gateway SharedWorker forwards terminal output while attach request is pend return () => {}; } + subscribeSftpTransfers() { + return () => {}; + } + snapshotTerminal(sessionId, maxBytes, projectPathKey) { this.calls.push(["snapshotTerminal", sessionId, maxBytes, projectPathKey]); return new Promise((resolve) => { @@ -891,6 +1017,10 @@ test("Gateway SharedWorker keeps upstream terminal attached until every port det return () => {}; } + subscribeSftpTransfers() { + return () => {}; + } + async snapshotTerminal(sessionId, maxBytes, projectPathKey) { this.calls.push(["snapshotTerminal", sessionId, maxBytes, projectPathKey]); return { @@ -1063,6 +1193,10 @@ test("Gateway SharedWorker forwards chat metadata and uploaded files", async () return () => {}; } + subscribeSftpTransfers() { + return () => {}; + } + async *chat(...args) { chatCalls.push(args); yield { type: "done", conversation_id: "conversation-1" }; @@ -1635,6 +1769,10 @@ test("Gateway SharedWorker forwards history share requests", async () => { return () => {}; } + subscribeSftpTransfers() { + return () => {}; + } + getHistoryShare(conversationID) { this.calls.push(["getHistoryShare", conversationID]); return { @@ -1776,6 +1914,10 @@ test("Gateway SharedWorker forwards tunnel requests", async () => { return () => {}; } + subscribeSftpTransfers() { + return () => {}; + } + listTunnels() { this.calls.push(["listTunnels"]); return [ @@ -1986,6 +2128,10 @@ test("Gateway SharedWorker forwards chat.attach streams to the requesting port", return () => {}; } + subscribeSftpTransfers() { + return () => {}; + } + async *attachChat(conversationID, options) { this.calls.push(["attachChat", conversationID, options.afterSeq]); yield { @@ -2082,6 +2228,10 @@ test("Gateway SharedWorker forwards conversation cancel without a stream id", as return () => {}; } + subscribeSftpTransfers() { + return () => {}; + } + async cancelChat(conversationID) { cancelCalls.push(conversationID); } diff --git a/crates/agent-gateway/test/webui/web-settings.test.mjs b/crates/agent-gateway/test/webui/web-settings.test.mjs index 60708b2e0..85c8f3fd8 100644 --- a/crates/agent-gateway/test/webui/web-settings.test.mjs +++ b/crates/agent-gateway/test/webui/web-settings.test.mjs @@ -202,6 +202,10 @@ test("loadWebSettings forces current gateway URL/token over stale persisted remo openProjectPathKeys: ["/stale/project"], openVersion: 1, }; + stale.customSettings.projectToolsSshTunnel = { + openProjectPathKeys: ["/stale/project"], + openVersion: 1, + }; store.set("liveagent.gateway.webui.settings.v1", JSON.stringify(stale)); const loaded = webSettings.loadWebSettings(" new-token "); @@ -212,6 +216,7 @@ test("loadWebSettings forces current gateway URL/token over stale persisted remo assert.deepEqual(loaded.customSettings.projectToolsFileTree.openProjectPathKeys, []); assert.deepEqual(loaded.customSettings.projectToolsGitReview.openProjectPathKeys, []); assert.deepEqual(loaded.customSettings.projectToolsTunnel.openProjectPathKeys, []); + assert.deepEqual(loaded.customSettings.projectToolsSshTunnel.openProjectPathKeys, []); }); test("gateway settings sync keeps remote connection local and syncs web terminal setting", () => { @@ -253,12 +258,346 @@ test("gateway settings sync keeps remote connection local and syncs web terminal const payload = settingsSync.buildGatewaySettingsSyncPayload(synced); assert.deepEqual(payload.remote, { enableWebTerminal: synced.remote.enableWebTerminal, + enableWebSshTerminal: synced.remote.enableWebSshTerminal, enableWebGit: synced.remote.enableWebGit, enableWebTunnels: synced.remote.enableWebTunnels, }); assert.deepEqual(payload.chatRuntimeControls, synced.chatRuntimeControls); }); +test("ssh settings sync redacts stored secrets and carries one-shot secret updates", () => { + installWindow(); + const source = settings.normalizeSettings({ + ssh: { + hosts: [ + { + id: "ssh-prod", + name: "Prod", + description: "Production jump host", + host: "prod.example.com", + port: 2222, + username: "deploy", + authType: "privateKey", + password: "ssh-password", + privateKey: "-----BEGIN OPENSSH PRIVATE KEY-----", + privateKeyPath: "~/.ssh/prod", + proxy: { + type: "http", + url: "http://127.0.0.1", + port: 1080, + username: "proxy-user", + password: "proxy-password", + }, + }, + ], + projectHostAssociations: { + "/project-a": ["ssh-prod", "missing-host", "ssh-prod"], + " ": ["ssh-prod"], + }, + }, + }); + assert.deepEqual(source.ssh.projectHostAssociations, { + "/project-a": ["ssh-prod"], + }); + + const redacted = settingsSync.redactSettingsForWebStorage(source); + assert.deepEqual(redacted.ssh.projectHostAssociations, { + "/project-a": ["ssh-prod"], + }); + assert.equal(redacted.ssh.hosts[0].password, ""); + assert.equal(redacted.ssh.hosts[0].privateKey, ""); + assert.equal(redacted.ssh.hosts[0].proxy.type, "http"); + assert.equal(redacted.ssh.hosts[0].proxy.password, ""); + assert.equal(redacted.ssh.hosts[0].passwordConfigured, true); + assert.equal(redacted.ssh.hosts[0].privateKeyConfigured, true); + assert.equal(redacted.ssh.hosts[0].proxy.passwordConfigured, true); + + const publicPayload = settingsSync.buildGatewaySettingsSyncPayload(source); + assert.deepEqual(publicPayload.ssh.projectHostAssociations, { + "/project-a": ["ssh-prod"], + }); + assert.equal(publicPayload.ssh.hosts[0].password, ""); + assert.equal(publicPayload.ssh.hosts[0].privateKey, ""); + assert.equal(publicPayload.ssh.hosts[0].proxy.type, "http"); + assert.equal(publicPayload.ssh.hosts[0].proxy.password, ""); + assert.equal(publicPayload.ssh.hosts[0].passwordConfigured, true); + assert.equal(publicPayload.ssh.hosts[0].privateKeyConfigured, true); + assert.equal(publicPayload.ssh.hosts[0].proxy.passwordConfigured, true); + assert.equal(Object.hasOwn(publicPayload, "sshSecretUpdates"), false); + + const privatePayload = settingsSync.buildGatewaySettingsSyncPayload(source, { + includeProviderApiKeyUpdates: true, + }); + assert.deepEqual(privatePayload.ssh.projectHostAssociations, { + "/project-a": ["ssh-prod"], + }); + assert.equal(privatePayload.ssh.hosts[0].password, ""); + assert.equal(privatePayload.ssh.hosts[0].privateKey, ""); + assert.equal(privatePayload.ssh.hosts[0].proxy.type, "http"); + assert.equal(privatePayload.ssh.hosts[0].proxy.password, ""); + assert.deepEqual(privatePayload.sshSecretUpdates, { + "ssh-prod": { + password: "ssh-password", + privateKey: "-----BEGIN OPENSSH PRIVATE KEY-----", + proxyPassword: "proxy-password", + }, + }); +}); + +test("ssh settings sync merges one-shot secret updates into existing hosts", () => { + installWindow(); + const current = settings.normalizeSettings({ + ssh: { + hosts: [ + { + id: "ssh-prod", + name: "Prod", + host: "prod.example.com", + username: "deploy", + authType: "password", + password: "old-password", + privateKey: "old-key", + proxy: { + type: "socks5", + url: "socks5://127.0.0.1", + port: 1080, + username: "proxy-user", + password: "old-proxy-password", + }, + }, + ], + }, + }); + + const synced = settingsSync.applyGatewaySettingsSyncPayload(current, { + ssh: { + hosts: [ + { + id: "ssh-prod", + name: "Prod", + host: "prod.example.com", + username: "deploy", + authType: "privateKey", + password: "", + passwordConfigured: true, + privateKey: "", + privateKeyPath: "~/.ssh/prod", + privateKeyConfigured: true, + proxy: { + type: "http", + url: "http://127.0.0.1", + port: 1080, + username: "proxy-user", + password: "", + passwordConfigured: true, + }, + }, + ], + projectHostAssociations: { + "/project-a": ["ssh-prod"], + }, + }, + sshSecretUpdates: { + "ssh-prod": { + password: "new-password", + privateKey: "new-key", + proxyPassword: "new-proxy-password", + }, + }, + }); + + assert.equal(synced.ssh.hosts[0].authType, "privateKey"); + assert.equal(synced.ssh.hosts[0].password, "new-password"); + assert.equal(synced.ssh.hosts[0].privateKey, "new-key"); + assert.equal(synced.ssh.hosts[0].proxy.type, "http"); + assert.equal(synced.ssh.hosts[0].proxy.password, "new-proxy-password"); + assert.equal(synced.ssh.hosts[0].passwordConfigured, true); + assert.equal(synced.ssh.hosts[0].privateKeyConfigured, true); + assert.equal(synced.ssh.hosts[0].proxy.passwordConfigured, true); + assert.deepEqual(synced.ssh.projectHostAssociations, { + "/project-a": ["ssh-prod"], + }); +}); + +test("ssh settings sync preserves project host associations when older payload omits them", () => { + installWindow(); + const current = settings.normalizeSettings({ + ssh: { + hosts: [ + { + id: "ssh-prod", + name: "Prod", + host: "prod.example.com", + username: "deploy", + authType: "password", + }, + ], + projectHostAssociations: { + "/project-a": ["ssh-prod"], + }, + }, + }); + + const preserved = settingsSync.applyGatewaySettingsSyncPayload(current, { + ssh: { + hosts: [ + { + id: "ssh-prod", + name: "Prod", + host: "prod.example.com", + username: "deploy", + authType: "password", + }, + ], + }, + }); + assert.deepEqual(preserved.ssh.projectHostAssociations, { + "/project-a": ["ssh-prod"], + }); + + const cleared = settingsSync.applyGatewaySettingsSyncPayload(current, { + ssh: { + hosts: [ + { + id: "ssh-prod", + name: "Prod", + host: "prod.example.com", + username: "deploy", + authType: "password", + }, + ], + projectHostAssociations: {}, + }, + }); + assert.deepEqual(cleared.ssh.projectHostAssociations, {}); +}); + +test("settings update payload omits unchanged empty ssh hosts for non-ssh updates", () => { + installWindow(); + const desktop = settings.normalizeSettings({ + ssh: { + hosts: [ + { + id: "ssh-prod", + name: "Prod", + host: "prod.example.com", + username: "deploy", + authType: "password", + }, + ], + projectHostAssociations: { + "/project-a": ["ssh-prod"], + }, + }, + }); + const staleWeb = settings.normalizeSettings({ + customSettings: { + projectToolsSshTunnel: { + openProjectPathKeys: [], + openVersion: 0, + }, + }, + ssh: { + hosts: [], + projectHostAssociations: {}, + }, + }); + const nextWeb = settings.updateProjectToolsSshTunnelOpen(staleWeb, "/project-a", true); + + const update = settingsSync.buildGatewaySettingsSyncUpdatePayload(staleWeb, nextWeb, { + includeProviderApiKeyUpdates: true, + }); + + assert.equal(Object.hasOwn(update, "ssh"), false); + assert.equal(Object.hasOwn(update, "customSettings"), true); + + const merged = settingsSync.applyGatewaySettingsSyncPayload(desktop, update); + assert.deepEqual( + merged.ssh.hosts.map((host) => host.id), + ["ssh-prod"], + ); + assert.deepEqual(merged.ssh.projectHostAssociations, { + "/project-a": ["ssh-prod"], + }); + assert.equal(settings.isProjectToolsSshTunnelOpen(merged.customSettings, "/project-a"), true); +}); + +test("settings update payload includes ssh when hosts are explicitly deleted", () => { + installWindow(); + const current = settings.normalizeSettings({ + ssh: { + hosts: [ + { + id: "ssh-prod", + name: "Prod", + host: "prod.example.com", + username: "deploy", + authType: "password", + }, + ], + projectHostAssociations: { + "/project-a": ["ssh-prod"], + }, + }, + }); + const deleted = settings.updateSsh(current, { + hosts: [], + projectHostAssociations: {}, + }); + + const update = settingsSync.buildGatewaySettingsSyncUpdatePayload(current, deleted, { + includeProviderApiKeyUpdates: true, + }); + + assert.equal(Object.hasOwn(update, "ssh"), true); + assert.deepEqual(update.ssh.hosts, []); + assert.deepEqual(update.ssh.projectHostAssociations, {}); +}); + +test("settings update payload includes ssh for secret-only ssh updates", () => { + installWindow(); + const current = settings.normalizeSettings({ + ssh: { + hosts: [ + { + id: "ssh-prod", + name: "Prod", + host: "prod.example.com", + username: "deploy", + authType: "password", + password: "old-password", + }, + ], + }, + }); + const next = settings.normalizeSettings({ + ...current, + ssh: { + ...current.ssh, + hosts: [ + { + ...current.ssh.hosts[0], + password: "new-password", + }, + ], + }, + }); + + const update = settingsSync.buildGatewaySettingsSyncUpdatePayload(current, next, { + includeProviderApiKeyUpdates: true, + }); + + assert.equal(Object.hasOwn(update, "ssh"), true); + assert.deepEqual(update.sshSecretUpdates, { + "ssh-prod": { + password: "new-password", + }, + }); + + const merged = settingsSync.applyGatewaySettingsSyncPayload(current, update); + assert.equal(merged.ssh.hosts[0].password, "new-password"); +}); + test("workspace project selection stays out of synced system workdir", () => { installWindow(); const resolvedSystem = settings.resolveWorkspaceProjects( @@ -366,6 +705,10 @@ test("gateway settings sync keeps newer project tool tab open state", () => { openProjectPathKeys: ["/web/project"], openVersion: 2, }, + projectToolsSshTunnel: { + openProjectPathKeys: ["/web/project"], + openVersion: 2, + }, }, }); @@ -386,6 +729,10 @@ test("gateway settings sync keeps newer project tool tab open state", () => { openProjectPathKeys: [], openVersion: 1, }, + projectToolsSshTunnel: { + openProjectPathKeys: [], + openVersion: 1, + }, }, }); assert.deepEqual(staleSynced.customSettings.projectToolsGitReview.openProjectPathKeys, [ @@ -396,6 +743,10 @@ test("gateway settings sync keeps newer project tool tab open state", () => { "/web/project", ]); assert.equal(staleSynced.customSettings.projectToolsTunnel.openVersion, 2); + assert.deepEqual(staleSynced.customSettings.projectToolsSshTunnel.openProjectPathKeys, [ + "/web/project", + ]); + assert.equal(staleSynced.customSettings.projectToolsSshTunnel.openVersion, 2); assert.equal(staleSynced.customSettings.projectToolsPanel.width, 612); assert.equal(staleSynced.customSettings.projectToolsPanel.activeTab, "gitReview"); assert.deepEqual(staleSynced.customSettings.projectToolsPanel.activeTabs, { @@ -425,6 +776,10 @@ test("gateway settings sync keeps newer project tool tab open state", () => { openProjectPathKeys: ["/desktop/project"], openVersion: 3, }, + projectToolsSshTunnel: { + openProjectPathKeys: ["/desktop/project"], + openVersion: 3, + }, }, }); assert.deepEqual(newerSynced.customSettings.projectToolsGitReview.openProjectPathKeys, [ @@ -435,6 +790,10 @@ test("gateway settings sync keeps newer project tool tab open state", () => { "/desktop/project", ]); assert.equal(newerSynced.customSettings.projectToolsTunnel.openVersion, 3); + assert.deepEqual(newerSynced.customSettings.projectToolsSshTunnel.openProjectPathKeys, [ + "/desktop/project", + ]); + assert.equal(newerSynced.customSettings.projectToolsSshTunnel.openVersion, 3); assert.equal(newerSynced.customSettings.projectToolsPanel.width, 612); assert.equal(newerSynced.customSettings.projectToolsPanel.activeTab, "gitReview"); assert.deepEqual(newerSynced.customSettings.projectToolsPanel.activeTabs, { @@ -454,6 +813,10 @@ test("gateway settings sync keeps newer project tool tab open state", () => { openProjectPathKeys: ["/desktop/project"], openVersion: 3, }); + assert.deepEqual(payload.customSettings.projectToolsSshTunnel, { + openProjectPathKeys: ["/desktop/project"], + openVersion: 3, + }); }); test("gateway settings sync keeps newer project conversation activity", () => { diff --git a/crates/agent-gateway/web/src/App.tsx b/crates/agent-gateway/web/src/App.tsx index 973d607b5..b61a6a932 100644 --- a/crates/agent-gateway/web/src/App.tsx +++ b/crates/agent-gateway/web/src/App.tsx @@ -31,6 +31,7 @@ import { Button } from "@/components/ui/button"; import { ScrollArea } from "@/components/ui/scroll-area"; import { ProjectToolsPanel } from "@/components/project-tools/ProjectToolsPanel"; import type { WorkspaceCodeEditorOpenRequest } from "@/components/workspace-editor/WorkspaceCodeEditorOverlay"; +import type { WorkspaceSshTerminalOpenRequest } from "@/components/workspace-editor/WorkspaceSshTerminalOverlay"; import type { WorkspaceImagePreviewOpenRequest } from "@/components/workspace-editor/WorkspaceImagePreviewOverlay"; import { isWorkspaceImagePath } from "@/components/workspace-editor/workspaceImagePreview"; import { LocaleContext, t as translate } from "@/i18n"; @@ -67,9 +68,11 @@ import { getProjectToolsFileTreeProjectState, getProjectToolsPanelActiveTab, getProjectToolsPanelTabOrder, + getSshProjectHostIds, isAgentDevMode, isProjectToolsFileTreeOpen, isProjectToolsGitReviewOpen, + isProjectToolsSshTunnelOpen, isProjectToolsTunnelOpen, normalizeChatRuntimeControlsForProvider, normalizeSettings, @@ -81,9 +84,11 @@ import { updateProjectToolsFileTreeProjectState, updateProjectToolsFileTreeOpen, updateProjectToolsGitReviewOpen, + updateProjectToolsSshTunnelOpen, updateProjectToolsTunnelOpen, updateProjectToolsPanelActiveTab, updateProjectToolsPanelTabOrder, + updateSshProjectHostIds, type AppSettings, type ChatRuntimeControls, type CustomProvider, @@ -94,6 +99,7 @@ import { import { applyGatewaySettingsSyncPayload, buildGatewaySettingsSyncPayload, + buildGatewaySettingsSyncUpdatePayload, redactSettingsForWebStorage, type GatewaySettingsSyncPayload, } from "@/lib/settings/sync"; @@ -101,6 +107,7 @@ import { toModelValue } from "@/lib/providers/llm"; import { getGatewayWebSocketClient, resetGatewayWebSocketClient } from "./lib/gatewaySocket"; import { createGatewayGitClient } from "./lib/git/gatewayGitClient"; +import { createGatewaySftpClient } from "./lib/sftp/gatewaySftpClient"; import { createGatewayTerminalClient } from "./lib/terminal/gatewayTerminalClient"; import { applyTerminalEventToSessions, @@ -176,6 +183,7 @@ import { GatewayTranscript } from "./components/GatewayTranscript"; import { HistoryShareModal } from "./components/chat/HistoryShareModal"; import { useGatewayScrollAffordance } from "./components/useGatewayScrollAffordance"; import { LoginPage } from "./pages/LoginPage"; +import { SettingsSyncLoading } from "./pages/SettingsSyncLoading"; import { SharedHistoryPage } from "./pages/SharedHistoryPage"; import { WorkdirPickerModal } from "./pages/settings/WorkdirPickerModal"; import { @@ -232,6 +240,13 @@ const WorkspaceImagePreviewOverlay = lazy(async () => { }; }); +const WorkspaceSshTerminalOverlay = lazy(async () => { + const module = await import("@/components/workspace-editor/WorkspaceSshTerminalOverlay"); + return { + default: module.WorkspaceSshTerminalOverlay, + }; +}); + const MAX_UPLOAD_FILES = 9; function dragEventHasFiles(event: DragEvent) { @@ -386,6 +401,10 @@ const LIVE_STREAM_HISTORY_REFRESH_SUPPRESS_MS = 30_000; const PAGE_RESTORE_HISTORY_REFRESH_THROTTLE_MS = 900; const CHAT_RUNTIME_PREPARE_TIMEOUT_MS = 2_500; const CHAT_RUNTIME_FOREGROUND_PREPARE_TIMEOUT_MS = 1_500; +const CHAT_RUNTIME_KEEP_WARM_INTERVAL_MS = 10_000; +const CHAT_RUNTIME_STARTING_STATUS_DELAY_MS = 1_200; +const CHAT_RUNTIME_READY_STATUS_TTL_MS = 20_000; +const CHAT_RUNTIME_STARTING_STATUS = "Starting desktop runtime..."; const DEFAULT_BROWSER_TITLE = "LiveAgent Gateway"; const NEW_CONVERSATION_BROWSER_TITLE = "LiveAgent"; const SHARED_HISTORY_BROWSER_TITLE = "分享会话"; @@ -444,6 +463,33 @@ function normalizeGatewayTimestampMs(value: number | null | undefined) { return value < SECONDS_TIMESTAMP_MAX ? value * 1000 : value; } +function isChatRuntimeReadyStatus( + status: AgentStatus | null | undefined, + now = Date.now(), +) { + if (status?.online !== true) { + return false; + } + + const runtimeState = normalizeOptionalStatus(status.runtime_state)?.toLowerCase() ?? ""; + if (runtimeState === "suspended") { + return false; + } + + const hasReadyState = + runtimeState === "ready" || runtimeState === "draining" || runtimeState === "busy"; + if (status.chat_runtime_ready !== true && !hasReadyState) { + return false; + } + + const runtimeHeartbeatAt = normalizeGatewayTimestampMs(status.runtime_last_heartbeat); + if (runtimeHeartbeatAt <= 0) { + return status.chat_runtime_ready === true; + } + + return now - runtimeHeartbeatAt <= CHAT_RUNTIME_READY_STATUS_TTL_MS; +} + function isAbortError(error: unknown) { if ( (error instanceof DOMException && error.name === "AbortError") || @@ -598,10 +644,6 @@ function hasSettingsSyncChanged(prev: AppSettings, next: AppSettings) { ); } -function hasProviderApiKeyUpdates(settings: AppSettings) { - return settings.customProviders.some((provider) => provider.apiKey.trim().length > 0); -} - function resolveAppWorkspaceProjects(settings: AppSettings): AppSettings { return { ...settings, @@ -782,6 +824,7 @@ export default function App() { const [authError, setAuthError] = useState(null); const api = useMemo(() => (token ? getGatewayWebSocketClient(token) : null), [token]); const terminalClient = useMemo(() => (api ? createGatewayTerminalClient(api) : null), [api]); + const sftpClient = useMemo(() => (api ? createGatewaySftpClient(api) : null), [api]); const gitClient = useMemo(() => (api ? createGatewayGitClient(api) : null), [api]); const [status, setStatus] = useState(null); const [statusError, setStatusError] = useState(null); @@ -910,6 +953,11 @@ export default function App() { const [workspaceImagePreviewOpenRequest, setWorkspaceImagePreviewOpenRequest] = useState(null); const workspaceImagePreviewRequestIdRef = useRef(0); + const [workspaceSshTerminalMounted, setWorkspaceSshTerminalMounted] = useState(false); + const [workspaceSshTerminalOpen, setWorkspaceSshTerminalOpen] = useState(false); + const [workspaceSshTerminalOpenRequest, setWorkspaceSshTerminalOpenRequest] = + useState(null); + const workspaceSshTerminalRequestIdRef = useRef(0); const [terminalSessions, setTerminalSessions] = useState([]); const { confirm: requestConfirmDialog, dialog: confirmDialog } = useConfirmDialog(); const terminalSessionsVersionRef = useRef(0); @@ -2107,10 +2155,16 @@ export default function App() { }, [token]); const queueSettingsSave = useCallback( - (next: AppSettings, fallback: string, syncGateway: boolean) => { + (prev: AppSettings, next: AppSettings, fallback: string, syncGateway: boolean) => { const saveSequence = ++settingsSaveSequenceRef.current; setSettingsSaveState({ status: "saving" }); const redactedNext = redactSettingsForWebStorage(next); + const gatewayUpdate = + syncGateway && api + ? buildGatewaySettingsSyncUpdatePayload(prev, next, { + includeProviderApiKeyUpdates: true, + }) + : null; settingsSaveChainRef.current = settingsSaveChainRef.current .catch(() => undefined) @@ -2118,12 +2172,8 @@ export default function App() { persistWebSettings(redactedNext); }) .then(async () => { - if (syncGateway && api) { - await api.updateSettings( - buildGatewaySettingsSyncPayload(next, { - includeProviderApiKeyUpdates: true, - }), - ); + if (gatewayUpdate && Object.keys(gatewayUpdate).length > 0) { + await api?.updateSettings(gatewayUpdate); } }) .then(() => { @@ -2151,7 +2201,7 @@ export default function App() { if (!hasSettingsSyncChanged(prev, next)) { return prev; } - queueSettingsSave(next, "同步桌面端设置失败。", false); + queueSettingsSave(prev, next, "同步桌面端设置失败。", false); return next; }); }, @@ -2164,9 +2214,10 @@ export default function App() { const rawNext = resolveAppWorkspaceProjects(normalizeSettings(updater(prev))); const next = redactSettingsForWebStorage(rawNext); queueSettingsSave( + prev, rawNext, "保存 WebUI 设置失败。", - hasSettingsSyncChanged(prev, next) || hasProviderApiKeyUpdates(rawNext), + true, ); return next; }); @@ -2584,6 +2635,7 @@ export default function App() { } const unsubscribe = api.subscribeStatus((nextStatus, error) => { + statusRef.current = nextStatus; setStatus(nextStatus); setStatusError(error); }); @@ -4234,6 +4286,32 @@ export default function App() { }; }, [api, historyShareToken, prepareChatRuntime, status?.online]); + useEffect(() => { + if (!api || historyShareToken || status?.online !== true) { + return; + } + + const keepWarm = () => { + if ( + typeof document !== "undefined" && + document.visibilityState === "hidden" + ) { + return; + } + void prepareChatRuntime( + "keep-warm", + api, + CHAT_RUNTIME_FOREGROUND_PREPARE_TIMEOUT_MS, + ).catch(() => undefined); + }; + + keepWarm(); + const intervalId = window.setInterval(keepWarm, CHAT_RUNTIME_KEEP_WARM_INTERVAL_MS); + return () => { + window.clearInterval(intervalId); + }; + }, [api, historyShareToken, prepareChatRuntime, status?.online]); + async function sendChat(message: string, options?: SendChatOptions) { if (!api || chatBusyRef.current) { return; @@ -4310,7 +4388,7 @@ export default function App() { updateConversationRuntimeEntry(activeConversationId, (current) => ({ ...current, error: null, - toolStatus: "Starting desktop runtime...", + toolStatus: null, toolStatusIsCompaction: false, isSending: true, workdir: effectiveWorkdir || undefined, @@ -4346,12 +4424,63 @@ export default function App() { let terminalEventSeen = false; let runStarted = false; + let runtimeStartingStatusTimer: number | null = null; + const clearRuntimeStartingStatusTimer = () => { + if (runtimeStartingStatusTimer === null) { + return; + } + window.clearTimeout(runtimeStartingStatusTimer); + runtimeStartingStatusTimer = null; + }; + const clearRuntimeStartingStatus = () => { + updateConversationRuntimeEntry(activeConversationId, (current) => { + if (current.toolStatus !== CHAT_RUNTIME_STARTING_STATUS) { + return current; + } + return { + ...current, + toolStatus: null, + toolStatusIsCompaction: false, + }; + }); + }; + const shouldShowRuntimeStartingStatus = () => + !isChatRuntimeReadyStatus(statusRef.current); + const scheduleRuntimeStartingStatusTimer = () => { + clearRuntimeStartingStatusTimer(); + if (!shouldShowRuntimeStartingStatus()) { + return; + } + runtimeStartingStatusTimer = window.setTimeout(() => { + runtimeStartingStatusTimer = null; + if ( + runStarted || + terminalEventSeen || + controller.signal.aborted || + !shouldShowRuntimeStartingStatus() + ) { + return; + } + updateConversationRuntimeEntry(activeConversationId, (current) => { + if (!current.isSending || current.toolStatus) { + return current; + } + return { + ...current, + toolStatus: CHAT_RUNTIME_STARTING_STATUS, + toolStatusIsCompaction: false, + }; + }); + }, CHAT_RUNTIME_STARTING_STATUS_DELAY_MS); + }; const markRunStarted = () => { if (runStarted) { return; } runStarted = true; + clearRuntimeStartingStatusTimer(); setConversationRunningState(activeConversationId, true); + clearRuntimeStartingStatus(); }; const runtimeControls = normalizeChatRuntimeControlsForProvider( options?.runtimeControls ?? settings.chatRuntimeControls, @@ -4362,10 +4491,19 @@ export default function App() { ); try { chatStartInFlightRef.current = true; - const preparedStatus = await prepareChatRuntime("send", api, CHAT_RUNTIME_PREPARE_TIMEOUT_MS); - if (preparedStatus.chat_runtime_ready !== true) { - throw new Error("Desktop chat runtime is not ready. Please retry."); - } + scheduleRuntimeStartingStatusTimer(); + // chat.start is itself the reliable wake-up signal for a suspended desktop + // WebView. Keep the status refresh in the background so a stale runtime + // heartbeat cannot block the request that would wake it. + void prepareChatRuntime("send", api, CHAT_RUNTIME_PREPARE_TIMEOUT_MS) + .then((nextStatus) => { + statusRef.current = nextStatus; + if (isChatRuntimeReadyStatus(nextStatus)) { + clearRuntimeStartingStatusTimer(); + clearRuntimeStartingStatus(); + } + }) + .catch(() => undefined); for await (const event of api.chat( message, isLocalDraftConversationId(activeConversationId) ? undefined : activeConversationId, @@ -4428,6 +4566,7 @@ export default function App() { })); } else if (isTerminalChatControlEvent(event)) { terminalEventSeen = true; + clearRuntimeStartingStatusTimer(); clearConversationStreamingState(activeConversationId); if (event.type === "failed" || event.state === "failed") { updateConversationRuntimeEntry(activeConversationId, (current) => ({ @@ -4494,6 +4633,7 @@ export default function App() { } } } finally { + clearRuntimeStartingStatusTimer(); chatStartInFlightRef.current = false; clearConversationStreamingState(activeConversationId); if (status?.online && !terminalEventSeen) { @@ -4746,7 +4886,11 @@ export default function App() { current.filter((session) => !terminalSessionBelongsToProject(session, pathKey)), ); }; - if (terminalClient && settings.remote.enableWebTerminal && pathKey) { + if ( + terminalClient && + (settings.remote.enableWebTerminal || settings.remote.enableWebSshTerminal) && + pathKey + ) { terminalSessionsToClose = await terminalClient.list(pathKey); const runningTerminalCount = terminalSessionsToClose.filter( (session) => session.running, @@ -4875,6 +5019,7 @@ export default function App() { refreshHistoryWorkdirs, removeWorkspaceProjectFromSettings, requestConfirmDialog, + settings.remote.enableWebSshTerminal, settings.remote.enableWebTerminal, settings.locale, settings.system, @@ -5875,6 +6020,11 @@ export default function App() { settings.customSettings, terminalProjectPathKey, ); + const projectToolsSshTunnelOpen = isProjectToolsSshTunnelOpen( + settings.customSettings, + terminalProjectPathKey, + ); + const associatedSshHostIds = getSshProjectHostIds(settings.ssh, terminalProjectPathKey); const projectToolsDisabledMessage = !settingsSyncReady ? "Syncing desktop settings..." : !isAgentMode @@ -5887,6 +6037,8 @@ export default function App() { (!settings.remote.enableWebTerminal ? "Enable WebUI Terminal in desktop Remote settings." : undefined); + const webTerminalSessionsEnabled = + settings.remote.enableWebTerminal || settings.remote.enableWebSshTerminal; const gitDisabledMessage = !settings.remote.enableWebGit ? "WebUI Git is disabled in desktop Remote settings." : undefined; @@ -5899,6 +6051,22 @@ export default function App() { : status?.online !== true ? translate("projectTools.tunnelRemoteOffline", settings.locale) : undefined; + const hideWorkspaceSshTerminalOverlay = useCallback(() => { + setWorkspaceSshTerminalOpen(false); + }, []); + const openWorkspaceSshTerminalRequest = useCallback( + (request: WorkspaceSshTerminalOpenRequest) => { + setWorkspaceImagePreviewOpen(false); + setWorkspaceEditorOpen(false); + setWorkspaceSshTerminalMounted(true); + setWorkspaceSshTerminalOpen(true); + setWorkspaceSshTerminalOpenRequest(request); + }, + [], + ); + const requestWorkspaceEditorClose = useCallback(() => { + setWorkspaceEditorCloseRequestId((current) => current + 1); + }, []); const handleOpenWorkspaceFile = useCallback( (path: string) => { if (!terminalProjectPath || !terminalProjectPathKey) return; @@ -5914,6 +6082,7 @@ export default function App() { }); return; } + hideWorkspaceSshTerminalOverlay(); setWorkspaceImagePreviewOpen(false); workspaceEditorRequestIdRef.current += 1; setWorkspaceEditorCleanupPending(false); @@ -5926,12 +6095,21 @@ export default function App() { path, }); }, - [terminalProjectPath, terminalProjectPathKey], + [hideWorkspaceSshTerminalOverlay, terminalProjectPath, terminalProjectPathKey], + ); + const handleOpenSshTerminal = useCallback( + (session: TerminalSession, kind: WorkspaceSshTerminalOpenRequest["kind"] = "bash") => { + if (session.kind !== "ssh") return; + workspaceSshTerminalRequestIdRef.current += 1; + const openRequest = { + id: workspaceSshTerminalRequestIdRef.current, + sessionId: session.id, + kind, + }; + openWorkspaceSshTerminalRequest(openRequest); + }, + [openWorkspaceSshTerminalRequest], ); - - const requestWorkspaceEditorClose = useCallback(() => { - setWorkspaceEditorCloseRequestId((current) => current + 1); - }, []); const requestWorkspaceImagePreviewClose = useCallback(() => { setWorkspaceImagePreviewOpen(false); }, []); @@ -5974,11 +6152,9 @@ export default function App() { const handleProjectTerminalSessionsChange = useCallback( (sessions: TerminalSession[]) => { terminalSessionsVersionRef.current += 1; - setTerminalSessions((current) => - replaceTerminalSessionsForProject(current, terminalProjectPathKey, sessions), - ); + setTerminalSessions(sortTerminalSessions(sessions)); }, - [terminalProjectPathKey], + [], ); useEffect(() => { @@ -5990,7 +6166,7 @@ export default function App() { if (!settingsSyncReady) { return; } - if (!isAgentMode || !settings.remote.enableWebTerminal || status?.online === false) { + if (!isAgentMode || !webTerminalSessionsEnabled || status?.online === false) { terminalSessionsVersionRef.current += 1; setTerminalSessions([]); return; @@ -6022,11 +6198,11 @@ export default function App() { }; }, [ isAgentMode, - settings.remote.enableWebTerminal, settingsSyncReady, status?.online, status?.session_id, terminalClient, + webTerminalSessionsEnabled, ]); useEffect(() => { @@ -6038,6 +6214,44 @@ export default function App() { }); }, [terminalClient]); + useEffect(() => { + if (!terminalClient) return; + if (!settingsSyncReady) return; + if (!isAgentMode || !webTerminalSessionsEnabled || status?.online !== true) return; + if (!projectToolsSshTunnelOpen || !terminalProjectPathKey) return; + + let cancelled = false; + let refreshSeq = 0; + const refreshProjectTerminalSessions = () => { + const seq = ++refreshSeq; + void terminalClient + .list(terminalProjectPathKey) + .then((sessions) => { + if (cancelled || seq !== refreshSeq) return; + terminalSessionsVersionRef.current += 1; + setTerminalSessions((current) => + replaceTerminalSessionsForProject(current, terminalProjectPathKey, sessions), + ); + }) + .catch(() => undefined); + }; + + refreshProjectTerminalSessions(); + const timer = window.setInterval(refreshProjectTerminalSessions, 5_000); + return () => { + cancelled = true; + window.clearInterval(timer); + }; + }, [ + isAgentMode, + projectToolsSshTunnelOpen, + settingsSyncReady, + status?.online, + terminalClient, + terminalProjectPathKey, + webTerminalSessionsEnabled, + ]); + useEffect(() => { if (activeView !== "chat") { return; @@ -6407,7 +6621,7 @@ export default function App() {
-
正在同步桌面端设置...
+
@@ -7018,6 +7232,25 @@ export default function App() { /> ) : null} + {workspaceSshTerminalMounted && terminalClient && sftpClient ? ( + + {translate("workspaceSshTerminal.loading", settings.locale)} + + } + > + setWorkspaceSshTerminalOpen(false)} + /> + + ) : null} {terminalClient ? ( @@ -7026,7 +7259,7 @@ export default function App() { collapseImmediately={activeView !== "chat"} projectPathKey={terminalProjectPathKey} cwd={terminalProjectPath} - sessions={projectTerminalSessions} + sessions={terminalSessions} width={settings.customSettings.projectToolsPanel.width} theme={settings.theme} disabledMessage={projectToolsDisabledMessage} @@ -7046,6 +7279,9 @@ export default function App() { terminalProjectPathKey, )} tunnelOpen={projectToolsTunnelOpen} + sshTunnelOpen={projectToolsSshTunnelOpen} + sshHosts={settings.ssh.hosts} + associatedSshHostIds={associatedSshHostIds} client={terminalClient} gitClient={gitClient} gitWriteEnabled={settings.remote.enableWebGit} @@ -7092,6 +7328,15 @@ export default function App() { onTunnelOpenChange={(open) => setSettings((prev) => updateProjectToolsTunnelOpen(prev, terminalProjectPathKey, open)) } + onSshTunnelOpenChange={(open) => + setSettings((prev) => + updateProjectToolsSshTunnelOpen(prev, terminalProjectPathKey, open), + ) + } + onSshProjectHostIdsChange={(hostIds) => + setSettings((prev) => updateSshProjectHostIds(prev, terminalProjectPathKey, hostIds)) + } + onOpenSshSession={handleOpenSshTerminal} onSessionsChange={handleProjectTerminalSessionsChange} onInsertFileMention={(path, kind) => { composerRef.current?.insertFileMention(path, kind); diff --git a/crates/agent-gateway/web/src/components/git/GitBranchSelector.tsx b/crates/agent-gateway/web/src/components/git/GitBranchSelector.tsx index 60075c48b..f26b1e971 100644 --- a/crates/agent-gateway/web/src/components/git/GitBranchSelector.tsx +++ b/crates/agent-gateway/web/src/components/git/GitBranchSelector.tsx @@ -480,7 +480,7 @@ export function GitBranchSelector(props: { {loading || mutating || initializing ? ( - + ) : ( - + )} {label} diff --git a/crates/agent-gateway/web/src/components/icons.tsx b/crates/agent-gateway/web/src/components/icons.tsx index 59c087d5b..8cd7ea03c 100644 --- a/crates/agent-gateway/web/src/components/icons.tsx +++ b/crates/agent-gateway/web/src/components/icons.tsx @@ -3,7 +3,84 @@ import type { ComponentType, SVGProps } from "react"; import AlertCircleSource from "~icons/lucide/circle-alert"; import AlertTriangleSource from "~icons/lucide/triangle-alert"; import ClaudeSource from "~icons/logos/claude-icon"; +import DefaultFileSource from "~icons/vscode-icons/default-file"; +import FileTypeApacheSource from "~icons/vscode-icons/file-type-apache"; +import FileTypeAudioSource from "~icons/vscode-icons/file-type-audio"; +import FileTypeBinarySource from "~icons/vscode-icons/file-type-binary"; +import FileTypeBunSource from "~icons/vscode-icons/file-type-bun"; +import FileTypeCSource from "~icons/vscode-icons/file-type-c"; +import FileTypeCargoSource from "~icons/vscode-icons/file-type-cargo"; +import FileTypeCertSource from "~icons/vscode-icons/file-type-cert"; +import FileTypeCmakeSource from "~icons/vscode-icons/file-type-cmake"; +import FileTypeConfigSource from "~icons/vscode-icons/file-type-config"; +import FileTypeCppSource from "~icons/vscode-icons/file-type-cpp"; +import FileTypeCsharpSource from "~icons/vscode-icons/file-type-csharp"; +import FileTypeCssSource from "~icons/vscode-icons/file-type-css"; +import FileTypeDartSource from "~icons/vscode-icons/file-type-dartlang"; +import FileTypeDbSource from "~icons/vscode-icons/file-type-db"; +import FileTypeDockerSource from "~icons/vscode-icons/file-type-docker"; +import FileTypeDotenvSource from "~icons/vscode-icons/file-type-dotenv"; +import FileTypeEslintSource from "~icons/vscode-icons/file-type-eslint"; +import FileTypeExcelSource from "~icons/vscode-icons/file-type-excel"; +import FileTypeFontSource from "~icons/vscode-icons/file-type-font"; import FileTypeGeminiSource from "~icons/vscode-icons/file-type-gemini"; +import FileTypeGitSource from "~icons/vscode-icons/file-type-git"; +import FileTypeGoSource from "~icons/vscode-icons/file-type-go"; +import FileTypeGoWorkSource from "~icons/vscode-icons/file-type-go-work"; +import FileTypeGradleSource from "~icons/vscode-icons/file-type-gradle"; +import FileTypeGraphqlSource from "~icons/vscode-icons/file-type-graphql"; +import FileTypeHtmlSource from "~icons/vscode-icons/file-type-html"; +import FileTypeImageSource from "~icons/vscode-icons/file-type-image"; +import FileTypeIniSource from "~icons/vscode-icons/file-type-ini"; +import FileTypeJavaSource from "~icons/vscode-icons/file-type-java"; +import FileTypeJsSource from "~icons/vscode-icons/file-type-js"; +import FileTypeJsConfigSource from "~icons/vscode-icons/file-type-jsconfig"; +import FileTypeJsonSource from "~icons/vscode-icons/file-type-json"; +import FileTypeKeySource from "~icons/vscode-icons/file-type-key"; +import FileTypeKotlinSource from "~icons/vscode-icons/file-type-kotlin"; +import FileTypeLicenseSource from "~icons/vscode-icons/file-type-license"; +import FileTypeLogSource from "~icons/vscode-icons/file-type-log"; +import FileTypeMarkdownSource from "~icons/vscode-icons/file-type-markdown"; +import FileTypeMavenSource from "~icons/vscode-icons/file-type-maven"; +import FileTypeNginxSource from "~icons/vscode-icons/file-type-nginx"; +import FileTypeNodeSource from "~icons/vscode-icons/file-type-node"; +import FileTypeNpmSource from "~icons/vscode-icons/file-type-npm"; +import FileTypePackageSource from "~icons/vscode-icons/file-type-package"; +import FileTypePdfSource from "~icons/vscode-icons/file-type-pdf2"; +import FileTypePhpSource from "~icons/vscode-icons/file-type-php"; +import FileTypePnpmSource from "~icons/vscode-icons/file-type-pnpm"; +import FileTypePowerpointSource from "~icons/vscode-icons/file-type-powerpoint"; +import FileTypePowershellSource from "~icons/vscode-icons/file-type-powershell"; +import FileTypePrettierSource from "~icons/vscode-icons/file-type-prettier"; +import FileTypePrismaSource from "~icons/vscode-icons/file-type-prisma"; +import FileTypePythonSource from "~icons/vscode-icons/file-type-python"; +import FileTypeReactJsSource from "~icons/vscode-icons/file-type-reactjs"; +import FileTypeReactTsSource from "~icons/vscode-icons/file-type-reactts"; +import FileTypeRubySource from "~icons/vscode-icons/file-type-ruby"; +import FileTypeRustSource from "~icons/vscode-icons/file-type-rust"; +import FileTypeScssSource from "~icons/vscode-icons/file-type-scss"; +import FileTypeShellSource from "~icons/vscode-icons/file-type-shell"; +import FileTypeSqlSource from "~icons/vscode-icons/file-type-sql"; +import FileTypeSqliteSource from "~icons/vscode-icons/file-type-sqlite"; +import FileTypeSvelteSource from "~icons/vscode-icons/file-type-svelte"; +import FileTypeSwiftSource from "~icons/vscode-icons/file-type-swift"; +import FileTypeSystemdSource from "~icons/vscode-icons/file-type-systemd"; +import FileTypeTerraformSource from "~icons/vscode-icons/file-type-terraform"; +import FileTypeTextSource from "~icons/vscode-icons/file-type-text"; +import FileTypeTomlSource from "~icons/vscode-icons/file-type-toml"; +import FileTypeTsConfigSource from "~icons/vscode-icons/file-type-tsconfig"; +import FileTypeTsSource from "~icons/vscode-icons/file-type-typescript"; +import FileTypeTsDefSource from "~icons/vscode-icons/file-type-typescriptdef"; +import FileTypeVideoSource from "~icons/vscode-icons/file-type-video"; +import FileTypeViteSource from "~icons/vscode-icons/file-type-vite"; +import FileTypeVitestSource from "~icons/vscode-icons/file-type-vitest"; +import FileTypeVueSource from "~icons/vscode-icons/file-type-vue"; +import FileTypeWebpackSource from "~icons/vscode-icons/file-type-webpack"; +import FileTypeWordSource from "~icons/vscode-icons/file-type-word"; +import FileTypeXmlSource from "~icons/vscode-icons/file-type-xml"; +import FileTypeYamlSource from "~icons/vscode-icons/file-type-yaml"; +import FileTypeYarnSource from "~icons/vscode-icons/file-type-yarn"; +import FileTypeZipSource from "~icons/vscode-icons/file-type-zip"; import ArrowLeftSource from "~icons/lucide/arrow-left"; import ArrowRightSource from "~icons/lucide/arrow-right"; import BanSource from "~icons/lucide/ban"; @@ -44,8 +121,10 @@ import HomeSource from "~icons/lucide/house"; import ImageIconSource from "~icons/lucide/image"; import ImageOffSource from "~icons/lucide/image-off"; import KeySource from "~icons/lucide/key"; +import LayoutGridSource from "~icons/lucide/layout-grid"; import Link2Source from "~icons/lucide/link-2"; import LightbulbSource from "~icons/lucide/lightbulb"; +import ListSource from "~icons/lucide/list"; import Loader2Source from "~icons/lucide/loader-circle"; import LockSource from "~icons/lucide/lock"; import LogOutSource from "~icons/lucide/log-out"; @@ -136,6 +215,83 @@ export const AlertCircle = createIcon(AlertCircleSource); export const AlertTriangle = createIcon(AlertTriangleSource); export const ClaudeIcon = createIcon(ClaudeSource); export const FileTypeGeminiIcon = createIcon(FileTypeGeminiSource); +export const DefaultFile = createIcon(DefaultFileSource); +export const FileTypeApache = createIcon(FileTypeApacheSource); +export const FileTypeAudio = createIcon(FileTypeAudioSource); +export const FileTypeBinary = createIcon(FileTypeBinarySource); +export const FileTypeBun = createIcon(FileTypeBunSource); +export const FileTypeC = createIcon(FileTypeCSource); +export const FileTypeCargo = createIcon(FileTypeCargoSource); +export const FileTypeCert = createIcon(FileTypeCertSource); +export const FileTypeCmake = createIcon(FileTypeCmakeSource); +export const FileTypeConfig = createIcon(FileTypeConfigSource); +export const FileTypeCpp = createIcon(FileTypeCppSource); +export const FileTypeCsharp = createIcon(FileTypeCsharpSource); +export const FileTypeCss = createIcon(FileTypeCssSource); +export const FileTypeDart = createIcon(FileTypeDartSource); +export const FileTypeDb = createIcon(FileTypeDbSource); +export const FileTypeDocker = createIcon(FileTypeDockerSource); +export const FileTypeDotenv = createIcon(FileTypeDotenvSource); +export const FileTypeEslint = createIcon(FileTypeEslintSource); +export const FileTypeExcel = createIcon(FileTypeExcelSource); +export const FileTypeFont = createIcon(FileTypeFontSource); +export const FileTypeGit = createIcon(FileTypeGitSource); +export const FileTypeGo = createIcon(FileTypeGoSource); +export const FileTypeGoWork = createIcon(FileTypeGoWorkSource); +export const FileTypeGradle = createIcon(FileTypeGradleSource); +export const FileTypeGraphql = createIcon(FileTypeGraphqlSource); +export const FileTypeHtml = createIcon(FileTypeHtmlSource); +export const FileTypeImage = createIcon(FileTypeImageSource); +export const FileTypeIni = createIcon(FileTypeIniSource); +export const FileTypeJava = createIcon(FileTypeJavaSource); +export const FileTypeJs = createIcon(FileTypeJsSource); +export const FileTypeJsConfig = createIcon(FileTypeJsConfigSource); +export const FileTypeJson = createIcon(FileTypeJsonSource); +export const FileTypeKey = createIcon(FileTypeKeySource); +export const FileTypeKotlin = createIcon(FileTypeKotlinSource); +export const FileTypeLicense = createIcon(FileTypeLicenseSource); +export const FileTypeLog = createIcon(FileTypeLogSource); +export const FileTypeMarkdown = createIcon(FileTypeMarkdownSource); +export const FileTypeMaven = createIcon(FileTypeMavenSource); +export const FileTypeNginx = createIcon(FileTypeNginxSource); +export const FileTypeNode = createIcon(FileTypeNodeSource); +export const FileTypeNpm = createIcon(FileTypeNpmSource); +export const FileTypePackage = createIcon(FileTypePackageSource); +export const FileTypePdf = createIcon(FileTypePdfSource); +export const FileTypePhp = createIcon(FileTypePhpSource); +export const FileTypePnpm = createIcon(FileTypePnpmSource); +export const FileTypePowerpoint = createIcon(FileTypePowerpointSource); +export const FileTypePowershell = createIcon(FileTypePowershellSource); +export const FileTypePrettier = createIcon(FileTypePrettierSource); +export const FileTypePrisma = createIcon(FileTypePrismaSource); +export const FileTypePython = createIcon(FileTypePythonSource); +export const FileTypeReactJs = createIcon(FileTypeReactJsSource); +export const FileTypeReactTs = createIcon(FileTypeReactTsSource); +export const FileTypeRuby = createIcon(FileTypeRubySource); +export const FileTypeRust = createIcon(FileTypeRustSource); +export const FileTypeScss = createIcon(FileTypeScssSource); +export const FileTypeShell = createIcon(FileTypeShellSource); +export const FileTypeSql = createIcon(FileTypeSqlSource); +export const FileTypeSqlite = createIcon(FileTypeSqliteSource); +export const FileTypeSvelte = createIcon(FileTypeSvelteSource); +export const FileTypeSwift = createIcon(FileTypeSwiftSource); +export const FileTypeSystemd = createIcon(FileTypeSystemdSource); +export const FileTypeTerraform = createIcon(FileTypeTerraformSource); +export const FileTypeText = createIcon(FileTypeTextSource); +export const FileTypeToml = createIcon(FileTypeTomlSource); +export const FileTypeTs = createIcon(FileTypeTsSource); +export const FileTypeTsConfig = createIcon(FileTypeTsConfigSource); +export const FileTypeTsDef = createIcon(FileTypeTsDefSource); +export const FileTypeVideo = createIcon(FileTypeVideoSource); +export const FileTypeVite = createIcon(FileTypeViteSource); +export const FileTypeVitest = createIcon(FileTypeVitestSource); +export const FileTypeVue = createIcon(FileTypeVueSource); +export const FileTypeWebpack = createIcon(FileTypeWebpackSource); +export const FileTypeWord = createIcon(FileTypeWordSource); +export const FileTypeXml = createIcon(FileTypeXmlSource); +export const FileTypeYaml = createIcon(FileTypeYamlSource); +export const FileTypeYarn = createIcon(FileTypeYarnSource); +export const FileTypeZip = createIcon(FileTypeZipSource); export const ArrowLeft = createIcon(ArrowLeftSource); export const ArrowRight = createIcon(ArrowRightSource); export const Ban = createIcon(BanSource); @@ -176,8 +332,10 @@ export const Home = createIcon(HomeSource); export const ImageIcon = createIcon(ImageIconSource); export const ImageOff = createIcon(ImageOffSource); export const Key = createIcon(KeySource); +export const LayoutGrid = createIcon(LayoutGridSource); export const Link2 = createIcon(Link2Source); export const Lightbulb = createIcon(LightbulbSource); +export const List = createIcon(ListSource); export const Loader2 = createIcon(Loader2Source); export const Lock = createIcon(LockSource); export const LogOut = createIcon(LogOutSource); diff --git a/crates/agent-gateway/web/src/components/project-tools/LocalTunnelPanel.tsx b/crates/agent-gateway/web/src/components/project-tools/LocalTunnelPanel.tsx index 0738ddcc5..3ec40e7ba 100644 --- a/crates/agent-gateway/web/src/components/project-tools/LocalTunnelPanel.tsx +++ b/crates/agent-gateway/web/src/components/project-tools/LocalTunnelPanel.tsx @@ -111,6 +111,25 @@ function TtlSegmented({ ); } +function normalizeTunnelHostname(hostname: string) { + return hostname.toLowerCase().replace(/^\[/, "").replace(/\]$/, ""); +} + +function isIpv4Address(hostname: string) { + const parts = hostname.split("."); + if (parts.length !== 4) return false; + return parts.every((part) => { + if (!/^\d+$/.test(part)) return false; + const value = Number(part); + return value >= 0 && value <= 255 && String(value) === part; + }); +} + +function isIpAddress(hostname: string) { + if (isIpv4Address(hostname)) return true; + return hostname.includes(":"); +} + function validateLocalHttpTarget(input: string) { const value = input.trim(); if (!value) return "projectTools.tunnelTargetRequired"; @@ -119,8 +138,8 @@ function validateLocalHttpTarget(input: string) { if (url.protocol !== "http:") { return "projectTools.tunnelInvalidUrl"; } - const hostname = url.hostname.toLowerCase(); - if (!["localhost", "127.0.0.1", "::1", "[::1]"].includes(hostname)) { + const hostname = normalizeTunnelHostname(url.hostname); + if (hostname !== "localhost" && !isIpAddress(hostname)) { return "projectTools.tunnelLocalhostOnly"; } if (url.username || url.password || url.hash) { diff --git a/crates/agent-gateway/web/src/components/project-tools/ProjectToolsPanel.tsx b/crates/agent-gateway/web/src/components/project-tools/ProjectToolsPanel.tsx index baf973971..00fc3b1c1 100644 --- a/crates/agent-gateway/web/src/components/project-tools/ProjectToolsPanel.tsx +++ b/crates/agent-gateway/web/src/components/project-tools/ProjectToolsPanel.tsx @@ -1,7 +1,3 @@ -import "@xterm/xterm/css/xterm.css"; - -import { FitAddon } from "@xterm/addon-fit"; -import { Terminal as XTerm } from "@xterm/xterm"; import { type CSSProperties, type KeyboardEvent as ReactKeyboardEvent, @@ -19,12 +15,13 @@ import type { ProjectToolsFileTreeProjectState, ProjectToolsFileTreeStatePatch, ProjectToolsPanelTab, + SshHostConfig, } from "@/lib/settings"; +import { workspaceProjectPathKey } from "@/lib/settings"; import type { GitClient } from "@/lib/git/types"; import { cn } from "@/lib/shared/utils"; import type { TerminalClient, - TerminalEvent, TerminalSession, TerminalShellOption, TerminalSnapshot, @@ -36,6 +33,7 @@ import { GitBranch, Globe, GripVertical, + Key, Plus, Terminal, X, @@ -57,6 +55,8 @@ import { } from "./GitReviewPanel"; import { LocalTunnelPanel, type LocalTunnelClient } from "./LocalTunnelPanel"; import { ProjectFileTreePanel } from "./ProjectFileTreePanel"; +import { SshTunnelPanel } from "./SshTunnelPanel"; +import { XTermViewport } from "./XTermViewport"; const MIN_PANEL_WIDTH = 320; const DEFAULT_MAX_PANEL_WIDTH = 720; @@ -67,6 +67,7 @@ const DEFAULT_TERMINAL_ROWS = 24; const FILE_TREE_TAB_ID = "__file_tree__"; const GIT_REVIEW_TAB_ID = "__git_review__"; const TUNNEL_TAB_ID = "__tunnel__"; +const SSH_TUNNEL_TAB_ID = "__ssh_tunnel__"; const PROJECT_TOOLS_RESIZE_END_EVENT = "liveagent:project-tools-resize-end"; type ProjectToolsPanelProps = { @@ -85,6 +86,9 @@ type ProjectToolsPanelProps = { fileTreeState: ProjectToolsFileTreeProjectState; gitReviewOpen: boolean; tunnelOpen?: boolean; + sshTunnelOpen?: boolean; + sshHosts?: SshHostConfig[]; + associatedSshHostIds?: string[]; client: TerminalClient; gitClient?: GitClient | null; gitWriteEnabled?: boolean; @@ -100,6 +104,9 @@ type ProjectToolsPanelProps = { onFileTreeStateChange: (patch: ProjectToolsFileTreeStatePatch) => void; onGitReviewOpenChange: (open: boolean) => void; onTunnelOpenChange?: (open: boolean) => void; + onSshTunnelOpenChange?: (open: boolean) => void; + onSshProjectHostIdsChange?: (hostIds: string[]) => void; + onOpenSshSession?: (session: TerminalSession, kind?: "bash" | "sftp") => void; onSessionsChange?: (sessions: TerminalSession[]) => void; onInsertFileMention?: (path: string, kind: "file" | "dir") => void; onOpenFile?: (path: string) => void; @@ -162,6 +169,17 @@ function areSessionsEqual(left: TerminalSession[], right: TerminalSession[]) { session.cwd === other.cwd && session.shell === other.shell && session.title === other.title && + session.kind === other.kind && + (session.ssh?.hostId ?? "") === (other.ssh?.hostId ?? "") && + (session.ssh?.hostName ?? "") === (other.ssh?.hostName ?? "") && + (session.ssh?.username ?? "") === (other.ssh?.username ?? "") && + (session.ssh?.host ?? "") === (other.ssh?.host ?? "") && + (session.ssh?.port ?? 0) === (other.ssh?.port ?? 0) && + (session.ssh?.authType ?? "") === (other.ssh?.authType ?? "") && + (session.ssh?.status ?? "") === (other.ssh?.status ?? "") && + (session.ssh?.reconnectAttempt ?? 0) === (other.ssh?.reconnectAttempt ?? 0) && + (session.ssh?.reconnectMaxAttempts ?? 0) === + (other.ssh?.reconnectMaxAttempts ?? 0) && session.pid === other.pid && session.cols === other.cols && session.rows === other.rows && @@ -180,6 +198,13 @@ function formatTerminalSessionTitle(title: string, terminalLabel: string) { return match[1] ? `${terminalLabel} ${match[1]}` : terminalLabel; } +function terminalSessionBelongsToProject(session: TerminalSession, projectPathKey: string) { + const wantedProjectKey = workspaceProjectPathKey(projectPathKey); + if (!wantedProjectKey) return false; + const sessionProjectKey = workspaceProjectPathKey(session.projectPathKey || session.cwd); + return sessionProjectKey === wantedProjectKey; +} + function dirname(path: string) { const normalized = path.replace(/\\/g, "/").replace(/\/+$/, ""); const index = normalized.lastIndexOf("/"); @@ -213,6 +238,10 @@ type ProjectToolsTab = | { id: typeof TUNNEL_TAB_ID; kind: "tunnel"; + } + | { + id: typeof SSH_TUNNEL_TAB_ID; + kind: "sshTunnel"; }; type TabDragState = { @@ -322,404 +351,6 @@ function reorderTabIdsByKeyboard(tabIds: readonly string[], tabId: string, key: return nextTabIds; } -function terminalTheme(theme: "light" | "dark") { - if (theme === "dark") { - return { - background: "#0b0f14", - foreground: "#d6deeb", - cursor: "#f8fafc", - selectionBackground: "#334155", - black: "#0f172a", - red: "#ef4444", - green: "#22c55e", - yellow: "#eab308", - blue: "#38bdf8", - magenta: "#c084fc", - cyan: "#2dd4bf", - white: "#e5e7eb", - }; - } - return { - background: "#ffffff", - foreground: "#172033", - cursor: "#111827", - selectionBackground: "#dbeafe", - black: "#111827", - red: "#dc2626", - green: "#16a34a", - yellow: "#ca8a04", - blue: "#2563eb", - magenta: "#9333ea", - cyan: "#0891b2", - white: "#f8fafc", - }; -} - -function terminalContainerHasSize(container: HTMLElement) { - const rect = container.getBoundingClientRect(); - return rect.width > 0 && rect.height > 0; -} - -function XTermViewport({ - client, - session, - theme, - isActive, - initialSnapshot, - onError, - onInitialSnapshotConsumed, -}: { - client: TerminalClient; - session: TerminalSession; - theme: "light" | "dark"; - isActive: boolean; - initialSnapshot?: TerminalSnapshot; - onError: (message: string | null) => void; - onInitialSnapshotConsumed?: (sessionId: string) => void; -}) { - const containerRef = useRef(null); - const resizeTimerRef = useRef(null); - const clientRef = useRef(client); - const sessionRef = useRef(session); - const themeRef = useRef(theme); - const onErrorRef = useRef(onError); - const initialSnapshotRef = useRef(initialSnapshot); - const onInitialSnapshotConsumedRef = useRef(onInitialSnapshotConsumed); - clientRef.current = client; - sessionRef.current = session; - themeRef.current = theme; - onErrorRef.current = onError; - onInitialSnapshotConsumedRef.current = onInitialSnapshotConsumed; - - const termRef = useRef(null); - const fitAndResizeRef = useRef<(() => void) | null>(null); - - useEffect(() => { - if (!termRef.current) return; - termRef.current.options.theme = terminalTheme(theme); - }, [theme]); - - useEffect(() => { - if (!isActive) { - termRef.current?.blur(); - return; - } - termRef.current?.focus(); - window.setTimeout(() => { - fitAndResizeRef.current?.(); - }, 0); - }, [isActive]); - - useEffect(() => { - const container = containerRef.current; - if (!container) return; - - let disposed = false; - let snapshotLoaded = false; - let loadingSnapshot = false; - let lastOutputOffset = 0; - const bufferedEvents: TerminalEvent[] = []; - const term = new XTerm({ - cursorBlink: true, - disableStdin: !sessionRef.current.running, - fontFamily: - '"SF Mono", SFMono-Regular, Menlo, Monaco, "Cascadia Code", Consolas, "Liberation Mono", monospace', - fontSize: 12, - fontWeight: "normal", - fontWeightBold: "bold", - lineHeight: 1.1, - letterSpacing: 0, - scrollback: 5000, - theme: terminalTheme(themeRef.current), - }); - termRef.current = term; - const fit = new FitAddon(); - term.loadAddon(fit); - term.open(container); - let touchScrollActive = false; - let touchScrollCancelled = false; - let lastTouchX = 0; - let lastTouchY = 0; - let touchScrollRemainder = 0; - - const fitAndResize = () => { - if (disposed) return; - if (!terminalContainerHasSize(container)) return; - try { - fit.fit(); - const s = sessionRef.current; - void clientRef.current - .resize(s.id, term.cols, term.rows, s.projectPathKey) - .catch(() => undefined); - } catch { - // xterm fit can throw while the panel is hidden or measuring at zero size. - } - }; - fitAndResizeRef.current = fitAndResize; - - const resizeObserver = new ResizeObserver(() => { - if (resizeTimerRef.current !== null) { - window.clearTimeout(resizeTimerRef.current); - } - resizeTimerRef.current = window.setTimeout(fitAndResize, 40); - }); - resizeObserver.observe(container); - window.setTimeout(fitAndResize, 0); - - const dataDisposable = term.onData((data) => { - const s = sessionRef.current; - if (!s.running) return; - void clientRef.current.input(s.id, data, s.projectPathKey).catch((error) => { - onErrorRef.current(error instanceof Error ? error.message : String(error)); - }); - }); - - const getTouchScrollRowHeight = () => - Math.max(8, Math.floor(container.clientHeight / Math.max(1, term.rows))); - - const handleTouchStart = (event: TouchEvent) => { - if (event.touches.length !== 1) { - touchScrollCancelled = true; - touchScrollActive = false; - touchScrollRemainder = 0; - return; - } - const touch = event.touches[0]; - if (!touch) return; - touchScrollCancelled = false; - touchScrollActive = false; - touchScrollRemainder = 0; - lastTouchX = touch.clientX; - lastTouchY = touch.clientY; - }; - - const handleTouchMove = (event: TouchEvent) => { - if (touchScrollCancelled || event.touches.length !== 1) return; - const touch = event.touches[0]; - if (!touch) return; - - const deltaX = touch.clientX - lastTouchX; - const deltaY = touch.clientY - lastTouchY; - const absX = Math.abs(deltaX); - const absY = Math.abs(deltaY); - if (!touchScrollActive) { - if (absX > absY && absX > 8) { - touchScrollCancelled = true; - return; - } - if (absY < 8) return; - touchScrollActive = true; - } - - lastTouchX = touch.clientX; - lastTouchY = touch.clientY; - touchScrollRemainder += -deltaY; - const rowHeight = getTouchScrollRowHeight(); - const rows = Math.trunc(touchScrollRemainder / rowHeight); - if (rows !== 0) { - term.scrollLines(rows); - touchScrollRemainder -= rows * rowHeight; - } - event.preventDefault(); - }; - - const handleTouchEnd = () => { - touchScrollActive = false; - touchScrollCancelled = false; - touchScrollRemainder = 0; - }; - - container.addEventListener("touchstart", handleTouchStart, { passive: true }); - container.addEventListener("touchmove", handleTouchMove, { passive: false }); - container.addEventListener("touchend", handleTouchEnd); - container.addEventListener("touchcancel", handleTouchEnd); - - const applySnapshot = (snapshot: TerminalSnapshot) => { - if (snapshot.output) { - term.write(snapshot.output); - } - lastOutputOffset = terminalSnapshotEndOffset(snapshot); - snapshotLoaded = true; - loadingSnapshot = false; - term.options.disableStdin = !snapshot.session.running; - replayBufferedEvents(); - window.setTimeout(fitAndResize, 0); - }; - - const replayBufferedEvents = () => { - const events = bufferedEvents.splice(0); - for (const event of events) { - writeTerminalEvent( - term, - event, - (nextOffset) => { - lastOutputOffset = nextOffset; - }, - lastOutputOffset, - ); - } - }; - - const loadSnapshot = () => { - if (disposed || loadingSnapshot) return; - const initial = initialSnapshotRef.current; - if (initial?.session.id === sessionRef.current.id) { - initialSnapshotRef.current = undefined; - onInitialSnapshotConsumedRef.current?.(initial.session.id); - applySnapshot(initial); - return; - } - loadingSnapshot = true; - const s = sessionRef.current; - void clientRef.current - .snapshot(s.id, undefined, s.projectPathKey) - .then((snapshot) => { - if (disposed) return; - applySnapshot(snapshot); - }) - .catch((error) => { - loadingSnapshot = false; - if (!disposed) { - onErrorRef.current(error instanceof Error ? error.message : String(error)); - snapshotLoaded = true; - replayBufferedEvents(); - } - }); - }; - - const unsubscribe = clientRef.current.subscribe((event) => { - if (disposed || event.sessionId !== session.id) return; - if (event.kind === "output" && event.data) { - if (snapshotLoaded && !loadingSnapshot) { - writeTerminalEvent( - term, - event, - (nextOffset) => { - lastOutputOffset = nextOffset; - }, - lastOutputOffset, - ); - } else { - bufferedEvents.push(event); - } - } - if (event.kind === "exit" || event.kind === "closed") { - term.options.disableStdin = true; - } - }); - - loadSnapshot(); - - return () => { - disposed = true; - termRef.current = null; - fitAndResizeRef.current = null; - unsubscribe(); - dataDisposable.dispose(); - resizeObserver.disconnect(); - if (resizeTimerRef.current !== null) { - window.clearTimeout(resizeTimerRef.current); - resizeTimerRef.current = null; - } - container.removeEventListener("touchstart", handleTouchStart); - container.removeEventListener("touchmove", handleTouchMove); - container.removeEventListener("touchend", handleTouchEnd); - container.removeEventListener("touchcancel", handleTouchEnd); - const s = sessionRef.current; - void clientRef.current.detach(s.id, s.projectPathKey).catch(() => undefined); - term.dispose(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [session.id, session.projectPathKey]); - - return ( -
- ); -} - -function terminalSnapshotEndOffset(snapshot: TerminalSnapshot) { - if ( - typeof snapshot.outputEndOffset === "number" && - Number.isFinite(snapshot.outputEndOffset) && - snapshot.outputEndOffset >= 0 - ) { - return snapshot.outputEndOffset; - } - const startOffset = - typeof snapshot.outputStartOffset === "number" && - Number.isFinite(snapshot.outputStartOffset) && - snapshot.outputStartOffset >= 0 - ? snapshot.outputStartOffset - : 0; - return startOffset + utf8ByteLength(snapshot.output); -} - -function writeTerminalEvent( - term: XTerm, - event: TerminalEvent, - setLastOutputOffset: (offset: number) => void, - lastOutputOffset: number, -): "written" | "skipped" { - const data = event.data ?? ""; - if (!data) return "skipped"; - const startOffset = event.outputStartOffset; - const endOffset = event.outputEndOffset; - if ( - typeof startOffset === "number" && - Number.isFinite(startOffset) && - typeof endOffset === "number" && - Number.isFinite(endOffset) && - endOffset >= startOffset - ) { - if (endOffset <= lastOutputOffset) return "skipped"; - const alreadyWritten = Math.max(0, lastOutputOffset - startOffset); - term.write(alreadyWritten > 0 ? sliceUtf8Bytes(data, alreadyWritten) : data); - setLastOutputOffset(endOffset); - return "written"; - } - term.write(data); - setLastOutputOffset(lastOutputOffset + utf8ByteLength(data)); - return "written"; -} - -function sliceUtf8Bytes(value: string, byteOffset: number) { - if (byteOffset <= 0) return value; - let consumed = 0; - let index = 0; - for (const segment of value) { - const next = consumed + utf8ByteLengthOfCodePoint(segment); - if (next <= byteOffset) { - consumed = next; - index += segment.length; - continue; - } - if (consumed < byteOffset) { - index += segment.length; - } - return value.slice(index); - } - return ""; -} - -function utf8ByteLength(value: string) { - let length = 0; - for (const segment of value) { - length += utf8ByteLengthOfCodePoint(segment); - } - return length; -} - -function utf8ByteLengthOfCodePoint(value: string) { - const codePoint = value.codePointAt(0) ?? 0; - if (codePoint <= 0x7f) return 1; - if (codePoint <= 0x7ff) return 2; - if (codePoint <= 0xffff) return 3; - return 4; -} - function clampScrollLeft(value: number, max: number) { return Math.min(max, Math.max(0, value)); } @@ -741,6 +372,9 @@ export function ProjectToolsPanel(props: ProjectToolsPanelProps) { fileTreeState, gitReviewOpen, tunnelOpen = false, + sshTunnelOpen = false, + sshHosts = [], + associatedSshHostIds = [], client, gitClient, gitWriteEnabled = false, @@ -756,6 +390,9 @@ export function ProjectToolsPanel(props: ProjectToolsPanelProps) { onFileTreeStateChange, onGitReviewOpenChange, onTunnelOpenChange, + onSshTunnelOpenChange, + onSshProjectHostIdsChange, + onOpenSshSession, onSessionsChange, onInsertFileMention, onOpenFile, @@ -805,17 +442,37 @@ export function ProjectToolsPanel(props: ProjectToolsPanelProps) { "--project-tools-panel-width": panelWidthStyleValue(panelStyleWidth), } as CSSProperties; const effectiveWidthCollapsed = !isOpen && collapseImmediately ? true : widthCollapsed; - const effectiveShouldRenderContent = !isOpen && collapseImmediately ? false : shouldRenderContent; + const effectiveShouldRenderContent = isOpen + ? true + : collapseImmediately + ? false + : shouldRenderContent; const isControlled = externalSessions !== undefined; + const localSessions = useMemo( + () => + sessions.filter( + (session) => + session.kind !== "ssh" && terminalSessionBelongsToProject(session, projectPathKey), + ), + [projectPathKey, sessions], + ); + const sshSessions = useMemo( + () => sessions.filter((session) => session.kind === "ssh"), + [sessions], + ); const fileTreeInitialized = Boolean(projectPathKey && fileTreeOpen); const gitReviewInitialized = Boolean(projectPathKey && gitReviewOpen); const tunnelInitialized = Boolean(tunnelOpen && tunnelClient); + const sshTunnelInitialized = Boolean(projectPathKey && sshTunnelOpen); const tunnelAvailable = Boolean(tunnelClient); const previousFileTreeInitializedRef = useRef(fileTreeInitialized); const previousGitReviewInitializedRef = useRef(gitReviewInitialized); const previousTunnelInitializedRef = useRef(tunnelInitialized); + const previousSshTunnelInitializedRef = useRef(sshTunnelInitialized); const currentActiveTab: ProjectToolsPanelTab = - activeTab === "tunnel" && tunnelInitialized + activeTab === "sshTunnel" && sshTunnelInitialized + ? "sshTunnel" + : activeTab === "tunnel" && tunnelInitialized ? "tunnel" : activeTab === "gitReview" && gitReviewInitialized ? "gitReview" @@ -824,17 +481,18 @@ export function ProjectToolsPanel(props: ProjectToolsPanelProps) { : "terminal"; const activeSession = useMemo( - () => sessions.find((session) => session.id === activeSessionId) ?? sessions[0] ?? null, - [activeSessionId, sessions], + () => + localSessions.find((session) => session.id === activeSessionId) ?? localSessions[0] ?? null, + [activeSessionId, localSessions], ); const pendingCloseSession = useMemo( - () => sessions.find((session) => session.id === pendingCloseSessionId) ?? null, - [pendingCloseSessionId, sessions], + () => localSessions.find((session) => session.id === pendingCloseSessionId) ?? null, + [localSessions, pendingCloseSessionId], ); const [draftTabOrder, setDraftTabOrder] = useState(null); const [draggingTabId, setDraggingTabId] = useState(""); const visibleTabs = useMemo(() => { - const terminalTabs: ProjectToolsTab[] = sessions.map((session) => ({ + const terminalTabs: ProjectToolsTab[] = localSessions.map((session) => ({ id: session.id, kind: "terminal", session, @@ -849,8 +507,17 @@ export function ProjectToolsPanel(props: ProjectToolsPanelProps) { if (tunnelInitialized) { nextTabs.push({ id: TUNNEL_TAB_ID, kind: "tunnel" }); } + if (sshTunnelInitialized) { + nextTabs.push({ id: SSH_TUNNEL_TAB_ID, kind: "sshTunnel" }); + } return nextTabs; - }, [fileTreeInitialized, gitReviewInitialized, sessions, tunnelInitialized]); + }, [ + fileTreeInitialized, + gitReviewInitialized, + localSessions, + sshTunnelInitialized, + tunnelInitialized, + ]); const effectiveTabOrder = draftTabOrder ?? tabOrder; const orderedProjectTabs = useMemo( () => orderProjectToolsTabs(visibleTabs, effectiveTabOrder), @@ -953,6 +620,18 @@ export function ProjectToolsPanel(props: ProjectToolsPanelProps) { } }, [activeTab, onActiveTabChange, tunnelInitialized]); + useEffect(() => { + const previousSshTunnelInitialized = previousSshTunnelInitializedRef.current; + previousSshTunnelInitializedRef.current = sshTunnelInitialized; + if (sshTunnelInitialized && !previousSshTunnelInitialized) { + onActiveTabChange("sshTunnel"); + return; + } + if (!sshTunnelInitialized && previousSshTunnelInitialized && activeTab === "sshTunnel") { + onActiveTabChange("terminal"); + } + }, [activeTab, onActiveTabChange, sshTunnelInitialized]); + const publishSessions = useCallback( (nextSessions: TerminalSession[], options?: { notifyParent?: boolean }) => { const sorted = sortSessions(nextSessions); @@ -986,13 +665,13 @@ export function ProjectToolsPanel(props: ProjectToolsPanelProps) { setLoading(true); setError(null); void client - .list(projectPathKey) + .list() .then((nextSessions) => { publishSessions(nextSessions, { notifyParent: !isControlled }); }) .catch((err) => setError(err instanceof Error ? err.message : String(err))) .finally(() => setLoading(false)); - }, [client, isControlled, projectPathKey, publishSessions, terminalReady]); + }, [client, isControlled, publishSessions, terminalReady]); useEffect(() => { if (!isOpen || isControlled) return; @@ -1079,6 +758,7 @@ export function ProjectToolsPanel(props: ProjectToolsPanelProps) { sessions, shellOptions.length, orderedProjectTabIds, + sshTunnelInitialized, updateTabsScrollState, ]); @@ -1093,7 +773,9 @@ export function ProjectToolsPanel(props: ProjectToolsPanelProps) { ? GIT_REVIEW_TAB_ID : currentActiveTab === "tunnel" ? TUNNEL_TAB_ID - : activeSession?.id; + : currentActiveTab === "sshTunnel" + ? SSH_TUNNEL_TAB_ID + : activeSession?.id; if (!targetTabId) return; const target = Array.from( element.querySelectorAll("[data-project-tools-tab-id]"), @@ -1159,7 +841,6 @@ export function ProjectToolsPanel(props: ProjectToolsPanelProps) { useEffect(() => { if (!terminalReady || isControlled) return; return client.subscribe((event) => { - if (event.projectPathKey !== projectPathKey) return; if (event.kind === "output") return; setSessions((current) => { let next = current; @@ -1209,10 +890,38 @@ export function ProjectToolsPanel(props: ProjectToolsPanelProps) { useEffect(() => { if (!pendingCloseSessionId) return; - if (!sessions.some((session) => session.id === pendingCloseSessionId)) { + if (!localSessions.some((session) => session.id === pendingCloseSessionId)) { setPendingCloseSessionId(""); } - }, [pendingCloseSessionId, sessions]); + }, [localSessions, pendingCloseSessionId]); + + const rememberTerminalSnapshot = useCallback( + (snapshot: TerminalSnapshot) => { + initialTerminalSnapshotsRef.current.set(snapshot.session.id, snapshot); + setSessions((current) => { + const next = sortSessions([ + ...current.filter((session) => session.id !== snapshot.session.id), + snapshot.session, + ]); + onSessionsChange?.(next); + return next; + }); + }, + [onSessionsChange], + ); + + const forgetTerminalSession = useCallback( + (sessionId: string) => { + initialTerminalSnapshotsRef.current.delete(sessionId); + setPendingCloseSessionId((current) => (current === sessionId ? "" : current)); + setSessions((current) => { + const next = sortSessions(current.filter((item) => item.id !== sessionId)); + onSessionsChange?.(next); + return next; + }); + }, + [onSessionsChange], + ); const createTerminal = useCallback( (shell?: string) => { @@ -1228,22 +937,22 @@ export function ProjectToolsPanel(props: ProjectToolsPanelProps) { rows: DEFAULT_TERMINAL_ROWS, }) .then((snapshot) => { - initialTerminalSnapshotsRef.current.set(snapshot.session.id, snapshot); - setSessions((current) => { - const next = sortSessions([ - ...current.filter((session) => session.id !== snapshot.session.id), - snapshot.session, - ]); - onSessionsChange?.(next); - return next; - }); + rememberTerminalSnapshot(snapshot); setActiveSessionId(snapshot.session.id); onActiveTabChange("terminal"); }) .catch((err) => setError(err instanceof Error ? err.message : String(err))) .finally(() => setCreating(false)); }, - [client, creating, cwd, onActiveTabChange, onSessionsChange, projectPathKey, terminalReady], + [ + client, + creating, + cwd, + onActiveTabChange, + projectPathKey, + rememberTerminalSnapshot, + terminalReady, + ], ); const handleCreate = useCallback(() => { @@ -1258,18 +967,12 @@ export function ProjectToolsPanel(props: ProjectToolsPanelProps) { void client .close(session.id, session.projectPathKey) .then(() => { - initialTerminalSnapshotsRef.current.delete(session.id); - setPendingCloseSessionId((current) => (current === session.id ? "" : current)); - setSessions((current) => { - const next = sortSessions(current.filter((item) => item.id !== session.id)); - onSessionsChange?.(next); - return next; - }); + forgetTerminalSession(session.id); }) .catch((err) => setError(err instanceof Error ? err.message : String(err))) .finally(() => setClosingSessionId((current) => (current === session.id ? "" : current))); }, - [client, closingSessionId, onSessionsChange], + [client, closingSessionId, forgetTerminalSession], ); const handleInitialTerminalSnapshotConsumed = useCallback((sessionId: string) => { @@ -1586,7 +1289,9 @@ export function ProjectToolsPanel(props: ProjectToolsPanelProps) { } }, []); - const showDisabledMessage = Boolean(disabledMessage && !tunnelAvailable && !tunnelInitialized); + const showDisabledMessage = Boolean( + disabledMessage && !tunnelAvailable && !tunnelInitialized && !sshTunnelInitialized, + ); const showProjectToolsChooser = !showDisabledMessage && (projectReady || tunnelAvailable) && @@ -1661,6 +1366,19 @@ export function ProjectToolsPanel(props: ProjectToolsPanelProps) { } }, [activeTab, onActiveTabChange, onTunnelOpenChange]); + const startSshTunnel = useCallback(() => { + if (!projectReady) return; + onSshTunnelOpenChange?.(true); + onActiveTabChange("sshTunnel"); + }, [onActiveTabChange, onSshTunnelOpenChange, projectReady]); + + const closeSshTunnelTab = useCallback(() => { + onSshTunnelOpenChange?.(false); + if (activeTab === "sshTunnel") { + onActiveTabChange("terminal"); + } + }, [activeTab, onActiveTabChange, onSshTunnelOpenChange]); + const renderCreateTerminalMenuItem = () => { if (shellOptions.length > 1) { return ( @@ -1732,7 +1450,7 @@ export function ProjectToolsPanel(props: ProjectToolsPanelProps) { aria-label={t("projectTools.resizePanel")} title={t("projectTools.resizePanel")} className={cn( - "group absolute inset-y-0 left-0 z-[90] hidden w-4 -translate-x-1/2 cursor-col-resize touch-none items-center justify-center border-0 bg-transparent p-0 md:flex", + "group absolute inset-y-0 left-0 z-[90] hidden w-3 cursor-col-resize touch-none items-center justify-center border-0 bg-transparent p-0 md:flex", "focus-visible:outline-none", )} onMouseDown={handleResizeStart} @@ -1927,6 +1645,63 @@ export function ProjectToolsPanel(props: ProjectToolsPanelProps) { ); } + if (tab.kind === "sshTunnel") { + return ( +
+ +
+ ); + } + const session = tab.session; const isPendingClose = pendingCloseSessionId === session.id; const isClosing = closingSessionId === session.id; @@ -2067,6 +1842,14 @@ export function ProjectToolsPanel(props: ProjectToolsPanelProps) { {t("projectTools.newTunnel")} + + + {t("projectTools.newSshTunnel")} + {onClose ? ( @@ -2205,6 +1988,25 @@ export function ProjectToolsPanel(props: ProjectToolsPanelProps) {
+ {loading ? (
@@ -2270,7 +2072,30 @@ export function ProjectToolsPanel(props: ProjectToolsPanelProps) { />
) : null} - {sessions.length > 0 ? ( + {sshTunnelInitialized ? ( +
+ onOpenSshSession?.(session, kind)} + onAssociatedHostIdsChange={(hostIds) => { + onSshProjectHostIdsChange?.(hostIds); + }} + /> +
+ ) : null} + {localSessions.length > 0 ? (
) : null}
- {sessions.map((session) => { + {localSessions.map((session) => { const isActiveTerminal = currentActiveTab === "terminal" && activeSession?.id === session.id; return ( diff --git a/crates/agent-gateway/web/src/components/project-tools/SshTunnelPanel.tsx b/crates/agent-gateway/web/src/components/project-tools/SshTunnelPanel.tsx new file mode 100644 index 000000000..eb33899c5 --- /dev/null +++ b/crates/agent-gateway/web/src/components/project-tools/SshTunnelPanel.tsx @@ -0,0 +1,1092 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useLocale } from "@/i18n"; +import type { SshHostConfig } from "@/lib/settings"; +import { workspaceProjectPathKey } from "@/lib/settings"; +import { cn } from "@/lib/shared/utils"; +import type { + TerminalClient, + TerminalSession, + TerminalSnapshot, + TerminalSshPrompt, +} from "@/lib/terminal/types"; +import { + AlertTriangle, + ArrowLeft, + Check, + ChevronDown, + Clock3, + FolderTree, + Globe, + Key, + Loader2, + Plus, + RefreshCw, + Server, + Settings, + Shield, + Terminal, + Wifi, + WifiOff, + X, +} from "../icons"; +import { Button } from "../ui/button"; +import { useConfirmDialog } from "../ui/confirm-dialog"; + +type SshTunnelScope = "project" | "all"; +type SshTunnelView = "list" | "settings" | "create"; + +type SshLatencyState = { + latencyMs?: number; + loading: boolean; + failed: boolean; +}; + +type PendingSshCreate = { + hostId: string; + existingSessionIds: Set; +}; + +type SshTunnelPanelProps = { + cwd: string; + projectPathKey: string; + hosts: SshHostConfig[]; + associatedHostIds: string[]; + client: TerminalClient; + sessions: TerminalSession[]; + onSessionSnapshot: (snapshot: TerminalSnapshot) => void; + onSessionClosed: (sessionId: string) => void; + onOpenSession: (session: TerminalSession, kind?: "bash" | "sftp") => void; + onAssociatedHostIdsChange: (hostIds: string[]) => void; +}; + +function endpointLabel(host: SshHostConfig) { + const userPrefix = host.username.trim() ? `${host.username.trim()}@` : ""; + return `${userPrefix}${host.host}:${host.port}`; +} + +function authLabel(host: Pick, t: (key: string) => string) { + return host.authType === "privateKey" + ? t("settings.sshAuthPrivateKey") + : t("settings.sshAuthPassword"); +} + +function hostHasProxy(host: SshHostConfig) { + return ( + host.proxy.url.trim().length > 0 || + host.proxy.port > 0 || + host.proxy.username.trim().length > 0 || + host.proxy.passwordConfigured === true + ); +} + +function hostSecretReady(host: SshHostConfig) { + if (host.authType === "privateKey") { + return ( + host.privateKey.trim().length > 0 || + host.privateKeyPath.trim().length > 0 || + host.privateKeyConfigured === true + ); + } + return host.password.trim().length > 0 || host.passwordConfigured === true; +} + +function hostStatusMessage(host: SshHostConfig, t: (key: string) => string) { + if (hostHasProxy(host)) return t("projectTools.sshTunnelProxyUnsupported"); + if (!hostSecretReady(host)) return t("projectTools.sshTunnelMissingSecret"); + return ""; +} + +function sessionBelongsToProject(session: TerminalSession, projectPathKey: string) { + const wantedProjectKey = workspaceProjectPathKey(projectPathKey); + if (!wantedProjectKey) return false; + const sessionProjectKey = workspaceProjectPathKey(session.projectPathKey || session.cwd); + return sessionProjectKey === wantedProjectKey; +} + +function sessionTitle(session: TerminalSession, fallback: string) { + return session.title || session.ssh?.hostName || fallback; +} + +function sessionEndpointLabel(session: TerminalSession) { + const ssh = session.ssh; + if (!ssh) return session.cwd || session.projectPathKey; + const userPrefix = ssh.username.trim() ? `${ssh.username.trim()}@` : ""; + return `${userPrefix}${ssh.host}:${ssh.port}`; +} + +function sessionProjectLabel(session: TerminalSession) { + return session.projectPathKey || session.cwd || ""; +} + +function sshSessionStatus(session: TerminalSession) { + const status = session.ssh?.status ?? (session.running ? "connected" : "disconnected"); + if (status === "connected" && !session.running) return "disconnected"; + return status; +} + +function sshSessionConnected(session: TerminalSession) { + return sshSessionStatus(session) === "connected" && session.running; +} + +function sshStatusLabel(session: TerminalSession, t: (key: string) => string) { + const status = sshSessionStatus(session); + if (status === "reconnecting") { + const attempt = Math.max(1, Number(session.ssh?.reconnectAttempt ?? 1)); + const max = Math.max(attempt, Number(session.ssh?.reconnectMaxAttempts ?? 3)); + return t("projectTools.sshTunnelReconnecting") + .replace("{attempt}", String(attempt)) + .replace("{max}", String(max)); + } + if (status === "disconnected") return t("projectTools.sshTunnelDisconnected"); + return t("projectTools.sshTunnelConnected"); +} + +function HostMetaTags(props: { host: SshHostConfig }) { + const { host } = props; + const { t } = useLocale(); + const tags: string[] = []; + if (host.authType === "privateKey" && host.privateKeyPath.trim()) { + tags.push(host.privateKeyPath.trim()); + } else if (host.authType === "privateKey" && host.privateKeyConfigured) { + tags.push(t("settings.sshPrivateKeyConfigured")); + } + if (host.privateKeyPassphraseConfigured) { + tags.push(t("settings.sshPrivateKeyPassphraseConfigured")); + } + if (hostHasProxy(host)) { + tags.push(t("settings.sshAdvancedProxy")); + } + if (tags.length === 0) return null; + return ( +
+ {tags.map((tag) => ( + + {tag} + + ))} +
+ ); +} + +export function SshTunnelPanel(props: SshTunnelPanelProps) { + const { + cwd, + projectPathKey, + hosts, + associatedHostIds, + client, + sessions, + onSessionSnapshot, + onSessionClosed, + onOpenSession, + onAssociatedHostIdsChange, + } = props; + const { t } = useLocale(); + const { confirm: requestCloseSessionConfirm, dialog: closeSessionConfirmDialog } = + useConfirmDialog(); + const [scope, setScope] = useState("project"); + const [view, setView] = useState("list"); + const [createHostId, setCreateHostId] = useState(""); + const [createTitle, setCreateTitle] = useState(""); + const [createSftpEnabled, setCreateSftpEnabled] = useState(false); + const [creating, setCreating] = useState(false); + const [closingSessionId, setClosingSessionId] = useState(""); + const [error, setError] = useState(null); + const [prompt, setPrompt] = useState(null); + const [promptAnswer, setPromptAnswer] = useState(""); + const [answeringPrompt, setAnsweringPrompt] = useState(false); + const [latencyBySessionId, setLatencyBySessionId] = useState>( + {}, + ); + const latencyRequestsRef = useRef>(new Set()); + const pendingCreateRef = useRef(null); + const associatedSet = useMemo(() => new Set(associatedHostIds), [associatedHostIds]); + const associatedHosts = useMemo( + () => hosts.filter((host) => associatedSet.has(host.id)), + [associatedSet, hosts], + ); + const sshSessions = useMemo( + () => sessions.filter((session) => session.kind === "ssh" && session.ssh), + [sessions], + ); + const projectSshSessions = useMemo( + () => sshSessions.filter((session) => sessionBelongsToProject(session, projectPathKey)), + [projectPathKey, sshSessions], + ); + const visibleSessions = scope === "project" ? projectSshSessions : sshSessions; + const canCreateInScope = scope === "project"; + const createHosts = canCreateInScope ? associatedHosts : []; + const selectedCreateHostId = createHosts.some((host) => host.id === createHostId) + ? createHostId + : (createHosts[0]?.id ?? ""); + const selectedCreateHost = createHosts.find((host) => host.id === selectedCreateHostId) ?? null; + const selectedHostMessage = selectedCreateHost ? hostStatusMessage(selectedCreateHost, t) : ""; + const canCreate = Boolean( + canCreateInScope && selectedCreateHost && !selectedHostMessage && !creating, + ); + + useEffect(() => { + if (canCreateInScope || view !== "create") return; + setView("list"); + }, [canCreateInScope, view]); + + const refreshSessionLatency = useCallback( + (session: TerminalSession) => { + if (!sshSessionConnected(session) || session.kind !== "ssh") return; + if (latencyRequestsRef.current.has(session.id)) return; + latencyRequestsRef.current.add(session.id); + setLatencyBySessionId((current) => ({ + ...current, + [session.id]: { + latencyMs: current[session.id]?.latencyMs, + loading: true, + failed: false, + }, + })); + void client + .sshLatency(session.id, session.projectPathKey) + .then((latency) => { + setLatencyBySessionId((current) => ({ + ...current, + [session.id]: { + latencyMs: latency.latencyMs, + loading: false, + failed: false, + }, + })); + }) + .catch(() => { + setLatencyBySessionId((current) => ({ + ...current, + [session.id]: { + loading: false, + failed: true, + }, + })); + }) + .finally(() => { + latencyRequestsRef.current.delete(session.id); + }); + }, + [client], + ); + + useEffect(() => { + const visibleIds = new Set(visibleSessions.map((session) => session.id)); + setLatencyBySessionId((current) => { + const next = Object.fromEntries( + Object.entries(current).filter(([sessionId]) => visibleIds.has(sessionId)), + ); + return Object.keys(next).length === Object.keys(current).length ? current : next; + }); + }, [visibleSessions]); + + useEffect(() => { + const connectedSshSessions = visibleSessions.filter( + (session) => sshSessionConnected(session) && session.kind === "ssh", + ); + if (connectedSshSessions.length === 0) return; + connectedSshSessions.forEach(refreshSessionLatency); + const timer = window.setInterval(() => { + connectedSshSessions.forEach(refreshSessionLatency); + }, 10_000); + return () => window.clearInterval(timer); + }, [refreshSessionLatency, visibleSessions]); + + const finishCreateFlow = useCallback(() => { + if (!pendingCreateRef.current) return; + pendingCreateRef.current = null; + setPrompt(null); + setPromptAnswer(""); + setCreateTitle(""); + setCreateSftpEnabled(false); + setCreating(false); + setError(null); + setView("list"); + }, []); + + const finishCreatedSnapshot = useCallback( + (snapshot: TerminalSnapshot) => { + onSessionSnapshot(snapshot); + finishCreateFlow(); + }, + [finishCreateFlow, onSessionSnapshot], + ); + + useEffect(() => { + const pending = pendingCreateRef.current; + if (!pending) return; + const createdSession = sshSessions.find( + (session) => + session.ssh?.hostId === pending.hostId && + !pending.existingSessionIds.has(session.id) && + sessionBelongsToProject(session, projectPathKey), + ); + if (!createdSession) return; + finishCreateFlow(); + }, [finishCreateFlow, projectPathKey, sshSessions]); + + const toggleHost = (hostId: string) => { + const current = associatedHostIds.filter((id) => hosts.some((host) => host.id === id)); + const next = associatedSet.has(hostId) + ? current.filter((id) => id !== hostId) + : [...current, hostId]; + onAssociatedHostIdsChange(next); + }; + + const handleCreate = useCallback(() => { + if (!selectedCreateHost || !canCreate) return; + pendingCreateRef.current = { + hostId: selectedCreateHost.id, + existingSessionIds: new Set(sshSessions.map((session) => session.id)), + }; + setCreating(true); + setError(null); + void client + .createSsh({ + cwd, + projectPathKey, + hostId: selectedCreateHost.id, + title: createTitle.trim() || undefined, + sftpEnabled: createSftpEnabled, + }) + .then((result) => { + if (result.prompt) { + setPrompt(result.prompt); + setPromptAnswer(""); + setView("list"); + return; + } + if (result.snapshot) { + finishCreatedSnapshot(result.snapshot); + } + }) + .catch((err) => { + pendingCreateRef.current = null; + setError(err instanceof Error ? err.message : String(err)); + }) + .finally(() => setCreating(false)); + }, [ + canCreate, + client, + createSftpEnabled, + createTitle, + cwd, + finishCreatedSnapshot, + projectPathKey, + selectedCreateHost, + sshSessions, + ]); + + const handleSubmitPrompt = useCallback(() => { + if (!prompt || answeringPrompt) return; + const hostKeyPrompt = prompt.kind === "hostKey"; + if (!hostKeyPrompt && !promptAnswer.trim()) return; + setAnsweringPrompt(true); + setError(null); + void client + .answerSshPrompt({ + promptId: prompt.id, + answer: hostKeyPrompt ? undefined : promptAnswer, + trustHostKey: hostKeyPrompt, + }) + .then((result) => { + if (result.prompt) { + setPrompt(result.prompt); + setPromptAnswer(""); + return; + } + setPrompt(null); + setPromptAnswer(""); + if (result.snapshot) { + finishCreatedSnapshot(result.snapshot); + } + }) + .catch((err) => setError(err instanceof Error ? err.message : String(err))) + .finally(() => setAnsweringPrompt(false)); + }, [answeringPrompt, client, finishCreatedSnapshot, prompt, promptAnswer]); + + const handleCancelPrompt = useCallback(() => { + const promptId = prompt?.id; + pendingCreateRef.current = null; + setPrompt(null); + setPromptAnswer(""); + if (!promptId) return; + void client.cancelSshPrompt(promptId).catch(() => undefined); + }, [client, prompt]); + + const handleCloseSession = useCallback( + async (session: TerminalSession) => { + if (closingSessionId === session.id) return; + const title = sessionTitle(session, t("projectTools.sshTunnelTitle")); + const confirmed = await requestCloseSessionConfirm({ + title: t("projectTools.confirmCloseSshSession"), + subtitle: t("projectTools.closeSshSessionConfirm").replace("{title}", title), + detail: sessionEndpointLabel(session), + confirmLabel: t("projectTools.closeSshSessionContinue"), + cancelLabel: t("projectTools.closeSshSessionCancel"), + closeLabel: t("projectTools.closeSshSessionClose"), + tone: "destructive", + }); + if (!confirmed) return; + setClosingSessionId(session.id); + setError(null); + void client + .close(session.id, session.projectPathKey) + .then(() => onSessionClosed(session.id)) + .catch((err) => setError(err instanceof Error ? err.message : String(err))) + .finally(() => setClosingSessionId((current) => (current === session.id ? "" : current))); + }, + [client, closingSessionId, onSessionClosed, requestCloseSessionConfirm, t], + ); + + const listActive = view === "list"; + const settingsActive = view === "settings"; + const createActive = view === "create"; + const listPageClassName = cn( + "absolute inset-0 flex min-h-0 flex-col bg-background transition-[opacity,transform] duration-200 ease-out motion-reduce:transform-none motion-reduce:transition-none", + listActive + ? "z-10 translate-x-0 opacity-100" + : "pointer-events-none z-0 -translate-x-4 opacity-0", + ); + const settingsPageClassName = cn( + "absolute inset-0 flex min-h-0 flex-col bg-background transition-[opacity,transform] duration-200 ease-out motion-reduce:transform-none motion-reduce:transition-none", + settingsActive + ? "z-10 translate-x-0 opacity-100" + : "pointer-events-none z-0 translate-x-4 opacity-0", + ); + const createPageClassName = cn( + "absolute inset-0 flex min-h-0 flex-col bg-background transition-[opacity,transform] duration-200 ease-out motion-reduce:transform-none motion-reduce:transition-none", + createActive + ? "z-10 translate-x-0 opacity-100" + : "pointer-events-none z-0 translate-x-4 opacity-0", + ); + const emptyTitle = + scope === "project" + ? t("projectTools.sshTunnelProjectEmpty") + : t("projectTools.sshTunnelAllEmpty"); + const emptyHint = + scope === "project" + ? t("projectTools.sshTunnelProjectEmptyHint") + : t("projectTools.sshTunnelAllEmptyHint"); + const hasCreateHosts = createHosts.length > 0; + const visibleSessionCount = visibleSessions.length; + const connectedSessionCount = visibleSessions.filter(sshSessionConnected).length; + const statusText = + visibleSessionCount > 0 + ? t("projectTools.sshTunnelConnectionCount") + .replace("{count}", String(visibleSessionCount)) + .replace("{connected}", String(connectedSessionCount)) + : scope === "all" + ? t("projectTools.sshTunnelAllEmpty") + : projectPathKey + ? t("projectTools.sshTunnelProjectEmpty") + : t("projectTools.sshTunnelNoProject"); + const hostKeyPrompt = prompt?.kind === "hostKey"; + const promptSubmitDisabled = + answeringPrompt || Boolean(prompt && !hostKeyPrompt && !promptAnswer.trim()); + const latencyText = (session: TerminalSession) => { + const state = latencyBySessionId[session.id]; + if (state?.failed) return t("projectTools.sshTunnelLatencyUnknown"); + if (state?.latencyMs) { + return t("projectTools.sshTunnelLatencyValue").replace( + "{ms}", + String(state.latencyMs), + ); + } + if (state?.loading) return t("projectTools.sshTunnelLatencyChecking"); + return t("projectTools.sshTunnelLatencyUnknown"); + }; + + return ( +
+
+
+ +
+
+ {t("projectTools.sshTunnelAssociateHosts")} +
+
+ {t("projectTools.sshTunnelAssociateHostsHint")} +
+
+
+ {associatedHosts.length}{" "} + {t("projectTools.sshTunnelAssociatedCount")} +
+
+ + {hosts.length === 0 ? ( +
+
+ +
+
+
+ {t("projectTools.sshTunnelNoConfiguredHosts")} +
+
+ {t("projectTools.sshTunnelNoConfiguredHostsHint")} +
+
+
+ ) : ( +
+
+ {hosts.map((host) => { + const selected = associatedSet.has(host.id); + return ( + + ); + })} +
+
+ )} +
+ +
+
+ +
+
+ {t("projectTools.sshTunnelCreateTitle")} +
+
+ {t("projectTools.sshTunnelCreateHint")} +
+
+
+ + {createHosts.length === 0 ? ( +
+
+ +
+
+
+ {hosts.length === 0 + ? t("projectTools.sshTunnelNoConfiguredHosts") + : t("projectTools.sshTunnelCreateNoAssociatedHosts")} +
+
+ {hosts.length === 0 + ? t("projectTools.sshTunnelNoConfiguredHostsHint") + : t("projectTools.sshTunnelCreateNoAssociatedHostsHint")} +
+
+
+ ) : ( +
{ + event.preventDefault(); + handleCreate(); + }} + > +
+ + + + + + + {selectedCreateHost ? ( +
+
+ +
+
+ {selectedCreateHost.name} +
+
+ {endpointLabel(selectedCreateHost)} +
+
+ + {authLabel(selectedCreateHost, t)} + +
+ {selectedHostMessage ? ( +
+ + {selectedHostMessage} +
+ ) : null} +
+ ) : null} + + {error ? ( +
+ {error} +
+ ) : null} +
+ +
+ + +
+
+ )} +
+ +
+
+
+
+ +
+
+
+ {t("projectTools.sshTunnelTitle")} +
+
+ {statusText} +
+
+ {canCreateInScope ? ( + + ) : null} + {scope === "project" ? ( + + ) : null} +
+ +
+ +
+ +
+ {error ? ( +
+ {error} +
+ ) : null} + + {visibleSessionCount === 0 ? ( +
+
+
+ +
+
{emptyTitle}
+
+ {emptyHint} +
+
+ {canCreateInScope ? ( + + ) : null} + {scope === "project" && associatedHosts.length === 0 ? ( + + ) : null} +
+
+
+ ) : ( +
+ {visibleSessions.map((session) => { + const title = sessionTitle(session, t("projectTools.sshTunnelTitle")); + const endpoint = sessionEndpointLabel(session); + const projectLabel = sessionProjectLabel(session); + const closing = closingSessionId === session.id; + const sshStatus = sshSessionStatus(session); + const connected = sshSessionConnected(session); + return ( +
+
+
+ +
+
+
+ + {title} + + + {sshStatus === "disconnected" ? ( + + ) : sshStatus === "reconnecting" ? ( + + ) : ( + + )} + {sshStatusLabel(session, t)} + +
+
+ {endpoint} +
+ {scope === "all" && projectLabel ? ( +
+ {projectLabel} +
+ ) : null} +
+
+ +
+
+ + {latencyBySessionId[session.id]?.loading && + !latencyBySessionId[session.id]?.latencyMs ? ( + + ) : ( + + )} + {latencyText(session)} + +
+
+ + {session.ssh?.sftpEnabled ? ( + + ) : null} + +
+
+
+ ); + })} +
+ )} +
+
+ + {prompt ? ( +
+
{ + event.preventDefault(); + handleSubmitPrompt(); + }} + > +
+
+ +
+
+
+ {hostKeyPrompt + ? t("projectTools.sshTunnelPromptTitle") + : t("projectTools.sshTunnelAuthPromptTitle")} +
+
+ {prompt.message} +
+
+
+
+
+ + {t("projectTools.sshTunnelHost")} + + + {prompt.host}:{prompt.port} + +
+ {prompt.keyType ? ( +
+ + {t("projectTools.sshTunnelKeyType")} + + + {prompt.keyType} + +
+ ) : null} + {prompt.fingerprintSha256 ? ( +
+ + {t("projectTools.sshTunnelFingerprint")} + + + {prompt.fingerprintSha256} + +
+ ) : null} +
+ {!hostKeyPrompt ? ( + setPromptAnswer(event.currentTarget.value)} + className="mt-3 h-10 w-full rounded-lg border border-border/70 bg-background/80 px-3 text-sm text-foreground outline-none transition-colors placeholder:text-muted-foreground/70 focus-visible:border-emerald-500/50 focus-visible:ring-1 focus-visible:ring-emerald-500/20" + type={prompt.answerEcho ? "text" : "password"} + autoFocus + /> + ) : null} +
+ + +
+
+
+ ) : null} + {closeSessionConfirmDialog} +
+ ); +} diff --git a/crates/agent-gateway/web/src/components/project-tools/XTermViewport.tsx b/crates/agent-gateway/web/src/components/project-tools/XTermViewport.tsx new file mode 100644 index 000000000..c19aee1fe --- /dev/null +++ b/crates/agent-gateway/web/src/components/project-tools/XTermViewport.tsx @@ -0,0 +1,482 @@ +import "@xterm/xterm/css/xterm.css"; + +import { FitAddon } from "@xterm/addon-fit"; +import { Terminal as XTerm } from "@xterm/xterm"; +import { useEffect, useRef, type CSSProperties } from "react"; +import { cn } from "@/lib/shared/utils"; +import type { + TerminalClient, + TerminalEvent, + TerminalSession, + TerminalSnapshot, +} from "@/lib/terminal/types"; + +type XTermViewportProps = { + client: TerminalClient; + session: TerminalSession; + theme: "light" | "dark"; + isActive: boolean; + initialSnapshot?: TerminalSnapshot; + className?: string; + onError: (message: string | null) => void; + onInitialSnapshotConsumed?: (sessionId: string) => void; +}; + +function terminalTheme(theme: "light" | "dark") { + if (theme === "dark") { + return { + background: "#0b0f14", + foreground: "#d6deeb", + cursor: "#f8fafc", + cursorAccent: "#0b0f14", + selectionBackground: "#2c3e57", + selectionInactiveBackground: "#22304a", + scrollbarSliderBackground: "rgba(148, 163, 184, 0.18)", + scrollbarSliderHoverBackground: "rgba(148, 163, 184, 0.3)", + scrollbarSliderActiveBackground: "rgba(148, 163, 184, 0.42)", + overviewRulerBorder: "transparent", + black: "#1b2733", + red: "#ef4444", + green: "#22c55e", + yellow: "#eab308", + blue: "#38bdf8", + magenta: "#c084fc", + cyan: "#2dd4bf", + white: "#cbd5e1", + brightBlack: "#64748b", + brightRed: "#f87171", + brightGreen: "#4ade80", + brightYellow: "#fde047", + brightBlue: "#7dd3fc", + brightMagenta: "#d8b4fe", + brightCyan: "#5eead4", + brightWhite: "#f8fafc", + }; + } + return { + background: "#fcfcfd", + foreground: "#1f2933", + cursor: "#111827", + cursorAccent: "#fcfcfd", + selectionBackground: "#bfdbfe", + selectionInactiveBackground: "#dbeafe", + scrollbarSliderBackground: "rgba(100, 116, 139, 0.16)", + scrollbarSliderHoverBackground: "rgba(100, 116, 139, 0.26)", + scrollbarSliderActiveBackground: "rgba(100, 116, 139, 0.36)", + overviewRulerBorder: "transparent", + black: "#1f2933", + red: "#dc2626", + green: "#16a34a", + yellow: "#b45309", + blue: "#2563eb", + magenta: "#9333ea", + cyan: "#0891b2", + white: "#e2e8f0", + brightBlack: "#64748b", + brightRed: "#ef4444", + brightGreen: "#22c55e", + brightYellow: "#d97706", + brightBlue: "#3b82f6", + brightMagenta: "#a855f7", + brightCyan: "#06b6d4", + brightWhite: "#f8fafc", + }; +} + +function terminalContainerHasSize(container: HTMLElement) { + const rect = container.getBoundingClientRect(); + return rect.width > 0 && rect.height > 0; +} + +export function XTermViewport({ + client, + session, + theme, + isActive, + initialSnapshot, + className, + onError, + onInitialSnapshotConsumed, +}: XTermViewportProps) { + const containerRef = useRef(null); + const resizeTimerRef = useRef(null); + const clientRef = useRef(client); + const sessionRef = useRef(session); + const themeRef = useRef(theme); + const onErrorRef = useRef(onError); + const initialSnapshotRef = useRef(initialSnapshot); + const onInitialSnapshotConsumedRef = useRef(onInitialSnapshotConsumed); + clientRef.current = client; + sessionRef.current = session; + themeRef.current = theme; + onErrorRef.current = onError; + onInitialSnapshotConsumedRef.current = onInitialSnapshotConsumed; + + const termRef = useRef(null); + const fitAndResizeRef = useRef<(() => void) | null>(null); + const viewportStyle = { + "--project-terminal-background": terminalTheme(theme).background, + } as CSSProperties; + + useEffect(() => { + if (!termRef.current) return; + termRef.current.options.theme = terminalTheme(theme); + }, [theme]); + + useEffect(() => { + if (!isActive) { + termRef.current?.blur(); + return; + } + termRef.current?.focus(); + window.setTimeout(() => { + fitAndResizeRef.current?.(); + }, 0); + }, [isActive]); + + useEffect(() => { + const container = containerRef.current; + if (!container) return; + + let disposed = false; + let snapshotLoaded = false; + let loadingSnapshot = false; + let lastOutputOffset = 0; + const bufferedEvents: TerminalEvent[] = []; + const term = new XTerm({ + cursorBlink: true, + cursorStyle: "block", + cursorInactiveStyle: "outline", + disableStdin: !sessionRef.current.running, + fontFamily: + '"SF Mono", SFMono-Regular, Menlo, Monaco, "Cascadia Code", Consolas, "Liberation Mono", monospace', + fontSize: 13, + fontWeight: "normal", + fontWeightBold: "bold", + lineHeight: 1.3, + letterSpacing: 0, + scrollback: 5000, + overviewRuler: { + width: 8, + }, + theme: terminalTheme(themeRef.current), + }); + termRef.current = term; + const fit = new FitAddon(); + term.loadAddon(fit); + term.open(container); + let touchScrollActive = false; + let touchScrollCancelled = false; + let lastTouchX = 0; + let lastTouchY = 0; + let touchScrollRemainder = 0; + + const focusTerminal = () => { + if (disposed || !sessionRef.current.running) return; + term.focus(); + }; + + const handlePointerDown = (event: PointerEvent) => { + if (event.pointerType === "touch") return; + focusTerminal(); + }; + + const fitAndResize = () => { + if (disposed) return; + if (!terminalContainerHasSize(container)) return; + try { + fit.fit(); + const s = sessionRef.current; + void clientRef.current + .resize(s.id, term.cols, term.rows, s.projectPathKey) + .catch(() => undefined); + } catch { + // xterm fit can throw while the panel is hidden or measuring at zero size. + } + }; + fitAndResizeRef.current = fitAndResize; + + const resizeObserver = new ResizeObserver(() => { + if (resizeTimerRef.current !== null) { + window.clearTimeout(resizeTimerRef.current); + } + resizeTimerRef.current = window.setTimeout(fitAndResize, 40); + }); + resizeObserver.observe(container); + window.setTimeout(fitAndResize, 0); + + const dataDisposable = term.onData((data) => { + const s = sessionRef.current; + if (!s.running) return; + void clientRef.current.input(s.id, data, s.projectPathKey).catch((error) => { + onErrorRef.current(error instanceof Error ? error.message : String(error)); + }); + }); + + const getTouchScrollRowHeight = () => + Math.max(8, Math.floor(container.clientHeight / Math.max(1, term.rows))); + + const handleTouchStart = (event: TouchEvent) => { + if (event.touches.length !== 1) { + touchScrollCancelled = true; + touchScrollActive = false; + touchScrollRemainder = 0; + return; + } + const touch = event.touches[0]; + if (!touch) return; + touchScrollCancelled = false; + touchScrollActive = false; + touchScrollRemainder = 0; + lastTouchX = touch.clientX; + lastTouchY = touch.clientY; + }; + + const handleTouchMove = (event: TouchEvent) => { + if (touchScrollCancelled || event.touches.length !== 1) return; + const touch = event.touches[0]; + if (!touch) return; + + const deltaX = touch.clientX - lastTouchX; + const deltaY = touch.clientY - lastTouchY; + const absX = Math.abs(deltaX); + const absY = Math.abs(deltaY); + if (!touchScrollActive) { + if (absX > absY && absX > 8) { + touchScrollCancelled = true; + return; + } + if (absY < 8) return; + touchScrollActive = true; + } + + lastTouchX = touch.clientX; + lastTouchY = touch.clientY; + touchScrollRemainder += -deltaY; + const rowHeight = getTouchScrollRowHeight(); + const rows = Math.trunc(touchScrollRemainder / rowHeight); + if (rows !== 0) { + term.scrollLines(rows); + touchScrollRemainder -= rows * rowHeight; + } + event.preventDefault(); + }; + + const resetTouchScroll = () => { + touchScrollActive = false; + touchScrollCancelled = false; + touchScrollRemainder = 0; + }; + + const handleTouchEnd = () => { + const shouldFocus = !touchScrollActive && !touchScrollCancelled; + resetTouchScroll(); + if (shouldFocus) { + focusTerminal(); + } + }; + + const handleTouchCancel = () => { + resetTouchScroll(); + }; + + container.addEventListener("pointerdown", handlePointerDown); + container.addEventListener("touchstart", handleTouchStart, { passive: true }); + container.addEventListener("touchmove", handleTouchMove, { passive: false }); + container.addEventListener("touchend", handleTouchEnd); + container.addEventListener("touchcancel", handleTouchCancel); + + const applySnapshot = (snapshot: TerminalSnapshot) => { + if (snapshot.output) { + term.write(snapshot.output); + } + lastOutputOffset = terminalSnapshotEndOffset(snapshot); + snapshotLoaded = true; + loadingSnapshot = false; + term.options.disableStdin = !snapshot.session.running; + replayBufferedEvents(); + window.setTimeout(fitAndResize, 0); + }; + + const replayBufferedEvents = () => { + const events = bufferedEvents.splice(0); + for (const event of events) { + writeTerminalEvent( + term, + event, + (nextOffset) => { + lastOutputOffset = nextOffset; + }, + lastOutputOffset, + ); + } + }; + + const loadSnapshot = () => { + if (disposed || loadingSnapshot) return; + const initial = initialSnapshotRef.current; + if (initial?.session.id === sessionRef.current.id) { + initialSnapshotRef.current = undefined; + onInitialSnapshotConsumedRef.current?.(initial.session.id); + applySnapshot(initial); + return; + } + loadingSnapshot = true; + const s = sessionRef.current; + void clientRef.current + .snapshot(s.id, undefined, s.projectPathKey) + .then((snapshot) => { + if (disposed) return; + applySnapshot(snapshot); + }) + .catch((error) => { + loadingSnapshot = false; + if (!disposed) { + onErrorRef.current(error instanceof Error ? error.message : String(error)); + snapshotLoaded = true; + replayBufferedEvents(); + } + }); + }; + + const unsubscribe = clientRef.current.subscribe((event) => { + if (disposed || event.sessionId !== session.id) return; + if (event.kind === "output" && event.data) { + if (snapshotLoaded && !loadingSnapshot) { + writeTerminalEvent( + term, + event, + (nextOffset) => { + lastOutputOffset = nextOffset; + }, + lastOutputOffset, + ); + } else { + bufferedEvents.push(event); + } + } + if (event.kind === "exit" || event.kind === "closed" || event.kind === "reconnecting") { + term.options.disableStdin = true; + } + if (event.kind === "reconnected") { + term.options.disableStdin = false; + window.setTimeout(fitAndResize, 0); + } + }); + + loadSnapshot(); + + return () => { + disposed = true; + termRef.current = null; + fitAndResizeRef.current = null; + unsubscribe(); + dataDisposable.dispose(); + resizeObserver.disconnect(); + if (resizeTimerRef.current !== null) { + window.clearTimeout(resizeTimerRef.current); + resizeTimerRef.current = null; + } + container.removeEventListener("pointerdown", handlePointerDown); + container.removeEventListener("touchstart", handleTouchStart); + container.removeEventListener("touchmove", handleTouchMove); + container.removeEventListener("touchend", handleTouchEnd); + container.removeEventListener("touchcancel", handleTouchCancel); + const s = sessionRef.current; + void clientRef.current.detach(s.id, s.projectPathKey).catch(() => undefined); + term.dispose(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [session.id, session.projectPathKey]); + + return ( +
+ ); +} + +function terminalSnapshotEndOffset(snapshot: TerminalSnapshot) { + if ( + typeof snapshot.outputEndOffset === "number" && + Number.isFinite(snapshot.outputEndOffset) && + snapshot.outputEndOffset >= 0 + ) { + return snapshot.outputEndOffset; + } + const startOffset = + typeof snapshot.outputStartOffset === "number" && + Number.isFinite(snapshot.outputStartOffset) && + snapshot.outputStartOffset >= 0 + ? snapshot.outputStartOffset + : 0; + return startOffset + utf8ByteLength(snapshot.output); +} + +function writeTerminalEvent( + term: XTerm, + event: TerminalEvent, + setLastOutputOffset: (offset: number) => void, + lastOutputOffset: number, +): "written" | "skipped" { + const data = event.data ?? ""; + if (!data) return "skipped"; + const startOffset = event.outputStartOffset; + const endOffset = event.outputEndOffset; + if ( + typeof startOffset === "number" && + Number.isFinite(startOffset) && + typeof endOffset === "number" && + Number.isFinite(endOffset) && + endOffset >= startOffset + ) { + if (endOffset <= lastOutputOffset) return "skipped"; + const alreadyWritten = Math.max(0, lastOutputOffset - startOffset); + term.write(alreadyWritten > 0 ? sliceUtf8Bytes(data, alreadyWritten) : data); + setLastOutputOffset(endOffset); + return "written"; + } + term.write(data); + setLastOutputOffset(lastOutputOffset + utf8ByteLength(data)); + return "written"; +} + +function sliceUtf8Bytes(value: string, byteOffset: number) { + if (byteOffset <= 0) return value; + let consumed = 0; + let index = 0; + for (const segment of value) { + const next = consumed + utf8ByteLengthOfCodePoint(segment); + if (next <= byteOffset) { + consumed = next; + index += segment.length; + continue; + } + if (consumed < byteOffset) { + index += segment.length; + } + return value.slice(index); + } + return ""; +} + +function utf8ByteLength(value: string) { + let length = 0; + for (const segment of value) { + length += utf8ByteLengthOfCodePoint(segment); + } + return length; +} + +function utf8ByteLengthOfCodePoint(value: string) { + const codePoint = value.codePointAt(0) ?? 0; + if (codePoint <= 0x7f) return 1; + if (codePoint <= 0x7ff) return 2; + if (codePoint <= 0xffff) return 3; + return 4; +} diff --git a/crates/agent-gateway/web/src/components/ui/input.tsx b/crates/agent-gateway/web/src/components/ui/input.tsx index 480494415..028196072 100644 --- a/crates/agent-gateway/web/src/components/ui/input.tsx +++ b/crates/agent-gateway/web/src/components/ui/input.tsx @@ -10,7 +10,7 @@ export const Input = React.forwardRef( ( return (