diff --git a/Cargo.lock b/Cargo.lock index 989f24c3c..dff2cdac7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -958,15 +958,6 @@ dependencies = [ "strsim", ] -[[package]] -name = "clap_complete" -version = "4.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7a9bfdb35811f9e59832f0f05975114d2251b415fb534108e6f34060fd772" -dependencies = [ - "clap", -] - [[package]] name = "clap_derive" version = "4.6.1" @@ -1033,7 +1024,7 @@ dependencies = [ "bs58", "coins-core", "digest 0.10.7", - "hmac 0.12.1", + "hmac", "k256", "serde", "sha2 0.10.9", @@ -1048,7 +1039,7 @@ checksum = "3db8fba409ce3dc04f7d804074039eb68b960b0829161f8e06c95fea3f122528" dependencies = [ "bitvec", "coins-bip32", - "hmac 0.12.1", + "hmac", "once_cell", "pbkdf2 0.12.2", "rand 0.8.6", @@ -1747,19 +1738,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "dialoguer" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f104b501bf2364e78d0d3974cbc774f738f5865306ed128e1e0d7499c0ad96" -dependencies = [ - "console", - "fuzzy-matcher", - "shell-words", - "tempfile", - "zeroize", -] - [[package]] name = "digest" version = "0.10.7" @@ -1781,7 +1759,6 @@ dependencies = [ "block-buffer 0.12.0", "const-oid 0.10.2", "crypto-common 0.2.1", - "ctutils", ] [[package]] @@ -2123,7 +2100,7 @@ dependencies = [ "ctr", "digest 0.10.7", "hex", - "hmac 0.12.1", + "hmac", "pbkdf2 0.11.0", "rand 0.8.6", "scrypt", @@ -2274,12 +2251,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "fallible-iterator" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" - [[package]] name = "fallible-iterator" version = "0.3.0" @@ -2643,15 +2614,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fuzzy-matcher" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" -dependencies = [ - "thread_local", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -2691,7 +2653,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] @@ -2914,7 +2876,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ - "hmac 0.12.1", + "hmac", ] [[package]] @@ -2926,15 +2888,6 @@ dependencies = [ "digest 0.10.7", ] -[[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 = "hostname" version = "0.4.2" @@ -3873,7 +3826,7 @@ dependencies = [ "indexmap", "itoa", "log", - "md-5 0.10.6", + "md-5", "nom 8.0.0", "nom_locate", "rand 0.9.4", @@ -3982,16 +3935,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "md-5" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" -dependencies = [ - "cfg-if", - "digest 0.11.3", -] - [[package]] name = "md5" version = "0.8.0" @@ -4068,7 +4011,7 @@ checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "log", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "windows-sys 0.61.2", ] @@ -4581,15 +4524,6 @@ dependencies = [ "objc2-foundation 0.3.2", ] -[[package]] -name = "objc2-system-configuration" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396" -dependencies = [ - "objc2-core-foundation", -] - [[package]] name = "objc2-ui-kit" version = "0.3.2" @@ -4708,7 +4642,6 @@ dependencies = [ "anyhow", "arboard", "argon2", - "async-imap", "async-trait", "axum", "base64 0.22.1", @@ -4720,14 +4653,12 @@ dependencies = [ "chrono", "chrono-tz", "clap", - "clap_complete", "coins-bip39", "console", "cpal", "cron", "crossterm", "curve25519-dalek", - "dialoguer", "directories", "dirs 5.0.1", "docx-rs", @@ -4747,7 +4678,7 @@ dependencies = [ "glob", "hex", "hkdf", - "hmac 0.12.1", + "hmac", "hostname", "hound", "iana-time-zone", @@ -4757,30 +4688,22 @@ dependencies = [ "lettre", "libc", "log", - "mail-parser", "motosan-ai-oauth", "nu-ansi-term 0.46.0", "objc2 0.6.4", "objc2-contacts", "objc2-foundation 0.3.2", "once_cell", - "opentelemetry", - "opentelemetry-otlp", - "opentelemetry_sdk", "parking_lot", "pdf-extract", - "postgres", "ppt-rs", - "prometheus", "proptest", - "prost", "rand 0.10.1", "ratatui", "rdev", "regex", "reqwest 0.12.28", "ring", - "ripemd", "rppal", "rusqlite", "rustls", @@ -4793,7 +4716,6 @@ dependencies = [ "serde_yaml", "sha1", "sha2 0.10.9", - "shellexpand", "similar", "socketioxide", "starship-battery", @@ -4818,13 +4740,7 @@ dependencies = [ "tracing-appender", "tracing-log", "tracing-subscriber", - "tree-sitter", - "tree-sitter-python", - "tree-sitter-rust", - "tree-sitter-typescript", "uiautomation", - "unicode-normalization", - "unicode-segmentation", "unicode-width", "url", "urlencoding", @@ -4884,75 +4800,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "opentelemetry" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0142c63252a9e054e68a4c61a5778f7b14f576274d593f8ce883d191a099682" -dependencies = [ - "futures-core", - "futures-sink", - "js-sys", - "pin-project-lite", - "thiserror 2.0.18", -] - -[[package]] -name = "opentelemetry-http" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5683015d09e2df236ef005b17f6f196f0d5f6313c4fa43a7b6a53b52776e4331" -dependencies = [ - "async-trait", - "bytes", - "http 1.4.0", - "opentelemetry", - "reqwest 0.13.1", -] - -[[package]] -name = "opentelemetry-otlp" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9966929966d17620d7c316c643ba62631826e10021409357772d5eea84f62c35" -dependencies = [ - "http 1.4.0", - "opentelemetry", - "opentelemetry-http", - "opentelemetry-proto", - "opentelemetry_sdk", - "prost", - "reqwest 0.13.1", - "thiserror 2.0.18", -] - -[[package]] -name = "opentelemetry-proto" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56d658ba1faf63f7b9c492cfbe6e0ec365440a16132d3270c1065f7b33f1b638" -dependencies = [ - "opentelemetry", - "opentelemetry_sdk", - "prost", -] - -[[package]] -name = "opentelemetry_sdk" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368afaed344110f40b179bb8fbe54bc52d98f9bd2b281799ef32487c2650c956" -dependencies = [ - "futures-channel", - "futures-executor", - "futures-util", - "opentelemetry", - "percent-encoding", - "portable-atomic", - "rand 0.9.4", - "thiserror 2.0.18", -] - [[package]] name = "option-ext" version = "0.2.0" @@ -5100,7 +4947,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ "digest 0.10.7", - "hmac 0.12.1", + "hmac", "password-hash 0.4.2", "sha2 0.10.9", ] @@ -5112,7 +4959,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", - "hmac 0.12.1", + "hmac", ] [[package]] @@ -5217,7 +5064,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ "phf_shared 0.13.1", - "serde", ] [[package]] @@ -5429,50 +5275,6 @@ dependencies = [ "portable-atomic", ] -[[package]] -name = "postgres" -version = "0.19.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aacf632d0554ff75f58183694f41dc8999c8a3a43a386994d0ec2d034f1dfbe1" -dependencies = [ - "bytes", - "fallible-iterator 0.2.0", - "futures-util", - "log", - "tokio", - "tokio-postgres", -] - -[[package]] -name = "postgres-protocol" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08808e3c483c46e999108051c78334f473d5adb59d78bb80a1268c7e6aa6c514" -dependencies = [ - "base64 0.22.1", - "byteorder", - "bytes", - "fallible-iterator 0.2.0", - "hmac 0.13.0", - "md-5 0.11.0", - "memchr", - "rand 0.10.1", - "sha2 0.11.0", - "stringprep", -] - -[[package]] -name = "postgres-types" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "851ca9db4932932d69f3ea811b1abe63087a0f740a47692619dd40d4899b68be" -dependencies = [ - "bytes", - "chrono", - "fallible-iterator 0.2.0", - "postgres-protocol", -] - [[package]] name = "postscript" version = "0.14.1" @@ -5562,20 +5364,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "prometheus" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ca5326d8d0b950a9acd87e6a3f94745394f62e4dae1b1ee22b2bc0c394af43a" -dependencies = [ - "cfg-if", - "fnv", - "lazy_static", - "memchr", - "parking_lot", - "thiserror 2.0.18", -] - [[package]] name = "proptest" version = "1.11.0" @@ -6170,7 +5958,6 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 1.0.7", ] [[package]] @@ -6179,7 +5966,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "hmac 0.12.1", + "hmac", "subtle", ] @@ -6283,7 +6070,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b3492ea85308705c3a5cc24fb9b9cf77273d30590349070db42991202b214c4" dependencies = [ "bitflags 2.13.1", - "fallible-iterator 0.3.0", + "fallible-iterator", "fallible-streaming-iterator", "hashlink", "libsqlite3-sys", @@ -6520,7 +6307,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d" dependencies = [ - "hmac 0.12.1", + "hmac", "pbkdf2 0.11.0", "salsa20", "sha2 0.10.9", @@ -6897,21 +6684,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shell-words" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" - -[[package]] -name = "shellexpand" -version = "3.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" -dependencies = [ - "dirs 6.0.0", -] - [[package]] name = "shlex" version = "1.3.0" @@ -7575,7 +7347,7 @@ dependencies = [ "futures", "futures-util", "hex", - "hmac 0.12.1", + "hmac", "lettre", "mail-parser", "parking_lot", @@ -7687,7 +7459,7 @@ dependencies = [ "ed25519-dalek", "futures-util", "hkdf", - "hmac 0.12.1", + "hmac", "rand 0.8.6", "reqwest 0.12.28", "serde", @@ -7772,32 +7544,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-postgres" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a528f7d280f6d5b9cd149635c8705b0dd049754bc67d81d31fa25169a93809d3" -dependencies = [ - "async-trait", - "byteorder", - "bytes", - "fallible-iterator 0.2.0", - "futures-channel", - "futures-util", - "log", - "parking_lot", - "percent-encoding", - "phf 0.13.1", - "pin-project-lite", - "postgres-protocol", - "postgres-types", - "rand 0.10.1", - "socket2", - "tokio", - "tokio-util", - "whoami", -] - [[package]] name = "tokio-rustls" version = "0.26.4" @@ -8545,7 +8291,7 @@ dependencies = [ "futures", "hex", "hkdf", - "hmac 0.12.1", + "hmac", "log", "md5", "once_cell", @@ -8632,7 +8378,7 @@ dependencies = [ "ghash 0.6.0", "hex", "hkdf", - "hmac 0.12.1", + "hmac", "log", "prost", "rand 0.10.1", @@ -8711,15 +8457,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - [[package]] name = "wasip2" version = "1.0.3+wasi-0.2.9" @@ -8738,15 +8475,6 @@ dependencies = [ "wit-bindgen 0.51.0", ] -[[package]] -name = "wasite" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66fe902b4a6b8028a753d5424909b764ccf79b7a209eac9bf97e59cda9f71a42" -dependencies = [ - "wasi 0.14.7+wasi-0.2.4", -] - [[package]] name = "wasm-bindgen" version = "0.2.121" @@ -9079,19 +8807,6 @@ dependencies = [ "semver", ] -[[package]] -name = "whoami" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "998767ef88740d1f5b0682a9c53c24431453923962269c2db68ee43788c5a40d" -dependencies = [ - "libc", - "libredox", - "objc2-system-configuration", - "wasite", - "web-sys", -] - [[package]] name = "winapi" version = "0.3.9" @@ -10082,7 +9797,7 @@ dependencies = [ "crc32fast", "crossbeam-utils", "flate2", - "hmac 0.12.1", + "hmac", "pbkdf2 0.11.0", "sha1", "time", diff --git a/Cargo.toml b/Cargo.toml index c8fbbac0d..51d395f83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,13 +96,6 @@ tinyagents = { version = "1.7", features = ["sqlite"] } # link. Keep the version pin in lockstep with the submodule tag. tinycortex = { version = "0.1", features = ["git-diff", "persona", "sync"] } tinychannels = { version = "0.1", features = ["relay-websocket"] } -# TokenJuice code compressor — AST-aware signature extraction. Optional (C build) -# behind the default `tokenjuice-treesitter` feature; disabling it falls back to -# the language-agnostic brace-depth heuristic. See src/openhuman/tokenjuice/compressors/code.rs. -tree-sitter = { version = "0.26", optional = true } -tree-sitter-rust = { version = "0.24", optional = true } -tree-sitter-typescript = { version = "0.23", optional = true } -tree-sitter-python = { version = "0.25", optional = true } serde = { version = "1", features = ["derive"] } serde_json = "1" serde_repr = "0.1" @@ -180,21 +173,16 @@ cron = "0.12" futures-util = "0.3" directories = "6" toml = "1.0" -shellexpand = "3.1" schemars = "1.2" tracing = { version = "0.1", default-features = false } tracing-log = "0.2" tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt", "ansi", "env-filter"] } tracing-appender = "0.2" -prometheus = { version = "0.14", default-features = false } urlencoding = "2.1" motosan-ai-oauth = { version = "0.2", features = ["codex"] } thiserror = "2.0" ring = "0.17" -prost = { version = "0.14", default-features = false } -postgres = { version = "0.19", features = ["with-chrono-0_4"] } chrono-tz = "0.10" -dialoguer = { version = "0.12", features = ["fuzzy-select"] } dotenvy = "0.15" console = "0.16" regex = "1.10" @@ -206,14 +194,6 @@ regex = "1.10" aho-corasick = "1.1" walkdir = "2" glob = "0.3" -unicode-segmentation = "1" -unicode-width = "0.2" -# NFKC + combining-mark detection for the cross-thread search inverted -# index (`memory_conversations::tokenize`). NFKC unifies CJK half/full- -# width variants and Arabic presentation forms; `canonical_combining_class` -# lets us strip diacritics across all scripts (Polish ą→a, Arabic harakat, -# Hebrew niqqud, etc.) without per-language tables. -unicode-normalization = "0.1" hostname = "0.4.2" rustls = { version = "0.23", features = ["ring"] } rustls-pki-types = "1.14.0" @@ -222,15 +202,9 @@ webpki-roots = "1.0.6" sysinfo = { version = "0.33", default-features = false, features = ["system"] } keyring = { version = "3", features = ["apple-native", "windows-native", "linux-native"] } clap = { version = "4.5", features = ["derive"] } -clap_complete = "4.5" lettre = { version = "0.11.22", default-features = false, features = ["builder", "smtp-transport", "rustls-tls"], optional = true } -mail-parser = "0.11.2" -async-imap = { version = "0.11", features = ["runtime-tokio"], default-features = false } axum = { version = "0.8", default-features = false, features = ["http1", "json", "tokio", "query", "ws", "macros"] } tower = { version = "0.5", default-features = false } -opentelemetry = { version = "0.32", default-features = false, features = ["trace", "metrics"] } -opentelemetry_sdk = { version = "0.32", default-features = false, features = ["trace", "metrics"] } -opentelemetry-otlp = { version = "0.32", default-features = false, features = ["trace", "metrics", "http-proto", "reqwest-client", "reqwest-rustls-webpki-roots"] } sentry = { version = "0.47.0", default-features = false, features = ["backtrace", "contexts", "panic", "tracing", "debug-images", "reqwest", "rustls"] } tokio-stream = { version = "0.1.18", features = ["full"] } url = "2" @@ -255,11 +229,9 @@ ethers-signers = { version = "2.0.14", default-features = false } # - bitcoin: P2WPKH PSBT build/sign/broadcast (includes secp256k1). # - ed25519-dalek: Solana transaction signing. # - bs58: Solana base58 addresses + Tron base58check addresses. -# - ripemd: RIPEMD160 for BTC HASH160 (P2WPKH) and Tron address hash. bitcoin = { version = "0.32", default-features = false, features = ["std", "secp-recovery", "rand-std"], optional = true } ed25519-dalek = { version = "2", default-features = false, features = ["std", "rand_core"] } bs58 = { version = "0.5", default-features = false, features = ["std", "check"] } -ripemd = "0.1" # Shared BIP-39 mnemonic → seed for non-EVM chains (BTC P2WPKH derivation, # Tron secp256k1 derivation, Solana ed25519 SLIP-0010 derivation). Same crate # ethers-signers uses internally, exposed as a direct dep so we can derive @@ -282,6 +254,9 @@ ppt-rs = "0.2.14" # `src/openhuman/tui/` behind `#[cfg(feature = "tui")]`. ratatui = { version = "0.30", optional = true } crossterm = { version = "0.29", optional = true } +# Terminal column-width measurement for the `tui` chat renderer +# (`src/openhuman/tui/render.rs`); only compiled behind `#[cfg(feature = "tui")]`. +unicode-width = { version = "0.2", optional = true } # Native-Rust `.docx` writer for the `generate_document` tool (GH #4847). # Pure Rust (no subprocess / managed runtime), MIT-licensed, actively # maintained (2.8M downloads, last release 0.4.20 — Apr 2026). Mirrors the @@ -478,7 +453,7 @@ mcp = [] # are `#[cfg(feature = "tui")]`; when off, `tui::stub::run_from_cli` returns a # build-fact "tui feature disabled at compile time" error from the untouched # `"tui" | "chat"` CLI arm (mirrors the `mcp` stub pattern). -tui = ["dep:ratatui", "dep:crossterm"] +tui = ["dep:ratatui", "dep:crossterm", "dep:unicode-width"] sandbox-landlock = ["dep:landlock"] sandbox-bubblewrap = [] peripheral-rpi = ["dep:rppal"] diff --git a/app/package.json b/app/package.json index 14ad44515..8875fb986 100644 --- a/app/package.json +++ b/app/package.json @@ -78,8 +78,6 @@ "@noble/secp256k1": "^3.0.0", "@radix-ui/react-dialog": "^1.1.15", "@reduxjs/toolkit": "^2.11.2", - "@remotion/player": "4.0.454", - "@remotion/zod-types": "4.0.454", "@rive-app/react-webgl2": "^4.28.6", "@scure/base": "^2.2.0", "@scure/bip32": "^2.0.1", @@ -116,7 +114,6 @@ "rehype-katex": "^7.0.1", "remark-gfm": "^4.0.1", "remark-math": "^6.0.0", - "remotion": "4.0.454", "socket.io-client": "^4.8.3", "tauri-plugin-ptt-api": "workspace:*", "three": "^0.183.2", diff --git a/app/src-tauri/Cargo.lock b/app/src-tauri/Cargo.lock index f4eefc9f3..cdacc4543 100644 --- a/app/src-tauri/Cargo.lock +++ b/app/src-tauri/Cargo.lock @@ -1217,15 +1217,6 @@ dependencies = [ "strsim", ] -[[package]] -name = "clap_complete" -version = "4.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7a9bfdb35811f9e59832f0f05975114d2251b415fb534108e6f34060fd772" -dependencies = [ - "clap", -] - [[package]] name = "clap_derive" version = "4.6.1" @@ -1262,12 +1253,6 @@ dependencies = [ "cc", ] -[[package]] -name = "cmov" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" - [[package]] name = "cocoa" version = "0.22.0" @@ -1292,7 +1277,7 @@ dependencies = [ "bs58", "coins-core", "digest 0.10.7", - "hmac 0.12.1", + "hmac", "k256", "serde", "sha2 0.10.9", @@ -1307,7 +1292,7 @@ checksum = "3db8fba409ce3dc04f7d804074039eb68b960b0829161f8e06c95fea3f122528" dependencies = [ "bitvec", "coins-bip32", - "hmac 0.12.1", + "hmac", "once_cell", "pbkdf2 0.12.2", "rand 0.8.6", @@ -1805,15 +1790,6 @@ dependencies = [ "cipher", ] -[[package]] -name = "ctutils" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" -dependencies = [ - "cmov", -] - [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -1999,19 +1975,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "dialoguer" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f104b501bf2364e78d0d3974cbc774f738f5865306ed128e1e0d7499c0ad96" -dependencies = [ - "console", - "fuzzy-matcher", - "shell-words", - "tempfile", - "zeroize", -] - [[package]] name = "digest" version = "0.10.7" @@ -2033,7 +1996,6 @@ dependencies = [ "block-buffer 0.12.0", "const-oid 0.10.2", "crypto-common 0.2.1", - "ctutils", ] [[package]] @@ -2536,7 +2498,7 @@ dependencies = [ "ctr", "digest 0.10.7", "hex", - "hmac 0.12.1", + "hmac", "pbkdf2 0.11.0", "rand 0.8.6", "scrypt", @@ -2687,12 +2649,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "fallible-iterator" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" - [[package]] name = "fallible-iterator" version = "0.3.0" @@ -3047,15 +3003,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fuzzy-matcher" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" -dependencies = [ - "thread_local", -] - [[package]] name = "fxhash" version = "0.2.1" @@ -3563,7 +3510,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ - "hmac 0.12.1", + "hmac", ] [[package]] @@ -3575,15 +3522,6 @@ dependencies = [ "digest 0.10.7", ] -[[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 = "hostname" version = "0.4.2" @@ -4598,7 +4536,7 @@ dependencies = [ "indexmap 2.14.0", "itoa", "log", - "md-5 0.10.6", + "md-5", "nom 8.0.0", "nom_locate", "rand 0.9.4", @@ -4748,16 +4686,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "md-5" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" -dependencies = [ - "cfg-if", - "digest 0.11.3", -] - [[package]] name = "memchr" version = "2.8.0" @@ -5452,15 +5380,6 @@ dependencies = [ "objc2-core-foundation", ] -[[package]] -name = "objc2-system-configuration" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396" -dependencies = [ - "objc2-core-foundation", -] - [[package]] name = "objc2-ui-kit" version = "0.3.2" @@ -5610,7 +5529,6 @@ dependencies = [ "anyhow", "arboard", "argon2", - "async-imap", "async-trait", "axum", "base64 0.22.1", @@ -5622,13 +5540,11 @@ dependencies = [ "chrono", "chrono-tz", "clap", - "clap_complete", "coins-bip39", "console", "cpal", "cron", "curve25519-dalek", - "dialoguer", "directories 6.0.0", "dirs 5.0.1", "docx-rs", @@ -5646,7 +5562,7 @@ dependencies = [ "glob", "hex", "hkdf", - "hmac 0.12.1", + "hmac", "hostname", "hound", "iana-time-zone", @@ -5655,28 +5571,20 @@ dependencies = [ "lettre", "libc", "log", - "mail-parser", "motosan-ai-oauth", "nu-ansi-term 0.46.0", "objc2 0.6.4", "objc2-contacts", "objc2-foundation 0.3.2", "once_cell", - "opentelemetry", - "opentelemetry-otlp", - "opentelemetry_sdk", "parking_lot", "pdf-extract", - "postgres", "ppt-rs", - "prometheus", - "prost", "rand 0.10.1", "rdev", "regex", "reqwest 0.12.28", "ring", - "ripemd", "rusqlite", "rustls", "rustls-pki-types", @@ -5688,7 +5596,6 @@ dependencies = [ "serde_yaml", "sha1", "sha2 0.10.9", - "shellexpand", "similar", "socketioxide", "starship-battery", @@ -5714,9 +5621,6 @@ dependencies = [ "tracing-log", "tracing-subscriber", "uiautomation", - "unicode-normalization", - "unicode-segmentation", - "unicode-width", "url", "urlencoding", "uuid 1.23.1", @@ -5774,75 +5678,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "opentelemetry" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0142c63252a9e054e68a4c61a5778f7b14f576274d593f8ce883d191a099682" -dependencies = [ - "futures-core", - "futures-sink", - "js-sys", - "pin-project-lite", - "thiserror 2.0.18", -] - -[[package]] -name = "opentelemetry-http" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5683015d09e2df236ef005b17f6f196f0d5f6313c4fa43a7b6a53b52776e4331" -dependencies = [ - "async-trait", - "bytes", - "http", - "opentelemetry", - "reqwest 0.13.1", -] - -[[package]] -name = "opentelemetry-otlp" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9966929966d17620d7c316c643ba62631826e10021409357772d5eea84f62c35" -dependencies = [ - "http", - "opentelemetry", - "opentelemetry-http", - "opentelemetry-proto", - "opentelemetry_sdk", - "prost", - "reqwest 0.13.1", - "thiserror 2.0.18", -] - -[[package]] -name = "opentelemetry-proto" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56d658ba1faf63f7b9c492cfbe6e0ec365440a16132d3270c1065f7b33f1b638" -dependencies = [ - "opentelemetry", - "opentelemetry_sdk", - "prost", -] - -[[package]] -name = "opentelemetry_sdk" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368afaed344110f40b179bb8fbe54bc52d98f9bd2b281799ef32487c2650c956" -dependencies = [ - "futures-channel", - "futures-executor", - "futures-util", - "opentelemetry", - "percent-encoding", - "portable-atomic", - "rand 0.9.4", - "thiserror 2.0.18", -] - [[package]] name = "option-ext" version = "0.2.0" @@ -6022,7 +5857,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ "digest 0.10.7", - "hmac 0.12.1", + "hmac", "password-hash 0.4.2", "sha2 0.10.9", ] @@ -6034,7 +5869,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", - "hmac 0.12.1", + "hmac", ] [[package]] @@ -6433,50 +6268,6 @@ dependencies = [ "portable-atomic", ] -[[package]] -name = "postgres" -version = "0.19.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aacf632d0554ff75f58183694f41dc8999c8a3a43a386994d0ec2d034f1dfbe1" -dependencies = [ - "bytes", - "fallible-iterator 0.2.0", - "futures-util", - "log", - "tokio", - "tokio-postgres", -] - -[[package]] -name = "postgres-protocol" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08808e3c483c46e999108051c78334f473d5adb59d78bb80a1268c7e6aa6c514" -dependencies = [ - "base64 0.22.1", - "byteorder", - "bytes", - "fallible-iterator 0.2.0", - "hmac 0.13.0", - "md-5 0.11.0", - "memchr", - "rand 0.10.1", - "sha2 0.11.0", - "stringprep", -] - -[[package]] -name = "postgres-types" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "851ca9db4932932d69f3ea811b1abe63087a0f740a47692619dd40d4899b68be" -dependencies = [ - "bytes", - "chrono", - "fallible-iterator 0.2.0", - "postgres-protocol", -] - [[package]] name = "postscript" version = "0.14.1" @@ -6617,20 +6408,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "prometheus" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ca5326d8d0b950a9acd87e6a3f94745394f62e4dae1b1ee22b2bc0c394af43a" -dependencies = [ - "cfg-if", - "fnv", - "lazy_static", - "memchr", - "parking_lot", - "thiserror 2.0.18", -] - [[package]] name = "proptest" version = "1.11.0" @@ -7146,7 +6923,6 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 1.0.7", ] [[package]] @@ -7169,7 +6945,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "hmac 0.12.1", + "hmac", "subtle", ] @@ -7303,7 +7079,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b3492ea85308705c3a5cc24fb9b9cf77273d30590349070db42991202b214c4" dependencies = [ "bitflags 2.11.1", - "fallible-iterator 0.3.0", + "fallible-iterator", "fallible-streaming-iterator", "hashlink", "libsqlite3-sys", @@ -7576,7 +7352,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d" dependencies = [ - "hmac 0.12.1", + "hmac", "pbkdf2 0.11.0", "salsa20", "sha2 0.10.9", @@ -8076,21 +7852,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shell-words" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" - -[[package]] -name = "shellexpand" -version = "3.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" -dependencies = [ - "dirs 6.0.0", -] - [[package]] name = "shlex" version = "1.3.0" @@ -9258,7 +9019,7 @@ dependencies = [ "futures", "futures-util", "hex", - "hmac 0.12.1", + "hmac", "lettre", "mail-parser", "parking_lot", @@ -9365,7 +9126,7 @@ dependencies = [ "ed25519-dalek", "futures-util", "hkdf", - "hmac 0.12.1", + "hmac", "rand 0.8.6", "reqwest 0.12.28", "serde", @@ -9442,32 +9203,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-postgres" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a528f7d280f6d5b9cd149635c8705b0dd049754bc67d81d31fa25169a93809d3" -dependencies = [ - "async-trait", - "byteorder", - "bytes", - "fallible-iterator 0.2.0", - "futures-channel", - "futures-util", - "log", - "parking_lot", - "percent-encoding", - "phf 0.13.1", - "pin-project-lite", - "postgres-protocol", - "postgres-types", - "rand 0.10.1", - "socket2", - "tokio", - "tokio-util", - "whoami", -] - [[package]] name = "tokio-rustls" version = "0.26.4" @@ -10364,15 +10099,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - [[package]] name = "wasip2" version = "1.0.3+wasi-0.2.9" @@ -10391,15 +10117,6 @@ dependencies = [ "wit-bindgen 0.51.0", ] -[[package]] -name = "wasite" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66fe902b4a6b8028a753d5424909b764ccf79b7a209eac9bf97e59cda9f71a42" -dependencies = [ - "wasi 0.14.7+wasi-0.2.4", -] - [[package]] name = "wasm-bindgen" version = "0.2.121" @@ -10728,19 +10445,6 @@ dependencies = [ "semver", ] -[[package]] -name = "whoami" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "998767ef88740d1f5b0682a9c53c24431453923962269c2db68ee43788c5a40d" -dependencies = [ - "libc", - "libredox", - "objc2-system-configuration", - "wasite", - "web-sys", -] - [[package]] name = "winapi" version = "0.3.9" @@ -11915,7 +11619,7 @@ dependencies = [ "crc32fast", "crossbeam-utils", "flate2", - "hmac 0.12.1", + "hmac", "pbkdf2 0.11.0", "sha1", "time", diff --git a/package.json b/package.json index e87ca4f8c..f38a97b6a 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,6 @@ "ws": "^8.20.0" }, "dependencies": { - "@rive-app/react-canvas": "^4.28.6", "@tauri-apps/api": "2.10.1" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6af4d5025..39b69ad82 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,9 +11,6 @@ importers: .: dependencies: - '@rive-app/react-canvas': - specifier: ^4.28.6 - version: 4.28.6(react@19.2.5) '@tauri-apps/api': specifier: 2.10.1 version: 2.10.1 @@ -48,12 +45,6 @@ importers: '@reduxjs/toolkit': specifier: ^2.11.2 version: 2.11.2(react-redux@9.2.0(@types/react@19.2.14)(react@19.2.5)(redux@5.0.1))(react@19.2.5) - '@remotion/player': - specifier: 4.0.454 - version: 4.0.454(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@remotion/zod-types': - specifier: 4.0.454 - version: 4.0.454(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6) '@rive-app/react-webgl2': specifier: ^4.28.6 version: 4.28.6(react@19.2.5) @@ -162,9 +153,6 @@ importers: remark-math: specifier: ^6.0.0 version: 6.0.0 - remotion: - specifier: 4.0.454 - version: 4.0.454(react-dom@19.2.5(react@19.2.5))(react@19.2.5) socket.io-client: specifier: ^4.8.3 version: 4.8.3 @@ -1407,25 +1395,6 @@ packages: react-redux: optional: true - '@remotion/player@4.0.454': - resolution: {integrity: sha512-Q4fEWapH21uEErdOfZxagOQcxbf/JGxPwqBOrW/NG9Y5qHgMMe5tBUCnr8rEqOA4txW/S4QHnN9HGWfV2nIvTg==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - - '@remotion/zod-types@4.0.454': - resolution: {integrity: sha512-nNXcHF4AmgihwI7sMx50rSgFuCfNyroP0OsgN/mMkQI7Y66uPWBh4Pcytlxs039EShvsiiH621E2TKZOX8ciRA==} - peerDependencies: - zod: 4.3.6 - - '@rive-app/canvas@2.37.8': - resolution: {integrity: sha512-nffrPG+VkBKHAxZdcqYlP5M+n/mOAoagj774HH397UPTsfC27gwQURg64i6dX7WswMc5qIVX0rzCrZ86Wb2HOA==} - - '@rive-app/react-canvas@4.28.6': - resolution: {integrity: sha512-tMEb7uDr+xuPny4HRVnkfGDHgTCkTFn15u7GHj12hC94N59g/dO15TiGr9312GVTO82FulOymOKy6sgxZyi5rw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0 - '@rive-app/react-webgl2@4.28.6': resolution: {integrity: sha512-QqtlD7bm01SxMLuEHE/BfhiPoX3RZ2/B32nF0IR0IRPe2ykxMeebWPtsX50AL9EKLbOh3Fgk3PJ7smkkvQwD2w==} peerDependencies: @@ -5196,12 +5165,6 @@ packages: remark-stringify@11.0.0: resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} - remotion@4.0.454: - resolution: {integrity: sha512-NGzA7HLBpzRPo1jAyLGXG7AaAdUYHJ18n06GM3nBvFkC5zVZua+2rhezxDTkMJFEMgL54cRQ2Zw/2yd3cWCRbg==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -7097,27 +7060,6 @@ snapshots: react: 19.2.5 react-redux: 9.2.0(@types/react@19.2.14)(react@19.2.5)(redux@5.0.1) - '@remotion/player@4.0.454(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - remotion: 4.0.454(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - - '@remotion/zod-types@4.0.454(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6)': - dependencies: - remotion: 4.0.454(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - zod: 4.3.6 - transitivePeerDependencies: - - react - - react-dom - - '@rive-app/canvas@2.37.8': {} - - '@rive-app/react-canvas@4.28.6(react@19.2.5)': - dependencies: - '@rive-app/canvas': 2.37.8 - react: 19.2.5 - '@rive-app/react-webgl2@4.28.6(react@19.2.5)': dependencies: '@rive-app/webgl2': 2.37.8 @@ -11677,11 +11619,6 @@ snapshots: mdast-util-to-markdown: 2.1.2 unified: 11.0.5 - remotion@4.0.454(react-dom@19.2.5(react@19.2.5))(react@19.2.5): - dependencies: - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - require-directory@2.1.1: {} require-from-string@2.0.2: {} diff --git a/scripts/generate-test-inventory.mjs b/scripts/generate-test-inventory.mjs index 83880dfc1..61f0f8665 100644 --- a/scripts/generate-test-inventory.mjs +++ b/scripts/generate-test-inventory.mjs @@ -68,7 +68,6 @@ const DOMAIN_ALLOWLIST = new Set([ 'plan_review', 'provider_surfaces', 'recall_calendar', - 'redirect_links', 'referral', 'session_import', 'skill_runtime', diff --git a/src/core/all.rs b/src/core/all.rs index b5821c13d..6fd7fd648 100644 --- a/src/core/all.rs +++ b/src/core/all.rs @@ -575,12 +575,6 @@ fn build_registered_controllers() -> Vec { DomainGroup::Memory, crate::openhuman::memory_diff::all_memory_diff_registered_controllers(), ); - // Link shortener for long tracking URLs — saves LLM tokens - push( - &mut controllers, - DomainGroup::Platform, - crate::openhuman::redirect_links::all_redirect_links_registered_controllers(), - ); // Referral and growth tracking push( &mut controllers, @@ -951,9 +945,6 @@ pub fn namespace_description(namespace: &str) -> Option<&'static str> { "memory_diff" => Some( "Snapshot-based change tracking for memory sources — capture state, compute diffs, and surface changes to agents.", ), - "redirect_links" => Some( - "Shorten long tracking URLs to `openhuman://link/` placeholders (SQLite-backed) to save tokens in prompts, with round-trip rewrite helpers.", - ), "referral" => Some("Referral codes, stats, and apply flows via the hosted backend API."), "run_ledger" => Some( "Durable agent and workflow run state, child lineage, events, telemetry, and checkpoint references.", diff --git a/src/core/all_tests.rs b/src/core/all_tests.rs index a562c711e..5667eabeb 100644 --- a/src/core/all_tests.rs +++ b/src/core/all_tests.rs @@ -119,7 +119,6 @@ fn registered_controller_rpc_method_name() { fn namespace_description_known_namespaces() { assert!(namespace_description("memory").is_some()); assert!(namespace_description("memory_tree").is_some()); - assert!(namespace_description("redirect_links").is_some()); assert!(namespace_description("billing").is_some()); assert!(namespace_description("config").is_some()); assert!(namespace_description("health").is_some()); diff --git a/src/openhuman/mod.rs b/src/openhuman/mod.rs index dc2657401..3bff80881 100644 --- a/src/openhuman/mod.rs +++ b/src/openhuman/mod.rs @@ -100,7 +100,6 @@ pub mod profiles; pub mod prompt_injection; pub mod provider_surfaces; pub mod recall_calendar; -pub mod redirect_links; pub mod referral; #[cfg(feature = "flows")] pub mod rhai_workflows; diff --git a/src/openhuman/redirect_links/README.md b/src/openhuman/redirect_links/README.md deleted file mode 100644 index 1f5d8984d..000000000 --- a/src/openhuman/redirect_links/README.md +++ /dev/null @@ -1,82 +0,0 @@ -# redirect_links - -Redirect-link shortener for token-heavy URLs. Long tracking URLs (e.g. `trip.com/forward/...?bizData=...`) burn model tokens every time they pass through a prompt. This domain encodes them to a short `openhuman://link/` placeholder on inbound text, keeps the full URL in a local SQLite store, and expands the placeholder back to the original URL on outbound messages so the user never sees the placeholder. It also has a separate helper for tagging public `openhm.xyz` short links with a `?u=` attribution param on the way out. - -## Responsibilities - -- **Shorten** a long URL to a content-addressed `openhuman://link/` form and persist it (idempotent / deterministic by URL). -- **Expand** a short id back to its full URL, bumping a hit counter + `last_used_at`. -- **Inbound rewrite**: replace every long URL (≥ `min_len`, default 80) in a text blob with its placeholder, preserving surrounding prose and trailing sentence punctuation. -- **Outbound rewrite**: replace every `openhuman://link/` placeholder in text back to the stored URL; unknown ids are left untouched (nothing silently disappears). -- **Public-link attribution**: append `?u=` (URL-encoded, idempotent, fragment-safe) to `openhm.xyz/` URLs, guarding against lookalike domains. -- List and remove stored links; expose all of the above over JSON-RPC. - -## Key files - -| File | Role | -| --- | --- | -| `src/openhuman/redirect_links/mod.rs` | Export-focused: module docstring, `mod` decls, `pub use` re-exports of ops + schemas + types; aliases `ops as rpc`. | -| `src/openhuman/redirect_links/types.rs` | Serde types: `RedirectLink`, `RewriteReplacement`, `RewriteResult`. | -| `src/openhuman/redirect_links/ops.rs` | Business logic: URL/short-URL/public-URL regexes, inbound/outbound rewrite, `append_user_id_to_public_links`, and the `rl_*` RPC handlers returning `RpcOutcome`. Holds `DEFAULT_MIN_URL_LEN = 80`. | -| `src/openhuman/redirect_links/store.rs` | SQLite persistence: `shorten`/`expand`/`peek`/`list`/`remove`, content-addressed id allocation (SHA-256 hex prefix), schema bootstrap, id<->short-URL helpers (`short_url_for`, `id_from_short`, `SHORT_URL_PREFIX`). | -| `src/openhuman/redirect_links/schemas.rs` | Controller schemas (`all_controller_schemas`, `all_registered_controllers`, `schemas`) + `handle_*` fns delegating to `ops.rs`. | - -## Public surface - -Re-exported from `mod.rs`: - -- Functions (via `pub use ops::…`): `shorten_url`, `expand_link`, `rewrite_inbound`, `rewrite_outbound`, `rewrite_outbound_for_user`, `append_user_id_to_public_links`. -- `pub use ops as rpc` — exposes the `rl_*` handlers under `redirect_links::rpc`. -- Schemas: `all_redirect_links_controller_schemas`, `all_redirect_links_registered_controllers`, `redirect_links_schemas`. -- Types: `RedirectLink`, `RewriteReplacement`, `RewriteResult`. - -Also defined (not re-exported through `mod.rs`): `ops::rewrite_inbound_with_threshold`, `ops::DEFAULT_MIN_URL_LEN`; `store::{shorten, expand, peek, list, remove, short_url_for, id_from_short, SHORT_URL_PREFIX}`. - -## RPC / controllers - -Namespace `redirect_links` (RPC methods `openhuman.redirect_links_`): - -| Function | Inputs | Output | -| --- | --- | --- | -| `shorten` | `url: String` | `link: RedirectLink` | -| `expand` | `id: String` | `link: RedirectLink` (errors if not found) | -| `list` | `limit?: u64` (default 50, max 1000) | `links: RedirectLink[]` (newest first) | -| `remove` | `id: String` | `{ id, removed: bool }` | -| `rewrite_inbound` | `text: String`, `min_len?: u64` (default 80) | `result: RewriteResult` | -| `rewrite_outbound` | `text: String` | `result: RewriteResult` | - -Handlers load config via `config::rpc::load_config_with_timeout()` and return `RpcOutcome` serialized with `into_cli_compatible_json()`. - -## Persistence - -SQLite DB at `{config.workspace_dir}/redirect_links/links.db`, table `redirect_links`: - -| Column | Notes | -| --- | --- | -| `id` | TEXT PRIMARY KEY — SHA-256(url) hex prefix, 8 chars default, grown by 2 up to 32 on prefix collision with a different URL. | -| `url` | TEXT NOT NULL UNIQUE (indexed). | -| `created_at` | RFC3339 TEXT. | -| `last_used_at` | RFC3339 TEXT, nullable; set on each expand. | -| `hit_count` | INTEGER, bumped on each expand. | - -Insert is atomic (`ON CONFLICT DO NOTHING`) so concurrent shortens of the same URL converge on one id with no PRIMARY KEY / UNIQUE error (regression-tested). The connection is opened per call and the schema is created if missing. - -## Dependencies - -- `crate::openhuman::config::Config` — supplies `workspace_dir` for the DB path; handlers call `config::rpc::load_config_with_timeout`. -- `crate::core::all` (`ControllerFuture`, `RegisteredController`) and `crate::core::{ControllerSchema, FieldSchema, TypeSchema}` — controller registry wiring. -- `crate::rpc::RpcOutcome` — RPC return envelope. -- External crates: `rusqlite` (SQLite), `sha2` + `hex` (content-addressed ids), `regex` (URL matching), `chrono` (timestamps), `urlencoding` (user-id encoding), `serde`/`serde_json`, `anyhow`. - -## Used by - -- `src/core/all.rs` registers the controllers + schemas and maps the `"redirect_links"` namespace description; `src/openhuman/mod.rs` declares the module. No other in-tree Rust callers of the rewrite/shorten functions were found — the rewrite pipeline is currently reachable via RPC rather than wired into an inbound/outbound message path inside the core. - -## Notes / gotchas - -- **Ids are content-addressed, not random**: same URL → same id (deterministic, deduped). Removing a link and re-shortening the same URL yields the same id again. -- **Length threshold guards token waste**: the placeholder is ~24 bytes, so URLs below `DEFAULT_MIN_URL_LEN` (80) are left untouched by inbound rewrite. -- **Trailing punctuation handling**: inbound rewrite and public-link tagging strip trailing `. , ; : !` so prose with a URL followed by a period doesn't capture the period into the stored/tagged URL. -- **`append_user_id_to_public_links` is anchored to `openhm.xyz`** specifically and rejects lookalikes (`evil-openhm.xyz`, `openhm.xyz.evil.com`); it splits off `#fragment` so `?u=` always lands in the query, and is idempotent against existing `?u=`/`&u=`. -- **`id_from_short`** accepts both `openhuman://link/` and a bare hex ``, lowercasing the result; non-hex input returns `None`. -- **No agent tools, no event-bus subscribers, no `bus.rs`/`tools.rs`** — this domain is store + ops + RPC only. diff --git a/src/openhuman/redirect_links/mod.rs b/src/openhuman/redirect_links/mod.rs deleted file mode 100644 index 308424567..000000000 --- a/src/openhuman/redirect_links/mod.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! Redirect-link shortener for token-heavy URLs. -//! -//! Long tracking URLs (e.g. `trip.com/forward/...?bizData=...`) burn tokens -//! whenever they pass through a model. This domain encodes them to a short -//! `openhuman://link/` form for inbound prompts, keeps the full URL in -//! a local SQLite store, and expands them back on outbound messages so the -//! user never sees the placeholder. - -pub mod ops; -mod schemas; -mod store; -mod types; - -pub use ops as rpc; -pub use ops::{ - append_user_id_to_public_links, expand_link, rewrite_inbound, rewrite_outbound, - rewrite_outbound_for_user, shorten_url, -}; -pub use schemas::{ - all_controller_schemas as all_redirect_links_controller_schemas, - all_registered_controllers as all_redirect_links_registered_controllers, - schemas as redirect_links_schemas, -}; -pub use types::{RedirectLink, RewriteReplacement, RewriteResult}; diff --git a/src/openhuman/redirect_links/ops.rs b/src/openhuman/redirect_links/ops.rs deleted file mode 100644 index 2e4eb5e4c..000000000 --- a/src/openhuman/redirect_links/ops.rs +++ /dev/null @@ -1,464 +0,0 @@ -use anyhow::Result; -use regex::Regex; -use serde_json::{json, Value}; -use std::sync::OnceLock; - -use crate::openhuman::config::Config; -use crate::openhuman::redirect_links::store; -use crate::openhuman::redirect_links::types::{RedirectLink, RewriteReplacement, RewriteResult}; -use crate::rpc::RpcOutcome; - -/// URLs shorter than this are not worth rewriting — the `openhuman://link/` -/// placeholder is ~24 bytes, so shortening below this just wastes work and -/// tokens. Callers may override via `rewrite_inbound_with_threshold`. -pub const DEFAULT_MIN_URL_LEN: usize = 80; - -fn url_regex() -> &'static Regex { - static RE: OnceLock = OnceLock::new(); - // Wider than the reference regex to catch common tracking-URL characters - // (`#`, `:`, `+`, `@`, `~`, `!`, `,`, `;`). Trailing sentence punctuation - // is stripped below so regular prose doesn't get mangled. - RE.get_or_init(|| Regex::new(r#"https?://[\w\d./\?=%\-&#:+@~!,;]+"#).unwrap()) -} - -fn short_url_regex() -> &'static Regex { - static RE: OnceLock = OnceLock::new(); - RE.get_or_init(|| Regex::new(r"openhuman://link/([0-9a-f]+)").unwrap()) -} - -fn public_url_regex() -> &'static Regex { - static RE: OnceLock = OnceLock::new(); - // Anchor on `https?://` and match the `openhm.xyz` domain specifically to - // avoid lookalikes (evil-openhm.xyz) or mid-token matches. Capture optional - // query and fragment as separate tail parts so callers can safely insert - // `?u=` into the query without polluting the fragment. - RE.get_or_init(|| { - Regex::new( - r#"https?://openhm\.xyz/[A-Za-z0-9_-]+(?:\?[\w\d./\?=%\-&:+@~!,;]*)?(?:#[\w\d./\?=%\-&:+@~!,;]*)?"#, - ) - .unwrap() - }) -} - -/// Strip trailing sentence punctuation (`.`, `,`, `;`, `:`, `!`) so that -/// "see https://example.com/path." doesn't capture the period. -fn trim_trailing_punct(s: &str) -> &str { - s.trim_end_matches(['.', ',', ';', ':', '!']) -} - -/// Shorten a single URL, persisting it in the global store. Idempotent. -pub fn shorten_url(config: &Config, url: &str) -> Result { - store::shorten(config, url) -} - -/// Expand a previously-shortened id back to its full URL. Bumps hit count. -pub fn expand_link(config: &Config, id: &str) -> Result> { - store::expand(config, id) -} - -/// Rewrite every long URL in `text` to `openhuman://link/`, using the -/// default length threshold. -pub fn rewrite_inbound(config: &Config, text: &str) -> Result { - rewrite_inbound_with_threshold(config, text, DEFAULT_MIN_URL_LEN) -} - -pub fn rewrite_inbound_with_threshold( - config: &Config, - text: &str, - min_len: usize, -) -> Result { - let re = url_regex(); - let mut replacements: Vec = Vec::new(); - let mut out = String::with_capacity(text.len()); - let mut cursor = 0usize; - - for m in re.find_iter(text) { - out.push_str(&text[cursor..m.start()]); - let raw = m.as_str(); - let url = trim_trailing_punct(raw); - let trailing = &raw[url.len()..]; - - if url.len() >= min_len { - let link = store::shorten(config, url)?; - out.push_str(&link.short_url); - replacements.push(RewriteReplacement { - original: url.to_string(), - replacement: link.short_url, - id: link.id, - }); - } else { - out.push_str(url); - } - out.push_str(trailing); - cursor = m.end(); - } - out.push_str(&text[cursor..]); - - Ok(RewriteResult { - text: out, - replacements, - }) -} - -/// Replace every `openhuman://link/` placeholder with its stored URL. -/// Unknown ids are left as-is so nothing silently disappears. -pub fn rewrite_outbound(config: &Config, text: &str) -> Result { - let re = short_url_regex(); - let mut replacements: Vec = Vec::new(); - let mut out = String::with_capacity(text.len()); - let mut cursor = 0usize; - - for caps in re.captures_iter(text) { - let whole = caps.get(0).unwrap(); - let id = caps.get(1).unwrap().as_str(); - out.push_str(&text[cursor..whole.start()]); - - match store::expand(config, id)? { - Some(link) => { - out.push_str(&link.url); - replacements.push(RewriteReplacement { - original: whole.as_str().to_string(), - replacement: link.url, - id: link.id, - }); - } - None => { - out.push_str(whole.as_str()); - } - } - cursor = whole.end(); - } - out.push_str(&text[cursor..]); - - Ok(RewriteResult { - text: out, - replacements, - }) -} - -/// Convenience wrapper that runs `rewrite_outbound` and then appends the -/// `user_id` to any public `openhm.xyz` links in the result. -pub fn rewrite_outbound_for_user( - config: &Config, - text: &str, - user_id: Option<&str>, -) -> Result { - let mut result = rewrite_outbound(config, text)?; - result.text = append_user_id_to_public_links(&result.text, user_id); - Ok(result) -} - -/// Append `?u=` to every `openhm.xyz/` URL in a string. -/// If `user_id` is `None`, the text is returned unchanged. -/// Idempotent: URLs already containing a `u=` query parameter are left alone. -pub fn append_user_id_to_public_links(text: &str, user_id: Option<&str>) -> String { - let Some(user_id) = user_id else { - return text.to_string(); - }; - - let re = public_url_regex(); - let encoded_user_id = urlencoding::encode(user_id); - let mut out = String::with_capacity(text.len()); - let mut cursor = 0usize; - - for m in re.find_iter(text) { - out.push_str(&text[cursor..m.start()]); - let raw = m.as_str(); - let url = trim_trailing_punct(raw); - let trailing = &raw[url.len()..]; - - // Split off any fragment (#…) so `?u=` lands in the query, not the fragment. - let (base, fragment) = match url.split_once('#') { - Some((b, f)) => (b, Some(f)), - None => (url, None), - }; - - if !base.contains("?u=") && !base.contains("&u=") { - let separator = if base.contains('?') { "&" } else { "?" }; - out.push_str(base); - out.push_str(separator); - out.push_str("u="); - out.push_str(&encoded_user_id); - } else { - out.push_str(base); - } - if let Some(frag) = fragment { - out.push('#'); - out.push_str(frag); - } - out.push_str(trailing); - cursor = m.end(); - } - out.push_str(&text[cursor..]); - out -} - -// ── RPC handlers ──────────────────────────────────────────────────────── - -pub async fn rl_shorten(config: &Config, url: &str) -> Result, String> { - let link = store::shorten(config, url).map_err(|e| e.to_string())?; - Ok(RpcOutcome::single_log( - link.clone(), - format!( - "[redirect_links][rpc][shorten] id={} short_url={} original_url_len={}", - link.id, - link.short_url, - link.url.len() - ), - )) -} - -pub async fn rl_expand(config: &Config, id: &str) -> Result, String> { - match store::expand(config, id).map_err(|e| e.to_string())? { - Some(link) => Ok(RpcOutcome::new( - serde_json::to_value(&link).map_err(|e| e.to_string())?, - vec![format!( - "[redirect_links][rpc][expand] id={} hit_count={}", - link.id, link.hit_count - )], - )), - None => Err(format!("[redirect_links][rpc][expand] not found: id={id}")), - } -} - -pub async fn rl_list(config: &Config, limit: Option) -> Result, String> { - let limit = limit.unwrap_or(50).clamp(1, 1_000); - let links = store::list(config, limit).map_err(|e| e.to_string())?; - Ok(RpcOutcome::new( - json!({ "links": links }), - vec![format!("[redirect_links][rpc][list] count={}", links.len())], - )) -} - -pub async fn rl_remove(config: &Config, id: &str) -> Result, String> { - let removed = store::remove(config, id).map_err(|e| e.to_string())?; - Ok(RpcOutcome::new( - json!({ "id": id, "removed": removed }), - vec![format!( - "[redirect_links][rpc][remove] id={id} removed={removed}" - )], - )) -} - -pub async fn rl_rewrite_inbound( - config: &Config, - text: &str, - min_len: Option, -) -> Result, String> { - let result = - rewrite_inbound_with_threshold(config, text, min_len.unwrap_or(DEFAULT_MIN_URL_LEN)) - .map_err(|e| e.to_string())?; - let count = result.replacements.len(); - Ok(RpcOutcome::single_log( - result, - format!("[redirect_links][rpc][rewrite_inbound] replaced={count}"), - )) -} - -pub async fn rl_rewrite_outbound( - config: &Config, - text: &str, -) -> Result, String> { - let result = rewrite_outbound(config, text).map_err(|e| e.to_string())?; - let count = result.replacements.len(); - Ok(RpcOutcome::single_log( - result, - format!("[redirect_links][rpc][rewrite_outbound] expanded={count}"), - )) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::openhuman::config::Config; - use tempfile::TempDir; - - fn test_config(tmp: &TempDir) -> Config { - let mut cfg = Config::default(); - cfg.workspace_dir = tmp.path().join("workspace"); - std::fs::create_dir_all(&cfg.workspace_dir).unwrap(); - cfg - } - - const LONG: &str = - "https://www.trip.com/forward/middlepages/channel/openEdm.gif?bizData=eyJldmVudCI6Im9wZW4iLCJmaWxlSWQiOiJmaWxlX2EwOD"; - - #[test] - fn inbound_shortens_long_urls_and_preserves_surrounding_text() { - let tmp = TempDir::new().unwrap(); - let cfg = test_config(&tmp); - let text = format!("click here: {LONG} thanks"); - let result = rewrite_inbound(&cfg, &text).unwrap(); - assert!(result.text.starts_with("click here: openhuman://link/")); - assert!(result.text.ends_with(" thanks")); - assert_eq!(result.replacements.len(), 1); - } - - #[test] - fn inbound_leaves_short_urls_untouched() { - let tmp = TempDir::new().unwrap(); - let cfg = test_config(&tmp); - let text = "see https://a.co/x for more"; - let result = rewrite_inbound(&cfg, text).unwrap(); - assert_eq!(result.text, text); - assert!(result.replacements.is_empty()); - } - - #[test] - fn inbound_trims_trailing_sentence_punctuation() { - let tmp = TempDir::new().unwrap(); - let cfg = test_config(&tmp); - let text = format!("open {LONG}."); - let result = rewrite_inbound(&cfg, &text).unwrap(); - assert!(result.text.ends_with(".")); - // The stored URL must not carry the trailing period. - let link = &result.replacements[0]; - assert!(!link.original.ends_with('.')); - } - - #[test] - fn outbound_expands_placeholders_roundtrip() { - let tmp = TempDir::new().unwrap(); - let cfg = test_config(&tmp); - let text = format!("go: {LONG}"); - let inbound = rewrite_inbound(&cfg, &text).unwrap(); - let outbound = rewrite_outbound(&cfg, &inbound.text).unwrap(); - assert_eq!(outbound.text, text); - } - - #[test] - fn outbound_leaves_unknown_ids_unchanged() { - let tmp = TempDir::new().unwrap(); - let cfg = test_config(&tmp); - let text = "no match: openhuman://link/ffffffff"; - let result = rewrite_outbound(&cfg, text).unwrap(); - assert_eq!(result.text, text); - assert!(result.replacements.is_empty()); - } - - #[test] - fn inbound_handles_multiple_urls_in_one_string() { - let tmp = TempDir::new().unwrap(); - let cfg = test_config(&tmp); - let text = format!("{LONG} and also {LONG}?extra=1234567890abcdef"); - let result = rewrite_inbound(&cfg, &text).unwrap(); - assert_eq!(result.replacements.len(), 2); - } - - #[test] - fn append_user_id_to_public_links_bare() { - let text = "https://openhm.xyz/abc"; - let got = append_user_id_to_public_links(text, Some("nikhil")); - assert_eq!(got, "https://openhm.xyz/abc?u=nikhil"); - } - - #[test] - fn append_user_id_to_public_links_query() { - let text = "https://openhm.xyz/abc?foo=bar"; - let got = append_user_id_to_public_links(text, Some("nikhil")); - assert_eq!(got, "https://openhm.xyz/abc?foo=bar&u=nikhil"); - } - - #[test] - fn append_user_id_to_public_links_idempotent() { - // Already ?u= - let text = "https://openhm.xyz/abc?u=existing"; - let got = append_user_id_to_public_links(text, Some("nikhil")); - assert_eq!(got, text); - - // Already &u= - let text = "https://openhm.xyz/abc?foo=bar&u=existing"; - let got = append_user_id_to_public_links(text, Some("nikhil")); - assert_eq!(got, text); - } - - #[test] - fn append_user_id_to_public_links_none() { - let text = "https://openhm.xyz/abc"; - let got = append_user_id_to_public_links(text, None); - assert_eq!(got, text); - } - - #[test] - fn append_user_id_to_public_links_no_match() { - let text = "https://example.com/abc"; - let got = append_user_id_to_public_links(text, Some("nikhil")); - assert_eq!(got, text); - } - - #[test] - fn append_user_id_to_public_links_multiple() { - let text = "https://openhm.xyz/a and https://openhm.xyz/b?x=y"; - let got = append_user_id_to_public_links(text, Some("nikhil")); - assert_eq!( - got, - "https://openhm.xyz/a?u=nikhil and https://openhm.xyz/b?x=y&u=nikhil" - ); - } - - #[test] - fn append_user_id_to_public_links_encoding() { - let text = "https://openhm.xyz/abc"; - let got = append_user_id_to_public_links(text, Some("nikhil@example.com + space")); - assert_eq!( - got, - "https://openhm.xyz/abc?u=nikhil%40example.com%20%2B%20space" - ); - } - - #[test] - fn append_user_id_to_public_links_lookalikes() { - let text = "https://evil-openhm.xyz/abc and openhm.xyz.evil.com/abc"; - let got = append_user_id_to_public_links(text, Some("nikhil")); - assert_eq!(got, text); - } - - #[test] - fn append_user_id_to_public_links_punctuation() { - let text = "Click https://openhm.xyz/abc."; - let got = append_user_id_to_public_links(text, Some("nikhil")); - assert_eq!(got, "Click https://openhm.xyz/abc?u=nikhil."); - } - - #[test] - fn append_user_id_to_public_links_query_with_fragment() { - let text = "https://openhm.xyz/abc?foo=bar#frag"; - let got = append_user_id_to_public_links(text, Some("nikhil")); - assert_eq!(got, "https://openhm.xyz/abc?foo=bar&u=nikhil#frag"); - } - - #[test] - fn append_user_id_to_public_links_bare_with_fragment() { - let text = "https://openhm.xyz/abc#frag"; - let got = append_user_id_to_public_links(text, Some("nikhil")); - assert_eq!(got, "https://openhm.xyz/abc?u=nikhil#frag"); - } - - #[test] - fn rewrite_outbound_for_user_expands_placeholder_and_tags_public_url() { - let tmp = TempDir::new().unwrap(); - let cfg = test_config(&tmp); - - // Shorten LONG into an openhuman:// placeholder, then craft outbound text - // that mixes the placeholder with a public openhm.xyz URL. - let inbound = rewrite_inbound(&cfg, LONG).unwrap(); - let placeholder = &inbound.replacements[0].replacement; - let text = format!("see {placeholder} and https://openhm.xyz/abc"); - - let result = rewrite_outbound_for_user(&cfg, &text, Some("nikhil")).unwrap(); - assert!( - result.text.contains(LONG), - "placeholder must expand back to LONG" - ); - assert!( - result.text.contains("https://openhm.xyz/abc?u=nikhil"), - "openhm.xyz URL must carry ?u= tag" - ); - - // None user_id leaves openhm.xyz untouched but still expands placeholder. - let result_none = rewrite_outbound_for_user(&cfg, &text, None).unwrap(); - assert!(result_none.text.contains(LONG)); - assert!(result_none.text.contains("https://openhm.xyz/abc")); - assert!(!result_none.text.contains("?u=")); - } -} diff --git a/src/openhuman/redirect_links/schemas.rs b/src/openhuman/redirect_links/schemas.rs deleted file mode 100644 index 8e94adfe2..000000000 --- a/src/openhuman/redirect_links/schemas.rs +++ /dev/null @@ -1,315 +0,0 @@ -use serde::de::DeserializeOwned; -use serde_json::{Map, Value}; - -use crate::core::all::{ControllerFuture, RegisteredController}; -use crate::core::{ControllerSchema, FieldSchema, TypeSchema}; -use crate::openhuman::config::rpc as config_rpc; -use crate::openhuman::redirect_links::ops as rl_ops; -use crate::rpc::RpcOutcome; - -pub fn all_controller_schemas() -> Vec { - vec![ - schemas("shorten"), - schemas("expand"), - schemas("list"), - schemas("remove"), - schemas("rewrite_inbound"), - schemas("rewrite_outbound"), - ] -} - -pub fn all_registered_controllers() -> Vec { - vec![ - RegisteredController { - schema: schemas("shorten"), - handler: handle_shorten, - }, - RegisteredController { - schema: schemas("expand"), - handler: handle_expand, - }, - RegisteredController { - schema: schemas("list"), - handler: handle_list, - }, - RegisteredController { - schema: schemas("remove"), - handler: handle_remove, - }, - RegisteredController { - schema: schemas("rewrite_inbound"), - handler: handle_rewrite_inbound, - }, - RegisteredController { - schema: schemas("rewrite_outbound"), - handler: handle_rewrite_outbound, - }, - ] -} - -pub fn schemas(function: &str) -> ControllerSchema { - match function { - "shorten" => ControllerSchema { - namespace: "redirect_links", - function: "shorten", - description: "Persist a long URL and return its `openhuman://link/` short form.", - inputs: vec![FieldSchema { - name: "url", - ty: TypeSchema::String, - comment: "The full URL to shorten.", - required: true, - }], - outputs: vec![FieldSchema { - name: "link", - ty: TypeSchema::Ref("RedirectLink"), - comment: "The stored redirect link record.", - required: true, - }], - }, - "expand" => ControllerSchema { - namespace: "redirect_links", - function: "expand", - description: "Resolve a short id back to its full URL and bump hit count.", - inputs: vec![FieldSchema { - name: "id", - ty: TypeSchema::String, - comment: "The short id (the hex portion after `openhuman://link/`).", - required: true, - }], - outputs: vec![FieldSchema { - name: "link", - ty: TypeSchema::Ref("RedirectLink"), - comment: "The resolved redirect link record.", - required: true, - }], - }, - "list" => ControllerSchema { - namespace: "redirect_links", - function: "list", - description: "List stored redirect links, newest first.", - inputs: vec![FieldSchema { - name: "limit", - ty: TypeSchema::Option(Box::new(TypeSchema::U64)), - comment: "Maximum number of links to return (default 50, max 1000).", - required: false, - }], - outputs: vec![FieldSchema { - name: "links", - ty: TypeSchema::Array(Box::new(TypeSchema::Ref("RedirectLink"))), - comment: "Stored redirect links.", - required: true, - }], - }, - "remove" => ControllerSchema { - namespace: "redirect_links", - function: "remove", - description: "Delete a redirect link by id.", - inputs: vec![FieldSchema { - name: "id", - ty: TypeSchema::String, - comment: "Redirect link id to remove.", - required: true, - }], - outputs: vec![FieldSchema { - name: "result", - ty: TypeSchema::Object { - fields: vec![ - FieldSchema { - name: "id", - ty: TypeSchema::String, - comment: "Id requested for removal.", - required: true, - }, - FieldSchema { - name: "removed", - ty: TypeSchema::Bool, - comment: "True when a row was deleted.", - required: true, - }, - ], - }, - comment: "Removal result.", - required: true, - }], - }, - "rewrite_inbound" => ControllerSchema { - namespace: "redirect_links", - function: "rewrite_inbound", - description: - "Rewrite every long URL in `text` to an `openhuman://link/` placeholder \ - to save tokens before a prompt hits the model. URLs shorter than `min_len` \ - are left untouched.", - inputs: vec![ - FieldSchema { - name: "text", - ty: TypeSchema::String, - comment: "Text to rewrite.", - required: true, - }, - FieldSchema { - name: "min_len", - ty: TypeSchema::Option(Box::new(TypeSchema::U64)), - comment: "Minimum URL length to shorten; defaults to 80.", - required: false, - }, - ], - outputs: vec![FieldSchema { - name: "result", - ty: TypeSchema::Ref("RewriteResult"), - comment: "Rewritten text and per-URL replacement records.", - required: true, - }], - }, - "rewrite_outbound" => ControllerSchema { - namespace: "redirect_links", - function: "rewrite_outbound", - description: - "Expand every `openhuman://link/` placeholder in `text` back to its full \ - URL before the message reaches the user.", - inputs: vec![FieldSchema { - name: "text", - ty: TypeSchema::String, - comment: "Text to rewrite.", - required: true, - }], - outputs: vec![FieldSchema { - name: "result", - ty: TypeSchema::Ref("RewriteResult"), - comment: "Rewritten text and per-placeholder expansion records.", - required: true, - }], - }, - _other => ControllerSchema { - namespace: "redirect_links", - function: "unknown", - description: "Unknown redirect_links controller function.", - inputs: vec![FieldSchema { - name: "function", - ty: TypeSchema::String, - comment: "Unknown function requested for schema lookup.", - required: true, - }], - outputs: vec![FieldSchema { - name: "error", - ty: TypeSchema::String, - comment: "Lookup error details.", - required: true, - }], - }, - } -} - -fn handle_shorten(params: Map) -> ControllerFuture { - Box::pin(async move { - let config = config_rpc::load_config_with_timeout().await?; - let url = read_required::(¶ms, "url")?; - to_json(rl_ops::rl_shorten(&config, url.trim()).await?) - }) -} - -fn handle_expand(params: Map) -> ControllerFuture { - Box::pin(async move { - let config = config_rpc::load_config_with_timeout().await?; - let id = read_required::(¶ms, "id")?; - to_json(rl_ops::rl_expand(&config, id.trim()).await?) - }) -} - -fn handle_list(params: Map) -> ControllerFuture { - Box::pin(async move { - let config = config_rpc::load_config_with_timeout().await?; - let limit = read_optional_u64(¶ms, "limit")? - .map(|raw| usize::try_from(raw).map_err(|_| "limit is too large for usize".to_string())) - .transpose()?; - to_json(rl_ops::rl_list(&config, limit).await?) - }) -} - -fn handle_remove(params: Map) -> ControllerFuture { - Box::pin(async move { - let config = config_rpc::load_config_with_timeout().await?; - let id = read_required::(¶ms, "id")?; - to_json(rl_ops::rl_remove(&config, id.trim()).await?) - }) -} - -fn handle_rewrite_inbound(params: Map) -> ControllerFuture { - Box::pin(async move { - let config = config_rpc::load_config_with_timeout().await?; - let text = read_required::(¶ms, "text")?; - let min_len = read_optional_u64(¶ms, "min_len")? - .map(|raw| usize::try_from(raw).map_err(|_| "min_len too large for usize".to_string())) - .transpose()?; - to_json(rl_ops::rl_rewrite_inbound(&config, &text, min_len).await?) - }) -} - -fn handle_rewrite_outbound(params: Map) -> ControllerFuture { - Box::pin(async move { - let config = config_rpc::load_config_with_timeout().await?; - let text = read_required::(¶ms, "text")?; - to_json(rl_ops::rl_rewrite_outbound(&config, &text).await?) - }) -} - -fn read_required(params: &Map, key: &str) -> Result { - let value = params - .get(key) - .cloned() - .ok_or_else(|| format!("missing required param '{key}'"))?; - serde_json::from_value(value).map_err(|e| format!("invalid '{key}': {e}")) -} - -fn read_optional_u64(params: &Map, key: &str) -> Result, String> { - match params.get(key) { - None => Ok(None), - Some(Value::Null) => Ok(None), - Some(Value::Number(n)) => n - .as_u64() - .map(Some) - .ok_or_else(|| format!("invalid '{key}': expected unsigned integer")), - Some(_) => Err(format!("invalid '{key}': expected unsigned integer")), - } -} - -fn to_json(outcome: RpcOutcome) -> Result { - outcome.into_cli_compatible_json() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn all_schemas_and_controllers_cover_every_function() { - let names: Vec<_> = all_controller_schemas() - .into_iter() - .map(|s| s.function) - .collect(); - assert_eq!( - names, - vec![ - "shorten", - "expand", - "list", - "remove", - "rewrite_inbound", - "rewrite_outbound", - ], - ); - assert_eq!(all_registered_controllers().len(), 6); - } - - #[test] - fn schemas_unknown_returns_placeholder() { - let s = schemas("does-not-exist"); - assert_eq!(s.function, "unknown"); - } - - #[test] - fn shorten_schema_requires_url() { - let s = schemas("shorten"); - assert_eq!(s.inputs.len(), 1); - assert!(s.inputs[0].required); - } -} diff --git a/src/openhuman/redirect_links/store.rs b/src/openhuman/redirect_links/store.rs deleted file mode 100644 index a88c78d55..000000000 --- a/src/openhuman/redirect_links/store.rs +++ /dev/null @@ -1,337 +0,0 @@ -use anyhow::{Context, Result}; -use chrono::{DateTime, Utc}; -use rusqlite::{params, Connection, OptionalExtension}; -use sha2::{Digest, Sha256}; - -use crate::openhuman::config::Config; -use crate::openhuman::redirect_links::types::RedirectLink; - -pub const SHORT_URL_PREFIX: &str = "openhuman://link/"; -const DEFAULT_ID_LEN: usize = 8; -const MAX_ID_LEN: usize = 32; - -/// Build the short URL representation for an id. -pub fn short_url_for(id: &str) -> String { - format!("{SHORT_URL_PREFIX}{id}") -} - -/// Parse a short URL back into its id component. Accepts both -/// `openhuman://link/` and bare `` (hex only). -pub fn id_from_short(short: &str) -> Option { - let trimmed = short.trim(); - let candidate = trimmed.strip_prefix(SHORT_URL_PREFIX).unwrap_or(trimmed); - if !candidate.is_empty() && candidate.chars().all(|c| c.is_ascii_hexdigit()) { - Some(candidate.to_ascii_lowercase()) - } else { - None - } -} - -fn content_id(url: &str, len: usize) -> String { - let digest = Sha256::digest(url.as_bytes()); - hex::encode(digest)[..len.min(64)].to_string() -} - -pub fn shorten(config: &Config, url: &str) -> Result { - let url = url.trim(); - if url.is_empty() { - anyhow::bail!("url must not be empty"); - } - - with_connection(config, |conn| { - let mut len = DEFAULT_ID_LEN; - let now = Utc::now(); - loop { - if len > MAX_ID_LEN { - anyhow::bail!("failed to allocate unique redirect id after expansion"); - } - let id = content_id(url, len); - - // Atomic insert. If either `id` or `url` already exists, the - // statement becomes a no-op — no PRIMARY KEY / UNIQUE error under - // concurrent calls, so we don't need a pre-read. - let affected = conn - .execute( - "INSERT INTO redirect_links - (id, url, created_at, last_used_at, hit_count) - VALUES (?1, ?2, ?3, NULL, 0) - ON CONFLICT DO NOTHING", - params![id, url, now.to_rfc3339()], - ) - .context("failed to insert redirect_link")?; - - if affected > 0 { - return Ok(RedirectLink { - id: id.clone(), - url: url.to_string(), - short_url: short_url_for(&id), - created_at: now, - last_used_at: None, - hit_count: 0, - }); - } - - // Insert was a no-op. Either the URL is already stored (possibly - // under a longer id from a concurrent writer — idempotent return) - // or this id prefix collides with a different URL. - if let Some(existing) = find_by_url(conn, url)? { - return Ok(existing); - } - match get_by_id(conn, &id)? { - Some(existing) if existing.url == url => return Ok(existing), - Some(_) => { - // Hash-prefix collision with a different URL — lengthen. - len += 2; - continue; - } - None => { - // Race with a concurrent delete; retry this same length. - continue; - } - } - } - }) -} - -pub fn expand(config: &Config, id: &str) -> Result> { - let id = id.trim(); - if id.is_empty() { - return Ok(None); - } - with_connection(config, |conn| { - let found = get_by_id(conn, id)?; - if found.is_some() { - let now = Utc::now().to_rfc3339(); - conn.execute( - "UPDATE redirect_links - SET hit_count = hit_count + 1, last_used_at = ?2 - WHERE id = ?1", - params![id, now], - ) - .context("failed to bump redirect_link hit count")?; - } - Ok(found) - }) -} - -pub fn peek(config: &Config, id: &str) -> Result> { - let id = id.trim(); - if id.is_empty() { - return Ok(None); - } - with_connection(config, |conn| get_by_id(conn, id)) -} - -pub fn list(config: &Config, limit: usize) -> Result> { - with_connection(config, |conn| { - let mut stmt = conn.prepare( - "SELECT id, url, created_at, last_used_at, hit_count - FROM redirect_links - ORDER BY datetime(created_at) DESC - LIMIT ?1", - )?; - let rows = stmt - .query_map(params![limit as i64], row_to_link)? - .collect::, _>>()?; - Ok(rows) - }) -} - -pub fn remove(config: &Config, id: &str) -> Result { - with_connection(config, |conn| { - let affected = conn - .execute("DELETE FROM redirect_links WHERE id = ?1", params![id]) - .context("failed to delete redirect_link")?; - Ok(affected > 0) - }) -} - -fn get_by_id(conn: &Connection, id: &str) -> Result> { - conn.query_row( - "SELECT id, url, created_at, last_used_at, hit_count - FROM redirect_links WHERE id = ?1", - params![id], - row_to_link, - ) - .optional() - .map_err(Into::into) -} - -fn find_by_url(conn: &Connection, url: &str) -> Result> { - conn.query_row( - "SELECT id, url, created_at, last_used_at, hit_count - FROM redirect_links WHERE url = ?1", - params![url], - row_to_link, - ) - .optional() - .map_err(Into::into) -} - -fn row_to_link(row: &rusqlite::Row<'_>) -> rusqlite::Result { - let id: String = row.get(0)?; - let url: String = row.get(1)?; - let created_at: String = row.get(2)?; - let last_used_at: Option = row.get(3)?; - let hit_count: i64 = row.get(4)?; - let created_at = parse_ts(&created_at)?; - let last_used_at = last_used_at.as_deref().map(parse_ts).transpose()?; - Ok(RedirectLink { - short_url: short_url_for(&id), - id, - url, - created_at, - last_used_at, - hit_count: hit_count.max(0) as u64, - }) -} - -fn parse_ts(s: &str) -> rusqlite::Result> { - DateTime::parse_from_rfc3339(s) - .map(|t| t.with_timezone(&Utc)) - .map_err(|e| { - rusqlite::Error::FromSqlConversionFailure(0, rusqlite::types::Type::Text, Box::new(e)) - }) -} - -fn with_connection(config: &Config, f: impl FnOnce(&Connection) -> Result) -> Result { - let db_path = config.workspace_dir.join("redirect_links").join("links.db"); - if let Some(parent) = db_path.parent() { - std::fs::create_dir_all(parent).with_context(|| { - format!( - "Failed to create redirect_links directory: {}", - parent.display() - ) - })?; - } - - let conn = Connection::open(&db_path) - .with_context(|| format!("Failed to open redirect_links DB: {}", db_path.display()))?; - - conn.execute_batch( - "PRAGMA foreign_keys = ON; - CREATE TABLE IF NOT EXISTS redirect_links ( - id TEXT PRIMARY KEY, - url TEXT NOT NULL UNIQUE, - created_at TEXT NOT NULL, - last_used_at TEXT, - hit_count INTEGER NOT NULL DEFAULT 0 - ); - CREATE INDEX IF NOT EXISTS idx_redirect_links_url ON redirect_links(url);", - ) - .context("Failed to initialize redirect_links schema")?; - - f(&conn) -} - -#[cfg(test)] -mod tests { - use super::*; - use tempfile::TempDir; - - fn test_config(tmp: &TempDir) -> Config { - let mut cfg = Config::default(); - cfg.workspace_dir = tmp.path().join("workspace"); - std::fs::create_dir_all(&cfg.workspace_dir).unwrap(); - cfg - } - - #[test] - fn shorten_is_deterministic_and_dedupes() { - let tmp = TempDir::new().unwrap(); - let cfg = test_config(&tmp); - let url = "https://www.trip.com/forward/middlepages/channel/openEdm.gif?bizData=eyJldmVudCI6Im9wZW4ifQ"; - let a = shorten(&cfg, url).unwrap(); - let b = shorten(&cfg, url).unwrap(); - assert_eq!(a.id, b.id); - assert_eq!(a.short_url, format!("openhuman://link/{}", a.id)); - assert_eq!(a.id.len(), DEFAULT_ID_LEN); - } - - #[test] - fn expand_returns_original_url_and_bumps_hits() { - let tmp = TempDir::new().unwrap(); - let cfg = test_config(&tmp); - let link = shorten(&cfg, "https://example.com/a?x=1").unwrap(); - let got = expand(&cfg, &link.id).unwrap().expect("link exists"); - assert_eq!(got.url, "https://example.com/a?x=1"); - assert_eq!(got.hit_count, 0); - let got2 = expand(&cfg, &link.id).unwrap().unwrap(); - assert_eq!(got2.hit_count, 1); - } - - #[test] - fn expand_unknown_id_returns_none() { - let tmp = TempDir::new().unwrap(); - let cfg = test_config(&tmp); - assert!(expand(&cfg, "deadbeef").unwrap().is_none()); - } - - #[test] - fn id_from_short_accepts_scheme_and_rejects_others() { - assert_eq!( - id_from_short("openhuman://link/abc123"), - Some("abc123".into()) - ); - assert!(id_from_short("https://example.com/").is_none()); - assert!(id_from_short("openhuman://link/").is_none()); - assert!(id_from_short("openhuman://link/not-hex!").is_none()); - } - - #[test] - fn id_from_short_accepts_bare_id_and_normalizes_case() { - // The docstring promises bare-id acceptance — lock it in. - assert_eq!(id_from_short("abc123").as_deref(), Some("abc123")); - assert_eq!(id_from_short(" ABC123 ").as_deref(), Some("abc123")); - assert!(id_from_short("").is_none()); - assert!(id_from_short("not-hex").is_none()); - } - - #[test] - fn shorten_handles_concurrent_calls_without_primary_key_error() { - // Regression test: the previous check-then-insert path raced under - // concurrent calls and hit a PRIMARY KEY constraint error. The - // ON CONFLICT DO NOTHING path must return the same link for every - // concurrent caller with the same URL. - use std::sync::Arc; - use std::thread; - - let tmp = TempDir::new().unwrap(); - let cfg = Arc::new(test_config(&tmp)); - let url = "https://example.com/concurrent?x=1".to_string(); - - let mut handles = Vec::new(); - for _ in 0..8 { - let cfg = Arc::clone(&cfg); - let url = url.clone(); - handles.push(thread::spawn(move || shorten(&cfg, &url).unwrap())); - } - let ids: Vec = handles.into_iter().map(|h| h.join().unwrap().id).collect(); - // Every concurrent writer must agree on a single id for the URL. - assert!(ids.iter().all(|id| id == &ids[0])); - } - - #[test] - fn list_orders_newest_first_and_respects_limit() { - let tmp = TempDir::new().unwrap(); - let cfg = test_config(&tmp); - for i in 0..5 { - shorten( - &cfg, - &format!("https://example.com/{i}?v=xxxxxxxxxxxxxxxxxxxx"), - ) - .unwrap(); - } - let rows = list(&cfg, 3).unwrap(); - assert_eq!(rows.len(), 3); - } - - #[test] - fn remove_deletes_and_reports_affected() { - let tmp = TempDir::new().unwrap(); - let cfg = test_config(&tmp); - let link = shorten(&cfg, "https://example.com/rm").unwrap(); - assert!(remove(&cfg, &link.id).unwrap()); - assert!(!remove(&cfg, &link.id).unwrap()); - } -} diff --git a/src/openhuman/redirect_links/types.rs b/src/openhuman/redirect_links/types.rs deleted file mode 100644 index 6d631a767..000000000 --- a/src/openhuman/redirect_links/types.rs +++ /dev/null @@ -1,25 +0,0 @@ -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct RedirectLink { - pub id: String, - pub url: String, - pub short_url: String, - pub created_at: DateTime, - pub last_used_at: Option>, - pub hit_count: u64, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct RewriteReplacement { - pub original: String, - pub replacement: String, - pub id: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct RewriteResult { - pub text: String, - pub replacements: Vec, -} diff --git a/src/openhuman/security/policy/types.rs b/src/openhuman/security/policy/types.rs index e6f02ab7a..317eb3bb7 100644 --- a/src/openhuman/security/policy/types.rs +++ b/src/openhuman/security/policy/types.rs @@ -184,6 +184,10 @@ pub(super) const WORKSPACE_INTERNAL_DIRS: &[&str] = &[ "vault", "task_sources", "whatsapp_data", + // The redirect_links domain was removed (#5051), but an upgraded profile can + // still hold a legacy `redirect_links/links.db` (stored URL history) written + // by an older version. Keep the directory on the internal denylist so agents + // with workspace access cannot read or overwrite that leftover state. "redirect_links", "codegraph", ".openhuman",