telegram bot user isolation, config store, user context channels
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
|
||||
/// Read/write access to the instance-wide key/value config store
|
||||
/// (`config` table in `system.db`).
|
||||
///
|
||||
/// [`ConfigApi::set`] emits `ConfigKeyUpdated` on the system bus when the
|
||||
/// value changes, so subscribers (e.g. the Telegram plugin reloading its
|
||||
/// bindings) are notified without polling.
|
||||
#[async_trait]
|
||||
pub trait ConfigApi: Send + Sync {
|
||||
async fn get(&self, key: &str) -> Result<Option<String>>;
|
||||
async fn set(&self, key: &str, value: &str) -> Result<()>;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ pub const APP_NAME: &str = "Skald";
|
||||
|
||||
pub mod approval;
|
||||
pub mod bus;
|
||||
pub mod config_api;
|
||||
pub mod system_bus;
|
||||
pub mod chatbot;
|
||||
pub mod chat_hub;
|
||||
@@ -18,6 +19,7 @@ pub mod plugin;
|
||||
pub mod provider;
|
||||
pub mod remote;
|
||||
pub mod tool;
|
||||
pub mod user_channel;
|
||||
pub mod secrets;
|
||||
pub mod transcribe;
|
||||
pub mod tts;
|
||||
|
||||
@@ -5,12 +5,10 @@ use async_trait::async_trait;
|
||||
use serde_json::Value;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::approval::ApprovalApi;
|
||||
use crate::command::CommandApi;
|
||||
use crate::config_api::ConfigApi;
|
||||
use crate::system_bus::SystemEventBus;
|
||||
use crate::chat_hub::ChatHubApi;
|
||||
use crate::image_generate::ImageGenerateRegistry;
|
||||
use crate::inbox::InboxApi;
|
||||
use crate::location::LocationUpdater;
|
||||
use crate::memory::Memory;
|
||||
use crate::provider::ApiProviderRegistry;
|
||||
@@ -18,6 +16,7 @@ use crate::remote::RemoteAccess;
|
||||
use crate::secrets::SecretsApi;
|
||||
use crate::transcribe::{TranscribeProvider, TranscribeRegistry};
|
||||
use crate::tts::{TtsProvider, TtsRegistry};
|
||||
use crate::user_channel::UserChannelApi;
|
||||
|
||||
/// Closure that builds a fresh Axum router (e.g. for the mesh-facing server).
|
||||
pub type RouterFactory = Arc<dyn Fn() -> axum::Router + Send + Sync>;
|
||||
@@ -33,6 +32,9 @@ pub struct PluginContext {
|
||||
/// Custom file-based slash commands (`commands/<name>/`). Read-only from the
|
||||
/// plugin side — lets the Telegram bot resolve `/command` expansions.
|
||||
pub command: Arc<dyn CommandApi>,
|
||||
/// Key/value config store (`config` table in `system.db`). `set` emits
|
||||
/// `ConfigKeyUpdated` on the system bus.
|
||||
pub config: Arc<dyn ConfigApi>,
|
||||
/// Skald's shared SQLite pool — lets plugins create/use their own tables
|
||||
/// (e.g. `relay_*`) in the main DB. See plugin.md §12.1.
|
||||
pub db: Arc<sqlx::SqlitePool>,
|
||||
@@ -45,6 +47,10 @@ pub struct PluginContext {
|
||||
pub api_provider_registry: Arc<dyn ApiProviderRegistry>,
|
||||
pub location: Arc<dyn LocationUpdater>,
|
||||
pub system_bus: Arc<SystemEventBus>,
|
||||
/// Channel-to-session resolver (blueprint §13). Lets channel plugins
|
||||
/// (Telegram, mobile, …) look up an unlocked user's chat hub, approval
|
||||
/// manager and event stream by user id.
|
||||
pub user_channel: Arc<dyn UserChannelApi>,
|
||||
pub web_port: u16,
|
||||
pub remote_slot: Arc<RwLock<Option<Arc<dyn RemoteAccess>>>>,
|
||||
pub router_factory: RouterFactory,
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
//! Channel-to-session contract (blueprint §13).
|
||||
//!
|
||||
//! In the multi-user architecture, chat hubs, approval managers and event
|
||||
//! streams are per-user (inside [`UserContext`]). External channels (Telegram,
|
||||
//! mobile, …) need a way to resolve a user's owner-bound runtime at runtime,
|
||||
//! without depending on the concrete `Skald` / `UserContext` types.
|
||||
//!
|
||||
//! [`UserChannelApi`] is the lookup seam: given a `user_id`, returns a
|
||||
//! [`UserChannelHandle`] when the user's database is unlocked (§9), or `None`
|
||||
//! when it is still locked. The handle exposes the per-user [`ChatHubApi`],
|
||||
//! [`ApprovalApi`] and event stream — everything a channel adapter needs to
|
||||
//! route a message and receive the response events.
|
||||
//!
|
||||
//! This is the "one contract" of §13: each channel (Telegram, mobile, …) is a
|
||||
//! thin adapter over it, so N channel rewrites become 1 contract + N adapters.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
use crate::approval::ApprovalApi;
|
||||
use crate::chat_hub::ChatHubApi;
|
||||
use crate::events::GlobalEvent;
|
||||
|
||||
/// Resolves an unlocked user's channel handle.
|
||||
///
|
||||
/// Implemented by the application core (`Skald`) and injected into
|
||||
/// [`crate::plugin::PluginContext`] as `user_channel`.
|
||||
#[async_trait]
|
||||
pub trait UserChannelApi: Send + Sync {
|
||||
/// Returns the user's handle if their database is unlocked in this boot
|
||||
/// (§9: from first login until restart). `None` = locked — the caller
|
||||
/// should prompt the user to log in.
|
||||
async fn resolve_user(&self, user_id: &str) -> Option<Arc<dyn UserChannelHandle>>;
|
||||
}
|
||||
|
||||
/// Handle to one unlocked user's owner-bound runtime.
|
||||
///
|
||||
/// Lifetime = the user's pool lifetime (§9). Cloning the returned `Arc`s is
|
||||
/// cheap (they share the underlying state). The event receiver obtained from
|
||||
/// [`UserChannelHandle::subscribe`] is independent per call — each subscriber
|
||||
/// gets every future event.
|
||||
pub trait UserChannelHandle: Send + Sync {
|
||||
/// The opaque user id this handle belongs to.
|
||||
fn user_id(&self) -> &str;
|
||||
|
||||
/// The user's chat hub — send messages, manage sessions, query context.
|
||||
fn chat_hub(&self) -> Arc<dyn ChatHubApi>;
|
||||
|
||||
/// The user's approval manager — resolve pending tool-call approvals.
|
||||
fn approval(&self) -> Arc<dyn ApprovalApi>;
|
||||
|
||||
/// Subscribe to the user's server→client event stream.
|
||||
/// Events are scoped to this user; no cross-user leakage.
|
||||
fn subscribe(&self) -> broadcast::Receiver<GlobalEvent>;
|
||||
}
|
||||
Reference in New Issue
Block a user