Initial scaffold: GyanDoot v2.0 in Rust

Full Cargo workspace with 11 crates:
- gyandoot-core: Shared types, config, error types
- gyandoot-agent: Agent loop, tool execution, context compression
- gyandoot-providers: LLM provider abstraction (OpenAI, Anthropic, Ollama)
- gyandoot-memory: MemPalace persistent memory system
- gyandoot-platform: Platform adapters (Telegram, Discord, Webhook, CLI)
- gyandoot-security: Credential vault, sanitizer, threat detector
- gyandoot-kanban: Task decomposition, swarm orchestration, diagnostics
- gyandoot-subagents: Sub-agent spawning and orchestration
- gyandoot-skills: Skills discovery, loading, preprocessing, bundles
- gyandoot-profile: Multi-agent profile manager
- gyandoot-gateway: Gateway sessions, routing, access control

Plus: CLI entry point with clap, cross-platform installer script,
KANBAN.md, .gnpl/AGENTS.md

Workspace compiles clean: cargo check passes with 0 errors.
This commit is contained in:
2026-06-11 20:04:12 +05:30
commit 851c838ca5
53 changed files with 8037 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
[package]
name = "gyandoot-gateway"
version.workspace = true
edition.workspace = true
license.workspace = true
description = "GyanDoot v2 — Gateway for platform sessions, routing, streaming"
[dependencies]
gyandoot-core = { path = "../gyandoot-core" }
gyandoot-memory = { path = "../gyandoot-memory" }
gyandoot-providers = { path = "../gyandoot-providers" }
tokio.workspace = true
serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
anyhow.workspace = true
tracing.workspace = true
uuid.workspace = true
chrono.workspace = true
async-trait.workspace = true
reqwest = { workspace = true, features = ["json"] }
futures.workspace = true

View File

@@ -0,0 +1,220 @@
//! # Gyandoot Gateway
//!
//! Gateway for platform sessions, message routing, and streaming delivery.
//! Connects platform adapters (Telegram, Discord, Webhook) to the agent core.
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;
use uuid::Uuid;
use chrono::Utc;
use serde::{Deserialize, Serialize};
use tracing::info;
/// Unique session identifier.
pub type SessionId = Uuid;
/// Source of a session (which platform + chat).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionSource {
pub platform: String,
pub chat_id: String,
pub thread_id: Option<String>,
}
/// A gateway session with lifecycle tracking.
#[derive(Debug, Clone)]
pub struct GatewaySession {
pub id: SessionId,
pub source: SessionSource,
pub created_at: chrono::DateTime<Utc>,
pub last_active: chrono::DateTime<Utc>,
pub message_count: u64,
pub active: bool,
}
impl GatewaySession {
pub fn new(source: SessionSource) -> Self {
Self {
id: Uuid::new_v4(),
source,
created_at: Utc::now(),
last_active: Utc::now(),
message_count: 0,
active: true,
}
}
}
/// Delivery target specification.
#[derive(Debug, Clone)]
pub struct DeliveryTarget {
pub platform: String,
pub chat_id: String,
pub thread_id: Option<String>,
}
/// Parses a target string like "telegram:-1001234567890:17585".
pub fn parse_delivery_target(target: &str) -> Option<DeliveryTarget> {
let parts: Vec<&str> = target.splitn(3, ':').collect();
match parts.len() {
1 => Some(DeliveryTarget {
platform: parts[0].to_string(),
chat_id: String::new(),
thread_id: None,
}),
2 => Some(DeliveryTarget {
platform: parts[0].to_string(),
chat_id: parts[1].to_string(),
thread_id: None,
}),
3 => Some(DeliveryTarget {
platform: parts[0].to_string(),
chat_id: parts[1].to_string(),
thread_id: Some(parts[2].to_string()),
}),
_ => None,
}
}
/// Three-gate access control for incoming messages.
#[derive(Debug, Clone)]
pub struct AccessControl {
pub block_threats: bool,
pub allowed_users: Option<Vec<String>>,
pub allowed_channels: Option<Vec<String>>,
}
impl AccessControl {
pub fn new() -> Self {
Self {
block_threats: true,
allowed_users: None,
allowed_channels: None,
}
}
pub fn check_user(&self, username: &str) -> bool {
match &self.allowed_users {
Some(users) => users.iter().any(|u| u.eq_ignore_ascii_case(username)),
None => true, // empty = allow all
}
}
pub fn check_channel(&self, channel: &str) -> bool {
match &self.allowed_channels {
Some(channels) => channels.iter().any(|c| c == channel),
None => true,
}
}
}
impl Default for AccessControl {
fn default() -> Self {
Self::new()
}
}
/// The main gateway runner — orchestrates platform connections and message flow.
pub struct GatewayRunner {
pub sessions: Arc<Mutex<HashMap<SessionId, GatewaySession>>>,
pub access_control: AccessControl,
running: Arc<Mutex<bool>>,
}
impl GatewayRunner {
pub fn new() -> Self {
Self {
sessions: Arc::new(Mutex::new(HashMap::new())),
access_control: AccessControl::new(),
running: Arc::new(Mutex::new(false)),
}
}
pub fn with_access_control(mut self, ac: AccessControl) -> Self {
self.access_control = ac;
self
}
pub async fn start(&self) -> anyhow::Result<()> {
let mut running = self.running.lock().await;
*running = true;
info!("Gateway started");
Ok(())
}
pub async fn stop(&self) {
let mut running = self.running.lock().await;
*running = false;
info!("Gateway stopped");
}
pub async fn is_running(&self) -> bool {
*self.running.lock().await
}
pub async fn create_session(&self, source: SessionSource) -> SessionId {
let session = GatewaySession::new(source);
let id = session.id;
let mut sessions = self.sessions.lock().await;
sessions.insert(id, session);
id
}
pub async fn get_session(&self, id: &SessionId) -> Option<GatewaySession> {
let sessions = self.sessions.lock().await;
sessions.get(id).cloned()
}
}
impl Default for GatewayRunner {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_session_creation() {
let source = SessionSource {
platform: "telegram".into(),
chat_id: "-10012345".into(),
thread_id: None,
};
let session = GatewaySession::new(source);
assert!(session.active);
assert_eq!(session.message_count, 0);
}
#[test]
fn test_delivery_target_parsing() {
let target = parse_delivery_target("telegram:-100123:17585").unwrap();
assert_eq!(target.platform, "telegram");
assert_eq!(target.chat_id, "-100123");
assert_eq!(target.thread_id, Some("17585".into()));
}
#[test]
fn test_access_control() {
let ac = AccessControl {
block_threats: true,
allowed_users: Some(vec!["kapil".into()]),
allowed_channels: None,
};
assert!(ac.check_user("Kapil"));
assert!(!ac.check_user("maya"));
}
#[tokio::test]
async fn test_gateway_lifecycle() {
let gw = GatewayRunner::new();
assert!(!gw.is_running().await);
gw.start().await.unwrap();
assert!(gw.is_running().await);
gw.stop().await;
assert!(!gw.is_running().await);
}
}