chore(core): remove zero-reference deps and redirect_links domain (#5052)

This commit is contained in:
YellowSnnowmann
2026-07-21 19:01:31 +03:00
committed by GitHub
parent c09bef22db
commit 4efd80db34
17 changed files with 40 additions and 1968 deletions
Generated
+18 -303
View File
@@ -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",
+4 -29
View File
@@ -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"]
-3
View File
@@ -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",
+14 -310
View File
@@ -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",
-1
View File
@@ -73,7 +73,6 @@
"ws": "^8.20.0"
},
"dependencies": {
"@rive-app/react-canvas": "^4.28.6",
"@tauri-apps/api": "2.10.1"
}
}
-63
View File
@@ -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: {}
-1
View File
@@ -68,7 +68,6 @@ const DOMAIN_ALLOWLIST = new Set([
'plan_review',
'provider_surfaces',
'recall_calendar',
'redirect_links',
'referral',
'session_import',
'skill_runtime',
-9
View File
@@ -575,12 +575,6 @@ fn build_registered_controllers() -> Vec<GroupedController> {
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/<id>` 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.",
-1
View File
@@ -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());
-1
View File
@@ -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;
-82
View File
@@ -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/<id>` 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=<user_id>` attribution param on the way out.
## Responsibilities
- **Shorten** a long URL to a content-addressed `openhuman://link/<id>` 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/<id>` placeholder in text back to the stored URL; unknown ids are left untouched (nothing silently disappears).
- **Public-link attribution**: append `?u=<user_id>` (URL-encoded, idempotent, fragment-safe) to `openhm.xyz/<id>` 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>`):
| 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<T>` 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/<id>` and a bare hex `<id>`, 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.
-24
View File
@@ -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/<id>` 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};
-464
View File
@@ -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/<id>`
/// 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<Regex> = 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<Regex> = OnceLock::new();
RE.get_or_init(|| Regex::new(r"openhuman://link/([0-9a-f]+)").unwrap())
}
fn public_url_regex() -> &'static Regex {
static RE: OnceLock<Regex> = 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<RedirectLink> {
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<Option<RedirectLink>> {
store::expand(config, id)
}
/// Rewrite every long URL in `text` to `openhuman://link/<id>`, using the
/// default length threshold.
pub fn rewrite_inbound(config: &Config, text: &str) -> Result<RewriteResult> {
rewrite_inbound_with_threshold(config, text, DEFAULT_MIN_URL_LEN)
}
pub fn rewrite_inbound_with_threshold(
config: &Config,
text: &str,
min_len: usize,
) -> Result<RewriteResult> {
let re = url_regex();
let mut replacements: Vec<RewriteReplacement> = 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/<id>` placeholder with its stored URL.
/// Unknown ids are left as-is so nothing silently disappears.
pub fn rewrite_outbound(config: &Config, text: &str) -> Result<RewriteResult> {
let re = short_url_regex();
let mut replacements: Vec<RewriteReplacement> = 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<RewriteResult> {
let mut result = rewrite_outbound(config, text)?;
result.text = append_user_id_to_public_links(&result.text, user_id);
Ok(result)
}
/// Append `?u=<user_id>` to every `openhm.xyz/<id>` 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<RpcOutcome<RedirectLink>, 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<RpcOutcome<Value>, 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<usize>) -> Result<RpcOutcome<Value>, 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<RpcOutcome<Value>, 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<usize>,
) -> Result<RpcOutcome<RewriteResult>, 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<RpcOutcome<RewriteResult>, 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="));
}
}
-315
View File
@@ -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<ControllerSchema> {
vec![
schemas("shorten"),
schemas("expand"),
schemas("list"),
schemas("remove"),
schemas("rewrite_inbound"),
schemas("rewrite_outbound"),
]
}
pub fn all_registered_controllers() -> Vec<RegisteredController> {
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/<id>` 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/<id>` 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/<id>` 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<String, Value>) -> ControllerFuture {
Box::pin(async move {
let config = config_rpc::load_config_with_timeout().await?;
let url = read_required::<String>(&params, "url")?;
to_json(rl_ops::rl_shorten(&config, url.trim()).await?)
})
}
fn handle_expand(params: Map<String, Value>) -> ControllerFuture {
Box::pin(async move {
let config = config_rpc::load_config_with_timeout().await?;
let id = read_required::<String>(&params, "id")?;
to_json(rl_ops::rl_expand(&config, id.trim()).await?)
})
}
fn handle_list(params: Map<String, Value>) -> ControllerFuture {
Box::pin(async move {
let config = config_rpc::load_config_with_timeout().await?;
let limit = read_optional_u64(&params, "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<String, Value>) -> ControllerFuture {
Box::pin(async move {
let config = config_rpc::load_config_with_timeout().await?;
let id = read_required::<String>(&params, "id")?;
to_json(rl_ops::rl_remove(&config, id.trim()).await?)
})
}
fn handle_rewrite_inbound(params: Map<String, Value>) -> ControllerFuture {
Box::pin(async move {
let config = config_rpc::load_config_with_timeout().await?;
let text = read_required::<String>(&params, "text")?;
let min_len = read_optional_u64(&params, "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<String, Value>) -> ControllerFuture {
Box::pin(async move {
let config = config_rpc::load_config_with_timeout().await?;
let text = read_required::<String>(&params, "text")?;
to_json(rl_ops::rl_rewrite_outbound(&config, &text).await?)
})
}
fn read_required<T: DeserializeOwned>(params: &Map<String, Value>, key: &str) -> Result<T, String> {
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<String, Value>, key: &str) -> Result<Option<u64>, 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<T: serde::Serialize>(outcome: RpcOutcome<T>) -> Result<Value, String> {
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);
}
}
-337
View File
@@ -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/<id>` and bare `<id>` (hex only).
pub fn id_from_short(short: &str) -> Option<String> {
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<RedirectLink> {
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<Option<RedirectLink>> {
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<Option<RedirectLink>> {
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<Vec<RedirectLink>> {
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::<std::result::Result<Vec<_>, _>>()?;
Ok(rows)
})
}
pub fn remove(config: &Config, id: &str) -> Result<bool> {
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<Option<RedirectLink>> {
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<Option<RedirectLink>> {
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<RedirectLink> {
let id: String = row.get(0)?;
let url: String = row.get(1)?;
let created_at: String = row.get(2)?;
let last_used_at: Option<String> = 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<Utc>> {
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<T>(config: &Config, f: impl FnOnce(&Connection) -> Result<T>) -> Result<T> {
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<String> = 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());
}
}
-25
View File
@@ -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<Utc>,
pub last_used_at: Option<DateTime<Utc>>,
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<RewriteReplacement>,
}
+4
View File
@@ -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",