From ba911ae8cbf239e529f4fa2a5e44b221d96daf0c Mon Sep 17 00:00:00 2001 From: xavix-yo Date: Sun, 19 Jul 2026 20:47:09 +0100 Subject: [PATCH] feat(plugins): plugin pages, per-user config, capabilities gate, mobile/telegram refactors - Plugin HTTP routes + web pages (plugin-page-host, plugin-catalog, plugin-detail) - Plugin access grants + per-user config (DB tables + API + frontend forms) - Capabilities-based guard (caps.rs) replacing role-id checks - Mobile connector: message routing, payload types, router refactor - Telegram bot: auth flow, event handling improvements - Honcho plugin: substantial rework - Sidebar: plugin pages integration, role-driven visibility - i18n: new strings for plugins, connectors, capabilities - Remove unused mascot asset --- CLAUDE.md | 10 +- crates/core-api/src/bus.rs | 4 + crates/core-api/src/lib.rs | 1 + crates/core-api/src/memory.rs | 6 +- crates/core-api/src/plugin.rs | 98 +++- crates/core-api/src/tool.rs | 4 + crates/core-api/src/user_channel.rs | 16 + crates/core-api/src/user_plugin_config.rs | 16 + crates/plugin-honcho/src/lib.rs | 461 +++++++++++------- crates/plugin-mobile-connector/src/app.rs | 111 ++++- crates/plugin-mobile-connector/src/lib.rs | 29 +- .../plugin-mobile-connector/src/payloads.rs | 83 ++++ crates/plugin-mobile-connector/src/router.rs | 258 ++++++++-- crates/plugin-mobile-connector/web/common.js | 51 ++ crates/plugin-mobile-connector/web/devices.js | 143 ++++++ crates/plugin-mobile-connector/web/pairing.js | 112 +++++ crates/plugin-telegram-bot/src/auth.rs | 81 ++- crates/plugin-telegram-bot/src/events.rs | 5 + crates/plugin-telegram-bot/src/handlers.rs | 13 + crates/plugin-telegram-bot/src/lib.rs | 59 ++- crates/skald-core/src/db/mod.rs | 115 +++++ crates/skald-core/src/db/plugin_access.rs | 103 ++++ .../skald-core/src/db/plugin_user_configs.rs | 49 ++ crates/skald-core/src/db/role_capabilities.rs | 5 + crates/skald-core/src/memory/mod.rs | 4 +- crates/skald-core/src/plugin/mod.rs | 341 +++++++++++-- .../src/session/handler/llm_loop.rs | 25 +- crates/skald-core/src/session/handler/mod.rs | 4 +- crates/skald-core/src/skald/accessors.rs | 12 + crates/skald-core/src/tools/fs/mod.rs | 6 +- src/frontend/api/caps.rs | 17 + src/frontend/api/guard.rs | 27 + src/frontend/api/mcp.rs | 12 +- src/frontend/api/mod.rs | 7 +- src/frontend/api/plugins.rs | 129 ++++- src/frontend/server.rs | 24 +- web/app.js | 8 + web/assets/mascot.png | Bin 283911 -> 0 bytes web/components/plugin-catalog.js | 160 ++++++ web/components/plugin-detail.js | 312 ++++++++++++ web/components/plugin-page-host.js | 98 ++++ web/components/plugins-page.js | 202 ++++++++ web/components/shared/plugin-common.js | 44 ++ web/components/sidebar.js | 53 +- web/css/connectors.css | 30 ++ web/css/page-shell.css | 16 + web/i18n/en.js | 41 ++ web/i18n/fr.js | 41 ++ web/i18n/it.js | 41 ++ web/index.html | 4 + 50 files changed, 3186 insertions(+), 305 deletions(-) create mode 100644 crates/core-api/src/user_plugin_config.rs create mode 100644 crates/plugin-mobile-connector/web/common.js create mode 100644 crates/plugin-mobile-connector/web/devices.js create mode 100644 crates/plugin-mobile-connector/web/pairing.js create mode 100644 crates/skald-core/src/db/plugin_access.rs create mode 100644 crates/skald-core/src/db/plugin_user_configs.rs create mode 100644 src/frontend/api/caps.rs delete mode 100644 web/assets/mascot.png create mode 100644 web/components/plugin-catalog.js create mode 100644 web/components/plugin-detail.js create mode 100644 web/components/plugin-page-host.js create mode 100644 web/components/plugins-page.js create mode 100644 web/components/shared/plugin-common.js diff --git a/CLAUDE.md b/CLAUDE.md index 7e8fb6c..155bef6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -55,6 +55,10 @@ Two rules keep the boundary real, and both are enforced by the compiler: - **The core never names a plugin.** A plugin contributes tools through `Plugin::tools(self: Arc)` — the sibling of `http_router()` — so nothing in the core has to downcast to a concrete type. Naming one would drag every plugin in the tree into the core, including a C build via `plugin-transcribe-whisper-local`. - **The core never learns about the process shell.** The `restart` tool defaults to the supervisor protocol (`exit(-1)`); a shell with different needs installs `tools::restart::set_restart_handler` at startup. The Tauri shell installs teardown-and-respawn there. This is why `skald-core` has no `desktop` feature. +**Plugin visibility & per-user config.** Plugins are managed from the `#plugins` page, not only by the agent. Enable/disable + instance config + access grants are gated by the `plugin.manage` capability (admin-only by construction). Visibility is **opt-in**: a row in `plugin_access(plugin_id, user_id)` grants a user sight of an enabled plugin (`plugin_id` is bare TEXT, never a FK — a `plugins` row exists only after the first toggle). A plugin with a non-empty `Plugin::user_config_schema()` exposes per-user settings, stored in `plugin_user_configs` (**admin-readable system.db — never secrets**) and applied through the `Plugin::update_user_config` hook, whose default just stores the blob via the `PluginUserConfigApi` on `PluginContext.user_config`. Telegram is the reference impl: the user pastes the bot's pairing code in their Plugins page, the override turns it into a `chat_id → user_id` binding (same write path as the `telegram_pairing` tool) and stores a `{linked, chat_id}` status blob for the UI. Endpoints: admin `GET/PUT /api/plugins[/{id}]` + `GET/PUT /api/plugins/{id}/access`; user `GET /api/plugins/mine` + `PUT /api/plugins/{id}/my-config`. + +**Plugin HTTP routes & web pages.** Every plugin's `http_router()` mounts at boot under `/api/plugin//` — **enabled or not**: two shared gates wrap each router (`require_auth`, then `guard::plugin_enabled_gate`, which re-checks the DB flag per request and answers 404 while disabled), so enable/disable serves/stops routes immediately with no restart, and plugin responses carry `Cache-Control: no-cache`. The router contract: cheap and safe to build pre-start, handlers tolerant of the not-running state (resolve runtime state per request through a shared cell, as mobile-connector does). A plugin may also contribute **frontend pages** via `Plugin::web_pages()` (`PluginPage { page_id, title, icon, entry, admin_only, priority }`): `GET /api/plugins/pages` returns the caller's visible pages (admin: all; others: non-`admin_only` pages of granted, enabled plugins) with `entry_url` resolved, and the sidebar renders them as menu entries routed `#plugin//`. A single `` (`web/components/plugin-page-host.js`) dynamic-imports the fragment ES module the plugin serves from its own router, registers its default-exported HTMLElement class, and mounts it with the `plugin-id` attribute — the fragment talks to its backend only through `/api/plugin//…` and runs with full session privileges (plugins are trusted: they ship in the binary). The frontend knows nothing about plugin page contents or behavior. + `skald_core::boot` emits curated startup lines on the `boot` tracing target; each shell decides how to render them (`src/boot_format.rs` here). The core says what happened, never how it looks. ## Key modules @@ -78,7 +82,7 @@ Two rules keep the boundary real, and both are enforced by the compiler: | `crates/skald-core/src/crypto/` | Envelope encryption (§4/§5.1). A random 256-bit DEK encrypts `{userid}.db`; `users.database_password` holds it sealed with AES-256-GCM under `Argon2id(password, salt)`. **The AEAD tag is the password verifier** — one derivation both authenticates and yields the key, and no second hash sits in the admin-readable DB. Cleartext users store the Argon2id output directly, compared constant-time. Argon2 runs in `spawn_blocking` behind a 2-permit semaphore (256 MiB per derivation) | | `src/config.rs` | Loads `config.yml`; LLM clients, strength/use_cases, data root. Also hosts `bootstrap_data_dir()` — under the `desktop` feature, relocates the process cwd to a per-user data dir when running inside a `.app` bundle (no-op in dev mode and headless mode) | | `crates/skald-core/src/mcp/` | MCP runtimes + the `McpProvider` seam (§7): the shared host **global** runtime and the per-user **container** runtimes, unioned per session as `UserMcpView`. See the MCP connectors section | -| `crates/skald-core/src/plugin/` | Plugin system: discovery, enable/disable, tool registration | +| `crates/skald-core/src/plugin/` | Plugin system: discovery, enable/disable, tool registration, per-user access grants + per-user config | | `crates/skald-core/src/cron/` | Scheduled job runner | | `crates/skald-core/src/compactor.rs` | Context compaction (summarises history when token budget exceeded) | | `crates/skald-core/src/approval/` | Approval rules engine | @@ -100,7 +104,7 @@ Two rules keep the boundary real, and both are enforced by the compiler: The schema is split into two buckets (§5.1), and the split is the point: -- **`create_registry_tables`** — instance-wide, readable without any user key: `users`, `roles`, `llm_providers`, `llm_models`, `transcribe_models`, `tts_models`, `image_generate_models`, `plugins`, `approval_rules`, `tool_permission_groups`, `config`, `known_tools`, `llm_requests`, `mcp_catalog`, `mcp_global_servers` + `mcp_global_access`, `oauth_providers`, `role_capabilities`, `shared_folders` + `shared_folder_members`. The MCP tables back the Connectors model (§7/§14/§15 — see its own section); `oauth_providers` (accessor `db/oauth_providers.rs`) holds one row per identity provider (Google…) — endpoints + `client_id`/`client_secret` + `redirect_uri`, admin-owned household secrets (§4/§15b), never a per-user token. The last two (accessor `db/shared_folders.rs`) are the **membership** of the on-disk shared folders (§6): a junction table so a member can be read-only (`can_write`) and so the container mount topology + the `shared/{X}` fs routing both query it. FK `shared_folder_members.user_id → users(id)` is registry→registry (same file), which is allowed — unlike an owner→registry key. +- **`create_registry_tables`** — instance-wide, readable without any user key: `users`, `roles`, `llm_providers`, `llm_models`, `transcribe_models`, `tts_models`, `image_generate_models`, `plugins`, `plugin_access` + `plugin_user_configs`, `approval_rules`, `tool_permission_groups`, `config`, `known_tools`, `llm_requests`, `mcp_catalog`, `mcp_global_servers` + `mcp_global_access`, `oauth_providers`, `role_capabilities`, `shared_folders` + `shared_folder_members`. The MCP tables back the Connectors model (§7/§14/§15 — see its own section); `oauth_providers` (accessor `db/oauth_providers.rs`) holds one row per identity provider (Google…) — endpoints + `client_id`/`client_secret` + `redirect_uri`, admin-owned household secrets (§4/§15b), never a per-user token. The last two (accessor `db/shared_folders.rs`) are the **membership** of the on-disk shared folders (§6): a junction table so a member can be read-only (`can_write`) and so the container mount topology + the `shared/{X}` fs routing both query it. FK `shared_folder_members.user_id → users(id)` is registry→registry (same file), which is allowed — unlike an owner→registry key. - **`create_owner_tables`** — one owner's content, **identical schema in every file that has it**: `chat_sessions`, `chat_sessions_stack`, `chat_history`, `chat_llm_tools`, `chat_summaries`, `session_scratchpad`, `session_mcp_grants`, `stack_mcp_grants`, `scheduled_jobs`, `job_runs`, `mcp_user_servers`, `mcp_events`, `sources`, `secrets`, `projects`, `project_tickets`, `llm_request_payloads`, `memory_docs` (+ FTS5 `memory_docs_fts`). `mcp_user_servers` (a user's activated per-user connectors) carries `catalog_name` as a **bare `TEXT` snapshot** of `mcp_catalog.name`, never a FK — an owner→registry key would fail every INSERT; for an OAuth connector it also snapshots `oauth_provider` + `deliver_json`, and its `api_key` column holds the refresh token (in the SQLCipher-encrypted file, so no column crypto). Because `memory_docs` is an owner table, one definition backs **private** memory in each `{userid}.db` and **shared** memory in `system.db` (the household owner) — see the memory namespace note below. Schema is greenfield (no migrations, §0), but a purely **additive** column lands on an existing DB in place: `db::ensure_column` runs `ALTER TABLE … ADD COLUMN` and swallows the "duplicate column" error, a no-op on a fresh DB where the `CREATE TABLE` already has the column. Used for the OAuth columns on `mcp_catalog` / `mcp_user_servers` so a dev box need not be wiped for an additive change (a full recreate is still valid). @@ -293,6 +297,8 @@ All extend `LightElement` from `web/lib/base.js` (Lit). `ChatSession` (`web/lib/ | `approval-rules.js` | `` | Approval rule management | | `cron-jobs.js` | `` | Scheduled job management | | `connectors.js` | `` | MCP Connectors list (one row per connector): user activate/deactivate + granted globals; admin gets a **Sign-in providers** modal (OAuth client creds) + Catalog/Marketplace nav (§7/§14/§15) | +| `plugins-page.js` | `` | `#plugins` — user: granted plugins + schema-driven per-user config form; admin: enable toggle, instance config, per-user access checklist | +| `plugin-page-host.js` | `` | Host for plugin-contributed pages (`#plugin//`): dynamic-imports the fragment module, registers its element, mounts it with `plugin-id` | | `connector-detail.js` | `` | A connector's own page (`#connector?name=X`): env/secret form + Test, the **OAuth login panel** (sign in → paste code → complete, §15), global enable + per-user access grants | | `shared/connector-common.js` | (helpers) | Shared Connectors vocabulary: `statusOf` (incl. `needs_login` for a pending OAuth row), `STATUS_LABEL`, schema normalization, `jf` fetch | | `llm-providers.js` | `` | LLM provider management | diff --git a/crates/core-api/src/bus.rs b/crates/core-api/src/bus.rs index 7de4fde..3430b23 100644 --- a/crates/core-api/src/bus.rs +++ b/crates/core-api/src/bus.rs @@ -69,6 +69,10 @@ pub struct ToolCallEvent { pub struct ChatEvent { pub session_id: i64, pub stack_id: i64, + /// The session owner's user id. Ids in this event (`session_id`, …) are local + /// to that user's pool, so consumers scoping per-user (e.g. the Honcho memory + /// sink) must key on `user_id` to avoid cross-user collisions. + pub user_id: String, /// `chat_history.id` for this message. pub message_id: i64, pub role: ChatEventRole, diff --git a/crates/core-api/src/lib.rs b/crates/core-api/src/lib.rs index 17ea3e3..7c5004f 100644 --- a/crates/core-api/src/lib.rs +++ b/crates/core-api/src/lib.rs @@ -21,6 +21,7 @@ pub mod remote; pub mod tool; pub mod user_channel; pub mod user_fs; +pub mod user_plugin_config; pub mod secrets; pub mod transcribe; pub mod tts; diff --git a/crates/core-api/src/memory.rs b/crates/core-api/src/memory.rs index 79f8376..c091ec6 100644 --- a/crates/core-api/src/memory.rs +++ b/crates/core-api/src/memory.rs @@ -18,7 +18,11 @@ pub trait Memory: Send + Sync { /// Retrieves context for the upcoming turn to inject into the system prompt. /// Returns `None` on cold start, backend down, or nothing useful available. - async fn query_context(&self, session_id: i64, user_message: &str) -> Option; + /// + /// `user_id` is the session owner: multi-user backends scope retrieval to that + /// user's own memory (e.g. their Honcho peer), and `session_id` is local to + /// that user's pool so it must be namespaced by `user_id` to stay unique. + async fn query_context(&self, user_id: &str, session_id: i64, user_message: &str) -> Option; /// Optional LLM-callable tools exposed by this backend (e.g. `memory_query`). /// Called per turn — added to the live tool list and dispatched before the diff --git a/crates/core-api/src/plugin.rs b/crates/core-api/src/plugin.rs index 6026bc0..5cd61c6 100644 --- a/crates/core-api/src/plugin.rs +++ b/crates/core-api/src/plugin.rs @@ -17,10 +17,46 @@ use crate::secrets::SecretsApi; use crate::transcribe::{TranscribeProvider, TranscribeRegistry}; use crate::tts::{TtsProvider, TtsRegistry}; use crate::user_channel::UserChannelApi; +use crate::user_plugin_config::PluginUserConfigApi; /// Closure that builds a fresh Axum router (e.g. for the mesh-facing server). pub type RouterFactory = Arc axum::Router + Send + Sync>; +/// The authenticated caller behind a plugin-router request. +/// +/// The frontend's auth layer injects this into request extensions for every +/// gated request (alongside its own richer, bin-private `AuthUser`). A plugin +/// router cannot name bin-crate types, so this is how a plugin handler learns +/// *who* is calling — e.g. to bind a freshly paired device to the admin who +/// opened the pairing window. Gate admin-only actions with +/// [`crate::user_channel::UserChannelApi::plugin_access`] (which returns `true` +/// only for admins when the plugin `manages_own_access`). +#[derive(Clone, Debug)] +pub struct Caller { + pub user_id: String, +} + +/// A web UI page contributed by a plugin — see [`Plugin::web_pages`]. +#[derive(Debug, Clone)] +pub struct PluginPage { + /// Stable id, unique within the plugin — used in the route + /// (`#plugin//`). e.g. "pairing", "devices". + pub page_id: &'static str, + /// Menu label. Shown as-is (the plugin owns its UI strings). + pub title: String, + /// Bootstrap Icons name (e.g. "qr-code", "phone"), rendered as `bi-`. + pub icon: &'static str, + /// Path of the page's ES module **inside this plugin's router**, e.g. + /// "web/pairing.js" — served at `/api/plugin//web/pairing.js`. + pub entry: String, + /// `true` = only the built-in admin role sees the menu entry (e.g. a + /// pairing/devices console). `false` = any user with `plugin_access`. + pub admin_only: bool, + /// Menu ordering — ascending; the native menu will adopt the same field + /// when it is reworked. Use round numbers (10, 20, …) to leave room. + pub priority: i32, +} + /// All deps a plugin may need — passed to [`Plugin::start`] and [`Plugin::reload`]. /// /// Fields are `Arc` sourced from `core-api`. Plugins use only the @@ -51,6 +87,9 @@ pub struct PluginContext { /// (Telegram, mobile, …) look up an unlocked user's chat hub, approval /// manager and event stream by user id. pub user_channel: Arc, + /// Per-user plugin configuration store (`plugin_user_configs` table). + /// Admin-readable — never secrets. + pub user_config: Arc, pub web_port: u16, pub remote_slot: Arc>>>, pub router_factory: RouterFactory, @@ -70,6 +109,30 @@ pub trait Plugin: Send + Sync { /// JSON Schema describing the plugin's config fields. fn config_schema(&self) -> Value { serde_json::json!({}) } + /// JSON Schema describing the plugin's *per-user* config fields (e.g. + /// Telegram's pairing code). Empty schema (the default) = the plugin has + /// no per-user settings and does not appear as configurable in the user + /// UI. Values are stored admin-readable in `system.db` — never secrets. + fn user_config_schema(&self) -> Value { serde_json::json!({}) } + + /// Applies a per-user config submission. The default just stores the blob + /// in the generic store; plugins that need validation or a side effect + /// (e.g. Telegram turning a pairing code into a chat binding) override it + /// and may store a sanitized status blob for the UI via `ctx.user_config`. + async fn update_user_config(&self, user_id: &str, config: Value, ctx: &PluginContext) -> Result<()> { + ctx.user_config.set(self.id(), user_id, config).await + } + + /// Whether the plugin decides *who may use it* through its own binding / + /// pairing lifecycle rather than the generic `plugin_access` grants — e.g. + /// the mobile connector, whose access is the admin-mediated device→user + /// binding (§13). When `true`, the admin Plugins UI suppresses the "User + /// access" checklist (it would control nothing) and the plugin never appears + /// in a user's "My plugins" view. Default `false`: access is the admin's + /// per-user `plugin_access` grant (as Telegram uses — its grant gates the + /// bot at runtime even though pairing is self-service). + fn manages_own_access(&self) -> bool { false } + /// Called whenever the enabled flag or config changes — including at startup. /// The plugin is responsible for diffing state and restarting only what changed. async fn reload(&self, enabled: bool, config: Value, ctx: PluginContext) -> Result<()>; @@ -82,11 +145,40 @@ pub trait Plugin: Send + Sync { /// Optional Axum router contributed by the plugin. When `Some`, the main /// `WebFrontend` nests it under `/api/plugin//` behind Skald's normal - /// auth (plugin.md §12.3). The router must close over the plugin's own state - /// (it receives no `State`). Default: no routes — existing plugins are - /// unaffected. + /// auth plus a runtime enabled-gate: **every** plugin router is mounted at + /// boot, and a disabled plugin's routes answer 404 until it is enabled + /// (no restart needed). + /// + /// Contract: + /// - Building the router must be cheap and safe even if the plugin never + /// starts — it is called at boot regardless of the enabled flag. Handlers + /// must tolerate the not-running state (an enabled-but-crashed plugin can + /// still receive requests). Resolve runtime state per request through a + /// shared cell (e.g. `Arc>>`) rather than capturing it. + /// - The router closes over the plugin's own state (it receives no `State`). + /// - Page fragments and other assets are served from here too; responses + /// automatically get `Cache-Control: no-cache` from the shell. + /// + /// Default: no routes — existing plugins are unaffected. fn http_router(&self) -> Option { None } + /// Web UI pages this plugin contributes to the frontend, surfaced as menu + /// entries and served to the browser by `GET /api/plugins/pages`. + /// + /// Each page is a self-contained ES module served by this plugin's own + /// [`Plugin::http_router`] at `entry` (e.g. `web/pairing.js` → + /// `/api/plugin//web/pairing.js`). Fragment contract: + /// - default-export an `HTMLElement` class (a Lit element works); the host + /// registers it as a custom element and sets the `plugin-id` attribute; + /// - the fragment talks to its own backend only through + /// `/api/plugin//…` — no host APIs are injected; + /// - it runs with the full privileges of the logged-in session (plugins are + /// trusted — they ship in the binary); + /// - it carries its own UI strings (reads the locale from `/api/auth/me`). + /// + /// Default: no pages. + fn web_pages(&self) -> Vec { Vec::new() } + /// Tools this plugin contributes to the registry — the sibling of /// [`Plugin::http_router`]. /// diff --git a/crates/core-api/src/tool.rs b/crates/core-api/src/tool.rs index b2b18fc..005e3f6 100644 --- a/crates/core-api/src/tool.rs +++ b/crates/core-api/src/tool.rs @@ -47,6 +47,10 @@ pub enum ToolCategory { pub struct ToolContext { /// The session that issued this tool call. Ids are local to `pool`. pub session_id: i64, + /// The owner (caller) user id. Tools that address a per-user external store + /// (e.g. the Honcho memory peer) key on this so they act on the caller's own + /// data, never a shared/global peer. + pub user_id: String, /// The owner's unlocked database pool (per-user in multi-user mode; the shared /// `system.db` in the transitional single-pool state). pub pool: Arc, diff --git a/crates/core-api/src/user_channel.rs b/crates/core-api/src/user_channel.rs index 7cb03a9..a19b23b 100644 --- a/crates/core-api/src/user_channel.rs +++ b/crates/core-api/src/user_channel.rs @@ -34,6 +34,22 @@ pub trait UserChannelApi: Send + Sync { /// (§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>; + + /// Whether `user_id` may currently use the plugin `plugin_id` — granted in + /// `plugin_access`, or holding the admin role (implicit, mirroring the web + /// `/plugins/mine` view). Channel adapters enforce this on every inbound + /// message so an admin revoking access takes effect immediately, without + /// having to touch existing pairing/binding rows. **Fail-closed**: an + /// unknown user or a lookup error returns `false`. + async fn plugin_access(&self, plugin_id: &str, user_id: &str) -> bool; + + /// Resolves a web **session token** to its user id, or `None` if the token + /// is unknown / expired. Lets a channel adapter turn a token the client + /// obtained from `POST /api/auth/login` into an authenticated identity — the + /// seam behind the mobile self-service device binding (a device proves *who* + /// it is by presenting the session it just logged in with). Sessions are + /// in-memory, so a token stops resolving after a restart. + async fn user_for_session(&self, token: &str) -> Option; } /// Handle to one unlocked user's owner-bound runtime. diff --git a/crates/core-api/src/user_plugin_config.rs b/crates/core-api/src/user_plugin_config.rs new file mode 100644 index 0000000..f14f4fc --- /dev/null +++ b/crates/core-api/src/user_plugin_config.rs @@ -0,0 +1,16 @@ +use anyhow::Result; +use async_trait::async_trait; +use serde_json::Value; + +/// Per-user plugin configuration store (`plugin_user_configs` table in +/// `system.db`). +/// +/// Values are deliberately admin-readable — the table lives in the registry +/// database — so `user_config_schema`s must never collect secrets. A plugin +/// that needs per-user secrets should keep them elsewhere. +#[async_trait] +pub trait PluginUserConfigApi: Send + Sync { + async fn get(&self, plugin_id: &str, user_id: &str) -> Result>; + async fn set(&self, plugin_id: &str, user_id: &str, config: Value) -> Result<()>; + async fn delete(&self, plugin_id: &str, user_id: &str) -> Result<()>; +} diff --git a/crates/plugin-honcho/src/lib.rs b/crates/plugin-honcho/src/lib.rs index 0b2be92..9e1177b 100644 --- a/crates/plugin-honcho/src/lib.rs +++ b/crates/plugin-honcho/src/lib.rs @@ -1,37 +1,48 @@ //! Honcho memory plugin — streams completed chat turns to a Honcho server //! and exposes a [`Memory`] read path via [`HonchoMemory`]. //! +//! # Multi-user model (blueprint §16) +//! Honcho stores conversations **in cleartext** in its own external database, +//! outside each user's encrypted `{userid}.db`. So streaming a user's turns to +//! Honcho is **strictly opt-in**: the admin enables + configures the plugin, and +//! then each user must explicitly opt in from their Plugins page before any of +//! their messages leave the box. Both the write path (event listener) and the +//! read path (`query_context` + the tools, which send the user's message to +//! Honcho as a search embedding) gate on that per-user flag. +//! //! # Write path //! Subscribes to the [`ChatEventBus`] and forwards every user/assistant message -//! from **interactive, non-ephemeral** sessions to Honcho so that the server can -//! build long-term memory (conclusions) about the user. +//! from **interactive, non-ephemeral** sessions *of opted-in users* to Honcho so +//! the server can build long-term memory (conclusions) about that user. //! //! # Read path //! [`HonchoMemory`] implements the [`Memory`] trait. Before each LLM turn, -//! `query_context` calls Honcho's `session_context` API to retrieve a -//! token-budgeted summary of what is known so far and injects it into the -//! system prompt. -//! -//! # Filtering (write path) -//! An event is forwarded only when **all** of the following hold: -//! - `is_interactive = true` — a real user is in the conversation -//! - `is_ephemeral = false` — not a short-lived automated session (cron, tic) -//! - `is_synthetic = false` — message content was typed by a user, not -//! injected by the system +//! `query_context` calls Honcho's `peer_context`/`session_context` APIs to +//! retrieve a token-budgeted summary of what is known about **the calling user** +//! and injects it into the system prompt. //! //! # Honcho object model -//! ``` -//! workspace (one per agent instance, from config) -//! ├── peer "user" (observe_others = true) -//! ├── peer "assistant" (observe_me = true) -//! └── session (one per local chat_sessions.id, created lazily) -//! ├── message peer_id="user" +//! ```text +//! workspace (one per instance/household, from config) +//! ├── peer "" (one per real user; observe_me = true) -> their profile +//! ├── peer "assistant" (SHARED; observe_me = FALSE) -> no global rep +//! └── session "{workspace}-{user_id}-{session_id}" (one per user's chat session) +//! ├── message peer_id="" //! └── message peer_id="assistant" //! ``` //! -//! The `session_map` (local session_id → Honcho session UUID) is shared between -//! the write-path listener task and `HonchoMemory` so both sides see the same -//! mapping without duplication. +//! The **assistant peer is shared** across every user's private session but runs +//! with `observe_me = false`, so Honcho never builds a global representation of +//! the assistant. That representation would otherwise aggregate every user's +//! messages (the assistant restates their private facts) into one cross-user +//! store — a leak. With it off there is nothing to leak, and retrieval only ever +//! reads a user's *own* peer, so a single shared assistant peer is safe without +//! splitting it per user. +//! +//! The `session_map` (`(user_id, local session_id)` → Honcho session id) is shared +//! between the write-path listener task and `HonchoMemory` so both sides see the +//! same mapping without duplication. Keying on `user_id` too is required: local +//! session ids are pool-local and collide across users. use std::collections::HashMap; use std::sync::Arc; @@ -48,7 +59,10 @@ use tracing::{debug, info, trace, warn}; use core_api::bus::{BusEvent, ChatEvent, ChatEventRole, RecvError}; use core_api::memory::Memory; use core_api::plugin::PluginContext; -use core_api::tool::{Tool, ToolCategory}; +use core_api::tool::{ + SimpleExecution, Tool, ToolCategory, ToolContext, ToolExecution, ToolResult, +}; +use core_api::user_plugin_config::PluginUserConfigApi; use honcho_client::HonchoClient; use honcho_client::models::{ ConclusionCreate, MessageCreate, PeerCreate, PeerRepresentationGet, @@ -56,7 +70,8 @@ use honcho_client::models::{ }; const PLUGIN_ID: &str = "honcho"; -const PEER_USER: &str = "user"; +/// The single shared assistant peer. Runs with `observe_me = false` in every +/// session so no cross-user global representation of the assistant is built. const PEER_ASSISTANT: &str = "assistant"; /// Token budget for session_context queries. const CONTEXT_TOKENS: u32 = 2000; @@ -70,6 +85,22 @@ struct HonchoConfig { workspace_id: String, } +/// Deterministic Honcho session id for a user's local chat session. Namespaced by +/// `user_id` because local session ids are pool-local and collide across users. +fn honcho_session_id(workspace_id: &str, user_id: &str, local_session_id: i64) -> String { + format!("{workspace_id}-{user_id}-{local_session_id}") +} + +/// Reads the per-user opt-in flag (`plugin_user_configs.enabled`). Off by default: +/// a user's turns never reach Honcho until they explicitly opt in. Any read/parse +/// failure is treated as "not opted in" — fail closed on a privacy control. +async fn opted_in(user_config: &Arc, user_id: &str) -> bool { + match user_config.get(PLUGIN_ID, user_id).await { + Ok(Some(cfg)) => cfg.get("enabled").and_then(|v| v.as_bool()).unwrap_or(false), + _ => false, + } +} + // ── HonchoMemory ────────────────────────────────────────────────────────────── /// Implements the [`Memory`] trait for Honcho. @@ -81,16 +112,18 @@ struct HonchoConfig { pub struct HonchoMemory { /// Mirrors `HonchoPlugin::running`; false when the plugin is stopped. running: Arc, - /// Active client + workspace_id; None when the plugin is not running. + /// Active client + workspace_id + per-user config store; None when stopped. inner: std::sync::RwLock>, - /// Shared with the write-path listener task. - session_map: Arc>>, + /// Shared with the write-path listener task. Keyed by `(user_id, session_id)`. + session_map: Arc>>, } #[derive(Clone)] struct HonchoInner { client: Arc, workspace_id: String, + /// Per-user opt-in store; gates both read and write paths. + user_config: Arc, } impl HonchoMemory { @@ -102,8 +135,13 @@ impl HonchoMemory { } } - fn activate(&self, client: Arc, workspace_id: String) { - *self.inner.write().unwrap() = Some(HonchoInner { client, workspace_id }); + fn activate( + &self, + client: Arc, + workspace_id: String, + user_config: Arc, + ) { + *self.inner.write().unwrap() = Some(HonchoInner { client, workspace_id, user_config }); } fn deactivate(&self) { @@ -131,7 +169,16 @@ impl Memory for HonchoMemory { && self.inner.read().unwrap().is_some() } - async fn query_context(&self, session_id: i64, user_message: &str) -> Option { + async fn query_context(&self, user_id: &str, session_id: i64, user_message: &str) -> Option { + let HonchoInner { client, workspace_id, user_config } = self.inner()?; + + // Privacy gate: query_context sends `user_message` to Honcho as a search + // embedding, so a non-opted-in user's turn would leak. Skip entirely. + if !opted_in(&user_config, user_id).await { + trace!(session_id, %user_id, "honcho: user not opted in — skipping query_context"); + return None; + } + // Truncate to at most 120 *characters* (not bytes) to avoid a panic on // multi-byte UTF-8 codepoints (e.g. 'è' spans two bytes, so a fixed // byte-index like 120 can land in the middle of it). @@ -142,25 +189,23 @@ impl Memory for HonchoMemory { .unwrap_or(user_message.len()); trace!( session_id, + %user_id, msg_preview = &user_message[..preview_end], "honcho: query_context invoked" ); - let HonchoInner { client, workspace_id } = self.inner()?; - // ── Strategy: peer_context (global) + session_context (current session) ── // // peer_context with search_query searches conclusions derived from ALL past - // sessions — this is the only way cross-session references ("remember when - // we talked about X last week?") can be resolved automatically. + // sessions of THIS user — this is the only way cross-session references + // ("remember when we talked about X last week?") can be resolved + // automatically. It reads the user's OWN peer, so it never surfaces another + // user's memory. // // session_context is kept as a secondary call for the current session only, // to surface conclusions/summaries specific to the ongoing conversation that // may not yet be reflected in the peer-level representation. // - // Two embeddings per turn is the cost; the benefit is that the LLM always - // has both global long-term memory AND current-session context. - // // NOTE: session_context is skipped on the first turn (404 — session not yet // created in Honcho by the write path) to avoid a wasted HTTP round-trip. @@ -168,7 +213,7 @@ impl Memory for HonchoMemory { trace!(session_id, "honcho: querying peer_context (global, with search_query)"); let peer_ctx = match client.peer_context( &workspace_id, - PEER_USER, + user_id, &PeerRepresentationGet { search_query: Some(user_message.to_string()), ..Default::default() @@ -193,8 +238,8 @@ impl Memory for HonchoMemory { // // session_context is a GET with search_query but Honcho re-uses the same // embedding vector already computed for the peer_context call above - // (server-side caching). No additional LM Studio call in practice. - let deterministic_id = format!("{workspace_id}-{session_id}"); + // (server-side caching). No additional embedding call in practice. + let deterministic_id = honcho_session_id(&workspace_id, user_id, session_id); trace!(session_id, honcho_session_id = %deterministic_id, "honcho: querying session_context"); let session_ctx = match client.session_context( &workspace_id, @@ -243,43 +288,76 @@ impl Memory for HonchoMemory { fn tools(&self) -> Vec> { match self.inner() { - Some(HonchoInner { client, workspace_id }) => vec![ + Some(HonchoInner { client, workspace_id, user_config }) => vec![ Arc::new(MemoryQueryTool { client: Arc::clone(&client), workspace_id: workspace_id.clone(), + user_config: Arc::clone(&user_config), }), Arc::new(HonchoProfileTool { client: Arc::clone(&client), workspace_id: workspace_id.clone(), + user_config: Arc::clone(&user_config), }), Arc::new(HonchoSearchTool { client: Arc::clone(&client), workspace_id: workspace_id.clone(), + user_config: Arc::clone(&user_config), }), Arc::new(HonchoContextTool { client: Arc::clone(&client), workspace_id: workspace_id.clone(), + user_config: Arc::clone(&user_config), + }), + Arc::new(HonchoConcludeTool { + client, + workspace_id, + user_config, }), - Arc::new(HonchoConcludeTool { client, workspace_id }), ], None => vec![], } } } +/// Message returned by any Honcho tool when the calling user has not opted in. +/// Keeps the agent from silently sending the query to the external memory server. +const NOT_OPTED_IN: &str = + "Long-term memory (Honcho) is off for this user — they have not opted in, so \ + nothing was queried or stored."; + +/// Wraps a Honcho tool's async work in the standard opt-in gate + `SimpleExecution`. +/// `f` receives the resolved peer (`user_id`) and is only run when the user is +/// opted in; otherwise the tool returns [`NOT_OPTED_IN`] without contacting Honcho. +fn gated_execution<'a, F, Fut>( + user_config: Arc, + user_id: String, + f: F, +) -> Box +where + F: FnOnce(String) -> Fut + Send + 'a, + Fut: std::future::Future> + Send + 'a, +{ + let fut = async move { + if !opted_in(&user_config, &user_id).await { + return Ok(ToolResult::Text(NOT_OPTED_IN.to_string())); + } + f(user_id).await.map(ToolResult::Text) + }; + Box::new(SimpleExecution::new(Box::pin(fut))) +} + // ── MemoryQueryTool ─────────────────────────────────────────────────────────── -/// LLM-callable tool that queries Honcho's Dialectic API. +/// LLM-callable tool that queries Honcho's Dialectic API for the calling user. /// /// The official Honcho documentation explicitly recommends exposing `peer.chat()` -/// as a tool for agents: the LLM decides on its own when extra memory context -/// is needed and calls this tool with a natural-language question. -/// -/// Uses `tokio::task::block_in_place` to bridge the sync `Tool::execute` interface -/// with the async HTTP call, safely running inside the existing Tokio runtime. +/// as a tool for agents: the LLM decides on its own when extra memory context is +/// needed and calls this tool with a natural-language question. struct MemoryQueryTool { client: Arc, workspace_id: String, + user_config: Arc, } impl Tool for MemoryQueryTool { @@ -311,71 +389,52 @@ impl Tool for MemoryQueryTool { ToolCategory::Introspection } - fn execute(&self, args: Value) -> anyhow::Result { - let query = args["query"] - .as_str() - .ok_or_else(|| anyhow::anyhow!("memory_query: missing 'query' argument"))? - .to_string(); - + fn run_with<'a>(&'a self, ctx: &ToolContext, args: Value) -> Box { let client = Arc::clone(&self.client); let workspace_id = self.workspace_id.clone(); + gated_execution(Arc::clone(&self.user_config), ctx.user_id.clone(), move |peer| async move { + let query = args["query"] + .as_str() + .ok_or_else(|| anyhow::anyhow!("memory_query: missing 'query' argument"))? + .to_string(); - // Bridge sync Tool::execute → async HTTP call. - // block_in_place yields the thread to the Tokio scheduler while the - // nested block_on drives the future to completion — safe inside an - // existing multi-thread Tokio runtime. - tokio::task::block_in_place(|| { - tokio::runtime::Handle::current().block_on(async move { - let opts = honcho_client::models::DialecticOptions { - query, - session_id: None, - target: None, - stream: Some(false), - reasoning_level: Some("low".to_string()), - }; - let response = client - .peer_chat(&workspace_id, PEER_USER, &opts) - .await - .map_err(|e| anyhow::anyhow!("memory_query: {e}"))?; + let opts = honcho_client::models::DialecticOptions { + query, + session_id: None, + target: None, + stream: Some(false), + reasoning_level: Some("low".to_string()), + }; + let response = client + .peer_chat(&workspace_id, &peer, &opts) + .await + .map_err(|e| anyhow::anyhow!("memory_query: {e}"))?; - // The Dialectic endpoint returns a JSON object. - // Try known content fields; fall back to pretty-printed JSON. - let text = response.get("content") - .or_else(|| response.get("response")) - .or_else(|| response.get("message")) - .and_then(|v| v.as_str()) - .map(str::to_string) - .unwrap_or_else(|| { - serde_json::to_string_pretty(&response) - .unwrap_or_else(|_| response.to_string()) - }); + // The Dialectic endpoint returns a JSON object. + // Try known content fields; fall back to pretty-printed JSON. + let text = response.get("content") + .or_else(|| response.get("response")) + .or_else(|| response.get("message")) + .and_then(|v| v.as_str()) + .map(str::to_string) + .unwrap_or_else(|| { + serde_json::to_string_pretty(&response) + .unwrap_or_else(|_| response.to_string()) + }); - Ok(text) - }) + Ok(text) }) } } -/// Bridge a synchronous `Tool::execute` to an async Honcho call. -/// -/// `block_in_place` yields the worker thread back to the Tokio scheduler while -/// the nested `block_on` drives the future to completion — safe inside the -/// existing multi-thread runtime without spawning a new thread. Shared by all -/// Honcho tools. -fn run_blocking(fut: F) -> T -where - F: std::future::Future, -{ - tokio::task::block_in_place(|| tokio::runtime::Handle::current().block_on(fut)) -} - // ── HonchoProfileTool ───────────────────────────────────────────────────────── -/// Reads or overwrites the user's *peer card* — a curated list of key facts -/// (name, role, preferences, communication style) maintained by Honcho. +/// Reads or overwrites the calling user's *peer card* — a curated list of key +/// facts (name, role, preferences, communication style) maintained by Honcho. struct HonchoProfileTool { client: Arc, workspace_id: String, + user_config: Arc, } impl Tool for HonchoProfileTool { @@ -403,23 +462,22 @@ impl Tool for HonchoProfileTool { fn category(&self) -> ToolCategory { ToolCategory::Introspection } - fn execute(&self, args: Value) -> anyhow::Result { + fn run_with<'a>(&'a self, ctx: &ToolContext, args: Value) -> Box { let client = Arc::clone(&self.client); let workspace_id = self.workspace_id.clone(); let card_update = args.get("card").and_then(|v| v.as_array()).cloned(); - - run_blocking(async move { + gated_execution(Arc::clone(&self.user_config), ctx.user_id.clone(), move |peer| async move { match card_update { Some(facts) => { client - .set_peer_card(&workspace_id, PEER_USER, None, json!(facts)) + .set_peer_card(&workspace_id, &peer, None, json!(facts)) .await .map_err(|e| anyhow::anyhow!("honcho_profile: {e}"))?; Ok(format!("Peer card updated ({} facts).", facts.len())) } None => { let card = client - .get_peer_card(&workspace_id, PEER_USER, None) + .get_peer_card(&workspace_id, &peer, None) .await .map_err(|e| anyhow::anyhow!("honcho_profile: {e}"))?; Ok(serde_json::to_string_pretty(&card) @@ -432,12 +490,13 @@ impl Tool for HonchoProfileTool { // ── HonchoSearchTool ────────────────────────────────────────────────────────── -/// Semantic search over the conclusions Honcho has derived about the user. -/// Returns raw ranked excerpts — no LLM synthesis — including their IDs so the -/// model can later delete a specific one via `honcho_conclude`. +/// Semantic search over the conclusions Honcho has derived about the calling +/// user. Returns raw ranked excerpts — no LLM synthesis — including their IDs so +/// the model can later delete a specific one via `honcho_conclude`. struct HonchoSearchTool { client: Arc, workspace_id: String, + user_config: Arc, } impl Tool for HonchoSearchTool { @@ -465,23 +524,22 @@ impl Tool for HonchoSearchTool { fn category(&self) -> ToolCategory { ToolCategory::Introspection } - fn execute(&self, args: Value) -> anyhow::Result { - let query = args["query"] - .as_str() - .ok_or_else(|| anyhow::anyhow!("honcho_search: missing 'query' argument"))? - .to_string(); - + fn run_with<'a>(&'a self, ctx: &ToolContext, args: Value) -> Box { let client = Arc::clone(&self.client); let workspace_id = self.workspace_id.clone(); - // Honcho's `conclusions/query` endpoint requires observer/observed // filters; the proven path (shared with the read-path) is `peer_context` // with a `search_query`, which ranks the user's conclusions by relevance. - run_blocking(async move { + gated_execution(Arc::clone(&self.user_config), ctx.user_id.clone(), move |peer| async move { + let query = args["query"] + .as_str() + .ok_or_else(|| anyhow::anyhow!("honcho_search: missing 'query' argument"))? + .to_string(); + let ctx = client .peer_context( &workspace_id, - PEER_USER, + &peer, &PeerRepresentationGet { search_query: Some(query), search_top_k: Some(10), @@ -517,11 +575,12 @@ fn format_conclusions(ctx: &Value) -> Option { // ── HonchoContextTool ───────────────────────────────────────────────────────── -/// Retrieves a full context snapshot for the user (conclusions, card, summary) -/// from Honcho's `peer_context` endpoint. No LLM synthesis. +/// Retrieves a full context snapshot for the calling user (conclusions, card, +/// summary) from Honcho's `peer_context` endpoint. No LLM synthesis. struct HonchoContextTool { client: Arc, workspace_id: String, + user_config: Arc, } impl Tool for HonchoContextTool { @@ -548,16 +607,15 @@ impl Tool for HonchoContextTool { fn category(&self) -> ToolCategory { ToolCategory::Introspection } - fn execute(&self, args: Value) -> anyhow::Result { + fn run_with<'a>(&'a self, ctx: &ToolContext, args: Value) -> Box { let client = Arc::clone(&self.client); let workspace_id = self.workspace_id.clone(); let search_query = args.get("query").and_then(|v| v.as_str()).map(str::to_string); - - run_blocking(async move { + gated_execution(Arc::clone(&self.user_config), ctx.user_id.clone(), move |peer| async move { let ctx = client .peer_context( &workspace_id, - PEER_USER, + &peer, &PeerRepresentationGet { search_query, ..Default::default() }, ) .await @@ -570,16 +628,17 @@ impl Tool for HonchoContextTool { // ── HonchoConcludeTool ──────────────────────────────────────────────────────── -/// Writes or deletes a persistent fact (conclusion) about the user in Honcho's -/// memory. Exactly one of `conclusion` or `delete_id` must be supplied. +/// Writes or deletes a persistent fact (conclusion) about the calling user in +/// Honcho's memory. Exactly one of `conclusion` or `delete_id` must be supplied. /// -/// Written as `observer = user`, `observed = user` — matching this plugin's peer -/// model, where the `user` peer has `observe_me = true` and therefore holds the -/// self-knowledge that the read-path (`peer_context("user")`) reads back. Using -/// any other observer slot would store facts the read-path never sees. +/// Written as `observer = observed = ` — matching this plugin's peer +/// model, where the user's own peer has `observe_me = true` and therefore holds +/// the self-knowledge that the read-path (`peer_context(user_id)`) reads back. +/// Using any other observer slot would store facts the read-path never sees. struct HonchoConcludeTool { client: Arc, workspace_id: String, + user_config: Arc, } impl Tool for HonchoConcludeTool { @@ -609,21 +668,20 @@ impl Tool for HonchoConcludeTool { fn category(&self) -> ToolCategory { ToolCategory::Introspection } - fn execute(&self, args: Value) -> anyhow::Result { + fn run_with<'a>(&'a self, ctx: &ToolContext, args: Value) -> Box { + let client = Arc::clone(&self.client); + let workspace_id = self.workspace_id.clone(); let conclusion = args.get("conclusion").and_then(|v| v.as_str()) .map(str::trim).filter(|s| !s.is_empty()).map(str::to_string); let delete_id = args.get("delete_id").and_then(|v| v.as_str()) .map(str::trim).filter(|s| !s.is_empty()).map(str::to_string); - // Exactly one must be present (XOR). - if conclusion.is_some() == delete_id.is_some() { - anyhow::bail!("honcho_conclude: provide exactly one of 'conclusion' or 'delete_id'"); - } + gated_execution(Arc::clone(&self.user_config), ctx.user_id.clone(), move |peer| async move { + // Exactly one must be present (XOR). + if conclusion.is_some() == delete_id.is_some() { + anyhow::bail!("honcho_conclude: provide exactly one of 'conclusion' or 'delete_id'"); + } - let client = Arc::clone(&self.client); - let workspace_id = self.workspace_id.clone(); - - run_blocking(async move { if let Some(id) = delete_id { client .delete_conclusion(&workspace_id, &id) @@ -637,8 +695,8 @@ impl Tool for HonchoConcludeTool { &workspace_id, ConclusionCreate { content: content.clone(), - observer_id: PEER_USER.to_string(), - observed_id: PEER_USER.to_string(), + observer_id: peer.clone(), + observed_id: peer, session_id: None, }, ) @@ -724,8 +782,8 @@ impl core_api::plugin::Plugin for HonchoPlugin { fn id(&self) -> &str { PLUGIN_ID } fn name(&self) -> &str { "Honcho Memory" } fn description(&self) -> &str { - "Streams completed interactive chat turns to Honcho for long-term memory \ - and injects retrieved context into every LLM turn." + "Streams completed interactive chat turns of opted-in users to Honcho for \ + long-term memory and injects retrieved context into their LLM turns." } fn is_running(&self) -> bool { self.running.load(Ordering::Relaxed) } @@ -752,14 +810,35 @@ impl core_api::plugin::Plugin for HonchoPlugin { "workspace_id": { "type": "string", "title": "Workspace ID", - "description": "Honcho workspace identifier for this agent instance", - "default": "personal-agent" + "description": "Honcho workspace identifier for this instance (one shared workspace; each user is a separate peer inside it). Use a fresh name to start clean — the pre-multi-user data lived under a different workspace with a single shared peer.", + "default": "skald-circle" } }, "required": ["base_url", "workspace_id"] }) } + /// Per-user opt-in. Honcho stores conversations in cleartext on an external + /// server, so a user must knowingly enable it. A plain boolean — no secrets — + /// so the admin-readable `plugin_user_configs` store is an honest home. The + /// default `update_user_config` (store the blob) is exactly right; no override. + fn user_config_schema(&self) -> Value { + json!({ + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "title": "Enable long-term memory", + "description": "Let the assistant remember you across sessions. \ + Your messages will be stored in cleartext on the \ + Honcho memory server, outside your encrypted \ + database. Off unless you turn it on.", + "default": false + } + } + }) + } + fn as_any(&self) -> &dyn std::any::Any { self } fn as_arc_any(self: Arc) -> Arc { self } @@ -767,7 +846,7 @@ impl core_api::plugin::Plugin for HonchoPlugin { let new_cfg = HonchoConfig { base_url: config["base_url"].as_str().unwrap_or("http://localhost:8000").to_string(), api_key: config["api_key"].as_str().unwrap_or("").to_string(), - workspace_id: config["workspace_id"].as_str().unwrap_or("personal-agent").to_string(), + workspace_id: config["workspace_id"].as_str().unwrap_or("skald-circle").to_string(), }; let old_cfg = self.config.lock().await.clone(); @@ -808,8 +887,9 @@ impl core_api::plugin::Plugin for HonchoPlugin { let client = Arc::new(HonchoClient::with_base_url(&cfg.base_url, &cfg.api_key)); let workspace_id = cfg.workspace_id.clone(); + let user_config = Arc::clone(&ctx.user_config); - self.honcho_memory.activate(Arc::clone(&client), workspace_id.clone()); + self.honcho_memory.activate(Arc::clone(&client), workspace_id.clone(), Arc::clone(&user_config)); let session_map = Arc::clone(&self.honcho_memory.session_map); let mut rx = ctx.event_bus.subscribe(); @@ -834,7 +914,7 @@ impl core_api::plugin::Plugin for HonchoPlugin { Ok(BusEvent::UserMessage(event)) | Ok(BusEvent::AssistantResponse(event)) => { handle_event( - &client, &workspace_id, event, &session_map, + &client, &workspace_id, event, &session_map, &user_config, ).await; } Ok(BusEvent::CompactionDone(_)) => {} @@ -875,6 +955,9 @@ impl core_api::plugin::Plugin for HonchoPlugin { // ── Internal helpers ────────────────────────────────────────────────────────── +/// Creates the workspace and the single shared `assistant` peer. Per-user peers +/// (`peer_id = user_id`) are created lazily in [`get_or_create_session`] — the set +/// of users is not known here and grows over the instance's life. async fn ensure_workspace_ready(client: &HonchoClient, workspace_id: &str) { match client.create_workspace(&WorkspaceCreate { id: workspace_id.to_string(), @@ -885,15 +968,13 @@ async fn ensure_workspace_ready(client: &HonchoClient, workspace_id: &str) { Err(e) => warn!("honcho: workspace '{workspace_id}' create/check failed: {e}"), } - for peer_id in [PEER_USER, PEER_ASSISTANT] { - match client.create_peer(workspace_id, &PeerCreate { - id: peer_id.to_string(), - metadata: None, - configuration: None, - }).await { - Ok(_) => debug!("honcho: peer '{peer_id}' ready"), - Err(e) => debug!("honcho: peer '{peer_id}' create/check: {e} (likely already exists)"), - } + match client.create_peer(workspace_id, &PeerCreate { + id: PEER_ASSISTANT.to_string(), + metadata: None, + configuration: None, + }).await { + Ok(_) => debug!("honcho: peer '{PEER_ASSISTANT}' ready"), + Err(e) => debug!("honcho: peer '{PEER_ASSISTANT}' create/check: {e} (likely already exists)"), } } @@ -901,15 +982,25 @@ async fn handle_event( client: &HonchoClient, workspace_id: &str, event: ChatEvent, - session_map: &Arc>>, + session_map: &Arc>>, + user_config: &Arc, ) { if !event.is_interactive || event.is_ephemeral || event.is_synthetic { return; } - let peer_id = match event.role { - ChatEventRole::User => PEER_USER, - ChatEventRole::Assistant => PEER_ASSISTANT, + // Privacy gate: only forward turns for users who have opted in (§16). The + // assistant's reply is stored under the shared assistant peer but still only + // when *its user* has opted in — no opted-out user's conversation leaves the box. + if !opted_in(user_config, &event.user_id).await { + return; + } + + // The author peer: the user's own peer for their turns, the shared assistant + // peer (observe_me=false) for the reply. + let peer_id: String = match event.role { + ChatEventRole::User => event.user_id.clone(), + ChatEventRole::Assistant => PEER_ASSISTANT.to_string(), ChatEventRole::Agent => return, }; @@ -918,13 +1009,13 @@ async fn handle_event( } let honcho_session_id = match get_or_create_session( - client, workspace_id, event.session_id, session_map, + client, workspace_id, &event.user_id, event.session_id, session_map, ).await { Ok(id) => id, Err(e) => { warn!( - "honcho: failed to get/create session for local session {}: {e}", - event.session_id + "honcho: failed to get/create session for user {} local session {}: {e}", + event.user_id, event.session_id ); return; } @@ -932,7 +1023,7 @@ async fn handle_event( let msg = MessageCreate { content: event.content, - peer_id: peer_id.to_string(), + peer_id: peer_id.clone(), metadata: Some(json!({ "local_message_id": event.message_id, "local_stack_id": event.stack_id, @@ -954,44 +1045,62 @@ async fn handle_event( async fn get_or_create_session( client: &HonchoClient, workspace_id: &str, + user_id: &str, local_session_id: i64, - session_map: &Arc>>, + session_map: &Arc>>, ) -> Result { + let key = (user_id.to_string(), local_session_id); { let map = session_map.read().await; - if let Some(id) = map.get(&local_session_id) { + if let Some(id) = map.get(&key) { return Ok(id.clone()); } } + // Ensure the user's own peer exists (idempotent; the set of users grows over + // the instance's life so it can't be seeded up front). + if let Err(e) = client.create_peer(workspace_id, &PeerCreate { + id: user_id.to_string(), + metadata: None, + configuration: None, + }).await { + debug!("honcho: peer '{user_id}' create/check: {e} (likely already exists)"); + } + let mut peers = HashMap::new(); - peers.insert(PEER_USER.to_string(), SessionPeerConfig { - observe_others: None, + // The user's own peer: Honcho builds their long-term profile (observe_me). + peers.insert(user_id.to_string(), SessionPeerConfig { + observe_others: Some(false), observe_me: Some(true), }); + // The shared assistant peer: observe_me=false so NO global representation of + // the assistant is built — it would otherwise blend every user's private + // facts (restated in the assistant's replies) into one cross-user store. peers.insert(PEER_ASSISTANT.to_string(), SessionPeerConfig { - observe_me: Some(true), - observe_others: None, + observe_me: Some(false), + observe_others: Some(false), }); - // Use a deterministic id so the mapping survives plugin restarts without - // needing a DB column — same local_session_id always maps to the same - // Honcho session. Honcho v3 requires `id` in the creation body. - let honcho_id = format!("{workspace_id}-{local_session_id}"); + // Deterministic, user-namespaced id so the mapping survives plugin restarts + // without a DB column — the same (user, local_session_id) always maps to the + // same Honcho session. Honcho v3 requires `id` in the creation body. + let honcho_id = honcho_session_id(workspace_id, user_id, local_session_id); let session = client.create_session(workspace_id, &SessionCreate { id: Some(honcho_id), - metadata: Some(json!({ "local_session_id": local_session_id })), + metadata: Some(json!({ + "local_session_id": local_session_id, + "user_id": user_id, + })), peers: Some(peers), configuration: None, }).await?; info!( - "honcho: created session {} for local session {local_session_id}", + "honcho: created session {} for user {user_id} local session {local_session_id}", session.id ); let mut map = session_map.write().await; - map.entry(local_session_id).or_insert(session.id.clone()); - Ok(map[&local_session_id].clone()) + Ok(map.entry(key).or_insert(session.id).clone()) } diff --git a/crates/plugin-mobile-connector/src/app.rs b/crates/plugin-mobile-connector/src/app.rs index 763a998..25d3900 100644 --- a/crates/plugin-mobile-connector/src/app.rs +++ b/crates/plugin-mobile-connector/src/app.rs @@ -52,6 +52,11 @@ pub struct RelayApp { pub(crate) forwarders: Mutex>, /// Per-user debounced notifiers, created on demand by the forwarders. pub(crate) notifiers: Mutex>>, + /// The user a device paired *during the current window* auto-binds to — set + /// by the web pairing console (the admin who opened the window). `None` for + /// the agent-tool flow (`mobile_start_pairing`), which leaves the device + /// Pending for an explicit `mobile_bind_device`. Cleared on stop-pairing. + pending_owner: Mutex>, } impl RelayApp { @@ -74,9 +79,22 @@ impl RelayApp { cancel, forwarders: Mutex::new(HashSet::new()), notifiers: Mutex::new(HashMap::new()), + pending_owner: Mutex::new(None), }) } + /// Set (or clear) the user that devices paired during the current window + /// auto-bind to. Called by the web pairing endpoint with the admin's id. + pub(crate) async fn set_pending_owner(&self, user_id: Option) { + *self.pending_owner.lock().await = user_id; + } + + /// The user devices should auto-bind to while a web-console pairing window + /// is open, if any. + pub(crate) async fn pending_owner(&self) -> Option { + self.pending_owner.lock().await.clone() + } + /// The underlying transport client (used by the `RelayAgent` impl + router). pub fn client(&self) -> &Arc { &self.client @@ -194,6 +212,44 @@ impl RelayApp { // ── Devices → Inbox ─────────────────────────────────────────────────────── + /// Seal and send a single payload to one device (best-effort; a send failure + /// is logged, never propagated). + async fn send_to_device(&self, device: &[u8; 32], payload: &serde_json::Value) { + match serde_json::to_vec(payload) { + Ok(bytes) => { + if let Err(e) = self.client.send(device, &bytes, true).await { + warn!(plugin = PLUGIN_ID, error = %e, "failed to send payload to device"); + } + } + Err(e) => warn!(plugin = PLUGIN_ID, error = %e, "failed to serialize device payload"), + } + } + + /// Self-service device binding (blueprint §13): resolve the presented web + /// session token to a user and bind this device to them, then reply with a + /// `bind_result`. An invalid/expired token yields `ok=false` so the app + /// prompts the user to sign in again. The token is a bearer credential — + /// never logged. + async fn handle_bind_request(&self, from: &[u8; 32], session_token: &str) { + match self.user_channel.user_for_session(session_token).await { + Some(user_id) => match self.bind_device(*from, user_id.clone(), None).await { + Ok(()) => { + info!(plugin = PLUGIN_ID, user_id = %user_id, device = %hex::encode(from), + "device self-bound via session token"); + self.send_to_device(from, &payloads::build_bind_result(true, Some(&user_id), None)).await; + } + Err(e) => { + warn!(plugin = PLUGIN_ID, error = %e, "self-bind failed"); + self.send_to_device(from, &payloads::build_bind_result(false, None, Some(&e.to_string()))).await; + } + }, + None => { + debug!(plugin = PLUGIN_ID, device = %hex::encode(from), "bind_request with invalid/expired session"); + self.send_to_device(from, &payloads::build_bind_result(false, None, Some("invalid or expired session"))).await; + } + } + } + /// Apply a decoded client payload to the sending device's *user's* Inbox. /// Unbound device or locked user → the request is ignored (no cross-user leak). async fn apply_client_payload(&self, from: &[u8; 32], payload: &[u8]) { @@ -213,6 +269,12 @@ impl RelayApp { } return; } + // Self-service binding resolves its own user from the token — it must + // NOT go through `user_for_device` (the device is not bound yet). + ClientPayload::BindRequest { session_token } => { + self.handle_bind_request(from, session_token).await; + return; + } ClientPayload::Unknown => { debug!(plugin = PLUGIN_ID, "unknown/ignored client payload"); return; @@ -226,7 +288,10 @@ impl RelayApp { return; }; let Some(handle) = self.user_channel.resolve_user(&user_id).await else { - debug!(plugin = PLUGIN_ID, user_id = %user_id, "payload dropped — user locked"); + // Locked (§9): tell the app to run the login/unlock handshake rather + // than silently dropping — the request is lost, but the app knows why. + debug!(plugin = PLUGIN_ID, user_id = %user_id, "user locked — signalling needs_unlock"); + self.send_to_device(from, &payloads::build_needs_unlock()).await; return; }; let inbox = handle.inbox(); @@ -255,8 +320,11 @@ impl RelayApp { warn!(plugin = PLUGIN_ID, error = %e, "failed to send targeted inbox snapshot"); } } - // Handled above. - ClientPayload::Hello { .. } | ClientPayload::Logout | ClientPayload::Unknown => {} + // Handled above (device-registry ops that return before this match). + ClientPayload::Hello { .. } + | ClientPayload::Logout + | ClientPayload::BindRequest { .. } + | ClientPayload::Unknown => {} } } @@ -282,19 +350,34 @@ impl RelayApp { self.apply_client_payload(&from, &payload).await; } Ok(RelayEvent::ClientPaired { ed25519_pub, .. }) => { - // The device is not bound to any user yet, so there is no - // one to push to. An admin binds it with `mobile_bind_device` - // (which authorizes it). We only optionally pre-authorize. - if !self.require_device_confirmation { - if let Err(e) = self.client.authorize(&ed25519_pub).await { - warn!(plugin = PLUGIN_ID, error = %e, "auto-authorize failed"); + // Web-console pairing: the admin who opened the window is + // the pending owner, so bind (and thereby authorize) the + // device to them straight away — usable on the phone at + // once, reassignable later from the Devices page. + if let Some(owner) = self.pending_owner().await { + match self.bind_device(ed25519_pub, owner.clone(), None).await { + Ok(()) => info!( + plugin = PLUGIN_ID, user_id = %owner, + device = %hex::encode(ed25519_pub), + "new device paired — auto-bound to pairing admin" + ), + Err(e) => warn!(plugin = PLUGIN_ID, error = %e, "auto-bind on pair failed"), } + } else { + // Agent-tool flow: no owner set. The device stays + // Pending for an explicit `mobile_bind_device`; only + // optionally pre-authorize per config. + if !self.require_device_confirmation { + if let Err(e) = self.client.authorize(&ed25519_pub).await { + warn!(plugin = PLUGIN_ID, error = %e, "auto-authorize failed"); + } + } + info!( + plugin = PLUGIN_ID, + device = %hex::encode(ed25519_pub), + "new device paired — awaiting admin binding (mobile_bind_device)" + ); } - info!( - plugin = PLUGIN_ID, - device = %hex::encode(ed25519_pub), - "new device paired — awaiting admin binding (mobile_bind_device)" - ); } Ok(RelayEvent::ClientRevoked { .. }) | Ok(RelayEvent::Connected) diff --git a/crates/plugin-mobile-connector/src/lib.rs b/crates/plugin-mobile-connector/src/lib.rs index 7069cff..dd6a143 100644 --- a/crates/plugin-mobile-connector/src/lib.rs +++ b/crates/plugin-mobile-connector/src/lib.rs @@ -48,7 +48,7 @@ use tokio::task::JoinHandle; use tokio_util::sync::CancellationToken; use tracing::{info, warn}; -use core_api::plugin::{Plugin, PluginContext}; +use core_api::plugin::{Plugin, PluginContext, PluginPage}; use skald_relay_client::{ClientState as RelayClientState, RelayClient, RelayClientConfig, SeedSource}; pub use agent::{ClientInfo, ClientState, PairingHandle, RelayAgent}; @@ -232,6 +232,10 @@ impl Plugin for MobileConnectorPlugin { } fn is_running(&self) -> bool { self.running.load(Ordering::Relaxed) } + /// Access is the device→user binding (§13), not a `plugin_access` grant, so + /// the admin Plugins UI hides the "User access" checklist for this plugin. + fn manages_own_access(&self) -> bool { true } + fn config_schema(&self) -> Value { json!({ "type": "object", @@ -305,6 +309,29 @@ impl Plugin for MobileConnectorPlugin { Some(router::build(Arc::clone(&self.inner))) } + /// Two admin-only console pages served from this plugin's own router + /// (`web/*.js`). `manages_own_access` already hides them from non-admins. + fn web_pages(&self) -> Vec { + vec![ + PluginPage { + page_id: "pairing", + title: "Pair a device".into(), + icon: "qr-code", + entry: "web/pairing.js".into(), + admin_only: true, + priority: 10, + }, + PluginPage { + page_id: "devices", + title: "Mobile devices".into(), + icon: "phone", + entry: "web/devices.js".into(), + admin_only: true, + priority: 20, + }, + ] + } + /// Control tools (plugin.md §11). They close over the plugin itself as a /// `RelayAgent` and call into it lazily, so building them before the runloop /// starts is fine — they fail gracefully while it is stopped. diff --git a/crates/plugin-mobile-connector/src/payloads.rs b/crates/plugin-mobile-connector/src/payloads.rs index 77bb328..883e498 100644 --- a/crates/plugin-mobile-connector/src/payloads.rs +++ b/crates/plugin-mobile-connector/src/payloads.rs @@ -118,6 +118,35 @@ pub fn build_notification(title: &str, body: &str) -> Value { }) } +/// Build a `bind_result` payload — the agent's reply to a `bind_request` +/// (self-service device binding). `ok=true` carries the bound `user`; `ok=false` +/// carries an `error` string (invalid/expired session, bind failure). The device +/// uses it to confirm the pairing or to prompt the user to sign in again. +pub fn build_bind_result(ok: bool, user: Option<&str>, error: Option<&str>) -> Value { + serde_json::json!({ + "v": 1, + "kind": "bind_result", + "id": new_id(), + "ts": Utc::now().timestamp_millis(), + "ok": ok, + "user": user, // Option<&str> → null when absent + "error": error, + }) +} + +/// Build a `needs_unlock` payload — sent when a device acts for a user whose +/// database is locked (§9). It tells the app to run the login/unlock handshake +/// (`POST /api/auth/login` through the loopback proxy) rather than treating the +/// dropped request as a hard failure. +pub fn build_needs_unlock() -> Value { + serde_json::json!({ + "v": 1, + "kind": "needs_unlock", + "id": new_id(), + "ts": Utc::now().timestamp_millis(), + }) +} + // ── Client → Agent ────────────────────────────────────────────────────────── /// A decoded client→agent payload (payloads.md §4). Only the fields the agent @@ -138,6 +167,11 @@ pub enum ClientPayload { /// §4.6). Sent after every `auth_ok`; the agent replies with a targeted /// `inbox_update`. No fields beyond the common envelope. InboxRequest, + /// `bind_request`: self-service device binding. The device presents the + /// `session_token` it obtained from `POST /api/auth/login`; the agent + /// resolves it to a user and binds this device's pubkey to them (no admin + /// step). The token is a bearer credential — never log it. + BindRequest { session_token: String }, /// `logout`: device removes itself. Logout, /// Anything else (ack, unknown kind, malformed request_id) — ignored. @@ -191,6 +225,10 @@ pub fn parse_client_payload(plaintext: &[u8]) -> ClientPayload { ClientPayload::ElicitationResponse { request_id: rid, action, content } } "inbox_request" => ClientPayload::InboxRequest, + "bind_request" => match v.get("session_token").and_then(Value::as_str) { + Some(tok) if !tok.is_empty() => ClientPayload::BindRequest { session_token: tok.to_string() }, + _ => ClientPayload::Unknown, + }, "logout" => ClientPayload::Logout, _ => ClientPayload::Unknown, } @@ -273,4 +311,49 @@ mod tests { }"#; assert!(matches!(parse_client_payload(raw), ClientPayload::Unknown)); } + + /// `bind_request` carries the session token the device logged in with. + #[test] + fn bind_request_parses_session_token() { + let raw = br#"{ + "v": 1, "kind": "bind_request", "id": "abc", "ts": 1750000000000, + "session_token": "tok-123" + }"#; + match parse_client_payload(raw) { + ClientPayload::BindRequest { session_token } => assert_eq!(session_token, "tok-123"), + other => panic!("expected BindRequest, got {other:?}"), + } + } + + /// A missing or empty `session_token` is rejected as `Unknown` (never binds). + #[test] + fn bind_request_missing_or_empty_token_is_unknown() { + let missing = br#"{ "v": 1, "kind": "bind_request", "id": "a", "ts": 1 }"#; + let empty = br#"{ "v": 1, "kind": "bind_request", "id": "a", "ts": 1, "session_token": "" }"#; + assert!(matches!(parse_client_payload(missing), ClientPayload::Unknown)); + assert!(matches!(parse_client_payload(empty), ClientPayload::Unknown)); + } + + /// `bind_result` shape: ok carries the user; failure carries the error. + #[test] + fn bind_result_shape() { + let ok = build_bind_result(true, Some("u1"), None); + assert_eq!(ok["kind"], "bind_result"); + assert_eq!(ok["ok"], true); + assert_eq!(ok["user"], "u1"); + assert!(ok["error"].is_null()); + + let err = build_bind_result(false, None, Some("invalid or expired session")); + assert_eq!(err["ok"], false); + assert!(err["user"].is_null()); + assert_eq!(err["error"], "invalid or expired session"); + } + + /// `needs_unlock` is a bare envelope the app reacts to by (re)logging in. + #[test] + fn needs_unlock_shape() { + let p = build_needs_unlock(); + assert_eq!(p["kind"], "needs_unlock"); + assert_eq!(p["v"], 1); + } } diff --git a/crates/plugin-mobile-connector/src/router.rs b/crates/plugin-mobile-connector/src/router.rs index 5a65613..c175892 100644 --- a/crates/plugin-mobile-connector/src/router.rs +++ b/crates/plugin-mobile-connector/src/router.rs @@ -1,47 +1,243 @@ -//! The single HTTP route the plugin contributes: the runtime QR-code endpoint -//! (plugin.md §5). Mounted by the main `WebFrontend` under -//! `/api/plugin/mobile-connector/` behind Skald's normal auth. No QR is ever -//! written to disk — the PNG is rendered on demand from the in-memory session. +//! The plugin's HTTP surface, mounted by the main `WebFrontend` under +//! `/api/plugin/mobile-connector/` behind Skald's normal auth + enabled-gate. //! -//! The router receives the plugin's shared state cell -//! (`Arc>>>`) so that every request resolves the -//! **current** `RelayState` — the same one the LLM tools use. This avoids the -//! classic stale-Arc bug when the plugin is reconfigured (reload stops the old -//! runloop + creates a fresh `RelayState`, but the router is only built once). +//! Two audiences on one router: +//! - the **QR endpoint** (`/pairingqrcode`) — renders the pairing QR PNG on +//! demand from the in-memory session (no QR ever touches disk); +//! - the **admin pairing console** — the JSON API + the two page fragments +//! (`web/pairing.js`, `web/devices.js`) that let an admin pair, list, bind and +//! revoke devices from the browser instead of driving the LLM control tools. +//! +//! Every request resolves the *current* [`RelayApp`] through the shared state +//! cell (`Arc>>>`), so a reconfigure (reload → fresh +//! `RelayApp`) is transparent. Management endpoints are admin-only: the router +//! runs inside `require_auth` (which injects [`Caller`]) and gates on +//! [`UserChannelApi::plugin_access`], which — because the connector +//! `manages_own_access` — returns `true` only for admins. use std::sync::Arc; -use axum::extract::{Query, State}; +use axum::extract::{Extension, Query, State}; use axum::http::{header, StatusCode}; -use axum::response::IntoResponse; -use axum::routing::get; -use axum::Router; +use axum::response::{IntoResponse, Response}; +use axum::routing::{get, post}; +use axum::{Json, Router}; use serde::Deserialize; +use serde_json::{json, Value}; use tokio::sync::Mutex; -use skald_relay_client::SessionState; +use core_api::plugin::Caller; +use skald_relay_client::{ClientState, SessionState}; use crate::app::RelayApp; +use crate::PLUGIN_ID; /// Shared cell type: an `Arc` to a `Mutex` holding the (optional) live app. /// Cloned cheaply and safely shared between the plugin and the router. type StateCell = Arc>>>; +/// Build the plugin's router. Takes the shared state cell so each request +/// resolves the *current* `RelayApp` — not a snapshot from startup. +pub fn build(state_cell: StateCell) -> Router { + Router::new() + .route("/pairingqrcode", get(pairing_qr)) + // Page fragments (served as ES modules to the browser). + .route("/web/pairing.js", get(|| async { serve_js(include_str!("../web/pairing.js")) })) + .route("/web/devices.js", get(|| async { serve_js(include_str!("../web/devices.js")) })) + .route("/web/common.js", get(|| async { serve_js(include_str!("../web/common.js")) })) + // Admin pairing console API. + .route("/pairing", post(start_pairing).delete(stop_pairing)) + .route("/devices", get(list_devices)) + .route("/devices/bind", post(bind_device)) + .route("/devices/revoke", post(revoke_device)) + .with_state(state_cell) +} + +// ── Admin console: shared plumbing ────────────────────────────────────────────── + +/// Resolve the live app, or `503` when the plugin is enabled but its runloop is +/// not up (e.g. no `relay_url` configured). +async fn app_or_503(cell: &StateCell) -> Result, Response> { + cell.lock().await.as_ref().map(Arc::clone).ok_or_else(|| { + (StatusCode::SERVICE_UNAVAILABLE, "mobile connector is not running").into_response() + }) +} + +/// Fail-closed admin gate. For a `manages_own_access` connector nobody holds a +/// `plugin_access` grant, so this is `true` only for the built-in admin role. +async fn require_admin(app: &RelayApp, caller: &Caller) -> Result<(), Response> { + if app.user_channel.plugin_access(PLUGIN_ID, &caller.user_id).await { + Ok(()) + } else { + Err((StatusCode::FORBIDDEN, "admin only").into_response()) + } +} + +/// Resolve the app and check admin in one step (the common prelude). +async fn admin_app(cell: &StateCell, caller: &Caller) -> Result, Response> { + let app = app_or_503(cell).await?; + require_admin(&app, caller).await?; + Ok(app) +} + +fn bad_request(msg: impl Into) -> Response { + (StatusCode::BAD_REQUEST, msg.into()).into_response() +} + +fn decode_pubkey(hex: &str) -> Result<[u8; 32], Response> { + skald_relay_common::crypto::decode_hex::<32>(hex) + .ok_or_else(|| bad_request("`pubkey` must be 32-byte hex")) +} + +// ── POST/DELETE /pairing ──────────────────────────────────────────────────────── + +#[derive(Deserialize)] +struct StartPairingBody { + /// Window lifetime in seconds; `0`/absent = the configured default, capped at 600. + #[serde(default)] + ttl: Option, +} + +/// Open a pairing window and return the QR URL. The caller (an admin) becomes +/// the pending owner, so a device that pairs in this window auto-binds to them. +async fn start_pairing( + State(cell): State, + Extension(caller): Extension, + Json(body): Json, +) -> Response { + let app = match admin_app(&cell, &caller).await { Ok(a) => a, Err(r) => return r }; + // Pairing brokers through the relay: without a live WS there is no channel to + // send `pairing_start` on ("WS outbound channel closed"). Fail with an + // actionable message instead of the transport-level one. + if !app.client().is_connected() { + return ( + StatusCode::SERVICE_UNAVAILABLE, + "Relay not connected. Set the connector's relay_url and make sure the relay is reachable, then try again.", + ) + .into_response(); + } + let ttl = body.ttl.unwrap_or(0).min(600); + app.set_pending_owner(Some(caller.user_id.clone())).await; + match app.client().start_pairing(ttl).await { + Ok(started) => Json(json!({ + "url": format!("/api/plugin/{PLUGIN_ID}/pairingqrcode?code={}", started.code), + "code": started.code, + "expires_at": started.expires_at, + })) + .into_response(), + Err(e) => { + app.set_pending_owner(None).await; + (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response() + } + } +} + +/// Close the pairing window and disarm auto-binding. +async fn stop_pairing( + State(cell): State, + Extension(caller): Extension, +) -> Response { + let app = match admin_app(&cell, &caller).await { Ok(a) => a, Err(r) => return r }; + app.set_pending_owner(None).await; + match app.client().stop_pairing().await { + Ok(()) => StatusCode::NO_CONTENT.into_response(), + Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), + } +} + +// ── GET /devices ──────────────────────────────────────────────────────────────── + +/// List every known device, each tagged with its bound user, state and metadata. +async fn list_devices( + State(cell): State, + Extension(caller): Extension, +) -> Response { + let app = match admin_app(&cell, &caller).await { Ok(a) => a, Err(r) => return r }; + let rows = app.client().list_clients().await; + let bindings = app.bindings.read().await; + let devices: Vec = rows + .into_iter() + .map(|r| { + let pk_hex = hex::encode(r.ed25519_pub); + let bound_user = bindings.user_for_pubkey(&pk_hex); + let device_info: Option = + r.device_info.as_deref().and_then(|s| serde_json::from_str(s).ok()); + json!({ + "pubkey": pk_hex, + "state": if r.state == ClientState::Authorized { "authorized" } else { "pending" }, + "bound_user": bound_user, + "platform": r.platform, + "device_info": device_info, + "last_seen": r.last_seen, + }) + }) + .collect(); + Json(json!({ "devices": devices })).into_response() +} + +// ── POST /devices/bind + /devices/revoke ──────────────────────────────────────── + +#[derive(Deserialize)] +struct BindBody { + pubkey: String, + user_id: String, + #[serde(default)] + display: Option, +} + +/// Bind (or reassign) a device to a user and authorize it. +async fn bind_device( + State(cell): State, + Extension(caller): Extension, + Json(body): Json, +) -> Response { + let app = match admin_app(&cell, &caller).await { Ok(a) => a, Err(r) => return r }; + let pk = match decode_pubkey(&body.pubkey) { Ok(p) => p, Err(r) => return r }; + if body.user_id.trim().is_empty() { + return bad_request("`user_id` must not be empty"); + } + match app.bind_device(pk, body.user_id, body.display).await { + Ok(()) => StatusCode::NO_CONTENT.into_response(), + Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), + } +} + +#[derive(Deserialize)] +struct RevokeBody { + pubkey: String, +} + +/// Revoke a device and drop its binding. +async fn revoke_device( + State(cell): State, + Extension(caller): Extension, + Json(body): Json, +) -> Response { + let app = match admin_app(&cell, &caller).await { Ok(a) => a, Err(r) => return r }; + let pk = match decode_pubkey(&body.pubkey) { Ok(p) => p, Err(r) => return r }; + match app.revoke_device(pk).await { + Ok(()) => StatusCode::NO_CONTENT.into_response(), + Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), + } +} + +// ── Static fragment serving ───────────────────────────────────────────────────── + +/// Serve an embedded ES module as `text/javascript`. The shell already adds +/// `Cache-Control: no-cache`, so a rebuilt fragment is never served stale. +fn serve_js(body: &'static str) -> Response { + ([(header::CONTENT_TYPE, "text/javascript; charset=utf-8")], body).into_response() +} + +// ── QR endpoint (unchanged) ───────────────────────────────────────────────────── + #[derive(Deserialize)] struct QrQuery { code: Option, } -/// Build the plugin's router. Takes the shared state cell so each request -/// resolves the *current* `RelayState` — not a snapshot from startup. -pub fn build(state_cell: StateCell) -> Router { - Router::new() - .route("/pairingqrcode", get(pairing_qr)) - .with_state(state_cell) -} - /// `GET /pairingqrcode?code=` → PNG of the QR while active, else a -/// placeholder PNG (plugin.md §5 table). +/// placeholder PNG. async fn pairing_qr( State(cell): State, Query(q): Query, @@ -50,23 +246,19 @@ async fn pairing_qr( return png_response(render_placeholder("QR non valido")); }; - // Dynamically resolve the *current* RelayApp (same one tools use). let app = match cell.lock().await.as_ref() { Some(s) => Arc::clone(s), None => return png_response(render_placeholder("Plugin non attivo")), }; match app.client().lookup_pairing(&code) { - Some((qr, SessionState::Active)) => { - // Encode the normative QrCodeData JSON into the QR. - match serde_json::to_string(&qr) { - Ok(json) => match render_qr(&json) { - Ok(png) => png_response(png), - Err(_) => png_response(render_placeholder("QR error")), - }, + Some((qr, SessionState::Active)) => match serde_json::to_string(&qr) { + Ok(json) => match render_qr(&json) { + Ok(png) => png_response(png), Err(_) => png_response(render_placeholder("QR error")), - } - } + }, + Err(_) => png_response(render_placeholder("QR error")), + }, Some((_, SessionState::Consumed)) => png_response(render_placeholder("QR already used")), Some((_, SessionState::Superseded)) => png_response(render_placeholder("QR expired")), None => png_response(render_placeholder("QR expired")), diff --git a/crates/plugin-mobile-connector/web/common.js b/crates/plugin-mobile-connector/web/common.js new file mode 100644 index 0000000..85a61b5 --- /dev/null +++ b/crates/plugin-mobile-connector/web/common.js @@ -0,0 +1,51 @@ +// Shared helpers for the mobile-connector console fragments. +// +// Served at `/api/plugin/mobile-connector/web/common.js` and imported by the +// two page fragments via a relative `./common.js` specifier. Everything the +// fragments need is self-contained here — the host injects no APIs (see +// `Plugin::web_pages` contract): they talk only to `/api/plugin//…` and, +// for the user directory used by the reassign dropdown, the host `/api/users` +// (the fragment runs with the logged-in admin's full session privileges). +import { LitElement } from 'lit'; + +/// JSON fetch that throws the server's error text on non-2xx and tolerates an +/// empty (204) body. +export async function jf(url, opts = {}) { + const res = await fetch(url, { + headers: { 'Content-Type': 'application/json', ...(opts.headers || {}) }, + ...opts, + }); + if (!res.ok) { + const txt = await res.text().catch(() => ''); + throw new Error(txt || `HTTP ${res.status}`); + } + if (res.status === 204) return null; + const ct = res.headers.get('content-type') || ''; + return ct.includes('application/json') ? res.json() : res.text(); +} + +/// Base for the console fragments: renders into light DOM (so Bootstrap classes +/// and the app's theme CSS variables apply) and exposes the plugin's API root +/// from the host-set `plugin-id` attribute. +export class MobileBase extends LitElement { + createRenderRoot() { return this; } + get api() { return `/api/plugin/${this.getAttribute('plugin-id') || 'mobile-connector'}`; } +} + +/// Human-friendly "time ago" for a Unix-ms timestamp (or "—" when absent). +export function ago(ms) { + if (!ms) return '—'; + const s = Math.max(0, Math.floor((Date.now() - ms) / 1000)); + if (s < 60) return `${s}s ago`; + const m = Math.floor(s / 60); + if (m < 60) return `${m}m ago`; + const h = Math.floor(m / 60); + if (h < 24) return `${h}h ago`; + return `${Math.floor(h / 24)}d ago`; +} + +/// Best-effort device label from the `device_info` JSON a phone sends on hello. +export function deviceLabel(d) { + const info = d.device_info || {}; + return info.name || info.model || info.device || d.platform || 'Unknown device'; +} diff --git a/crates/plugin-mobile-connector/web/devices.js b/crates/plugin-mobile-connector/web/devices.js new file mode 100644 index 0000000..49695b5 --- /dev/null +++ b/crates/plugin-mobile-connector/web/devices.js @@ -0,0 +1,143 @@ +// Mobile-connector "Mobile devices" console (page_id `devices`). +// +// Lists every paired device with its state and bound user, and lets an admin +// reassign a device to another user (`POST /devices/bind`) or revoke it +// (`POST /devices/revoke`). The user directory for the reassign dropdown comes +// from the host `/api/users` (the fragment runs with the admin's session). +// Default-exports the element class; the host registers it. +import { html, nothing } from 'lit'; +import { MobileBase, jf, ago, deviceLabel } from './common.js'; + +export default class MobileDevicesPage extends MobileBase { + static get properties() { + return { + _devices: { state: true }, // [] | null (loading) + _users: { state: true }, // [{id, username, display_name}] + _error: { state: true }, + _pick: { state: true }, // { [pubkey]: user_id } reassign selections + }; + } + + constructor() { + super(); + this._devices = null; + this._users = []; + this._error = null; + this._pick = {}; + this._poll = null; + } + + connectedCallback() { + super.connectedCallback(); + this._load(); + this._poll = setInterval(() => this._load(true), 5000); + } + + disconnectedCallback() { + super.disconnectedCallback(); + if (this._poll) { clearInterval(this._poll); this._poll = null; } + } + + async _load(quiet = false) { + if (!quiet) this._error = null; + try { + const [d, u] = await Promise.all([ + jf(`${this.api}/devices`), + this._users.length ? Promise.resolve({ list: this._users }) : jf('/api/users').then(list => ({ list })), + ]); + this._devices = d.devices || []; + if (u.list) this._users = u.list; + } catch (e) { + if (!quiet) this._error = e.message; + } + } + + _userName(id) { + const u = this._users.find(x => x.id === id); + return u ? (u.display_name || u.username) : id; + } + + async _bind(pubkey) { + const user_id = this._pick[pubkey]; + if (!user_id) return; + try { + await jf(`${this.api}/devices/bind`, { method: 'POST', body: JSON.stringify({ pubkey, user_id }) }); + await this._load(); + } catch (e) { this._error = e.message; } + } + + async _revoke(pubkey) { + if (!confirm('Revoke this device? It loses access immediately.')) return; + try { + await jf(`${this.api}/devices/revoke`, { method: 'POST', body: JSON.stringify({ pubkey }) }); + await this._load(); + } catch (e) { this._error = e.message; } + } + + render() { + const loading = this._devices === null && !this._error; + return html` +
+
+

Mobile devices

+ +
+
+ ${this._error ? html`
${this._error}
` : nothing} + ${loading ? html`
Loading…
` : this._renderList()} +
+
`; + } + + _renderList() { + const rows = this._devices || []; + if (!rows.length) { + return html`
+

No paired devices yet.

+

Use the Pair a device page to add one.

+
`; + } + return html` +
+ + + + + ${rows.map(d => this._renderRow(d))} +
DeviceStateBound toLast seenActions
+
`; + } + + _renderRow(d) { + const authorized = d.state === 'authorized'; + return html` + + +
${deviceLabel(d)}
+
+ ${d.pubkey.slice(0, 16)}…
+ + + ${d.state} + + ${d.bound_user ? this._userName(d.bound_user) : html``} + ${ago(d.last_seen)} + +
+ + + +
+ + `; + } +} diff --git a/crates/plugin-mobile-connector/web/pairing.js b/crates/plugin-mobile-connector/web/pairing.js new file mode 100644 index 0000000..263ad8d --- /dev/null +++ b/crates/plugin-mobile-connector/web/pairing.js @@ -0,0 +1,112 @@ +// Mobile-connector "Pair a device" console (page_id `pairing`). +// +// Opens a pairing window on the plugin (`POST /pairing`), shows the QR the phone +// scans, and counts down to expiry. A device that pairs in this window is +// auto-bound to the admin who opened it (server-side, on `ClientPaired`) — so it +// is usable on the phone immediately and can be reassigned later from the +// Devices page. Default-exports the element class; the host registers it. +import { html, nothing } from 'lit'; +import { MobileBase, jf } from './common.js'; + +export default class MobilePairingPage extends MobileBase { + static get properties() { + return { + _session: { state: true }, // { url, code, expires_at } | null + _remain: { state: true }, // seconds until expiry + _busy: { state: true }, + _error: { state: true }, + }; + } + + constructor() { + super(); + this._session = null; + this._remain = 0; + this._busy = false; + this._error = null; + this._timer = null; + } + + disconnectedCallback() { + super.disconnectedCallback(); + this._stopTimer(); + // Best-effort close so a forgotten window does not linger. + if (this._session) jf(`${this.api}/pairing`, { method: 'DELETE' }).catch(() => {}); + } + + _stopTimer() { if (this._timer) { clearInterval(this._timer); this._timer = null; } } + + _startTimer() { + this._stopTimer(); + const tick = () => { + const remain = Math.max(0, Math.round((this._session.expires_at - Date.now()) / 1000)); + this._remain = remain; + if (remain <= 0) { this._stopTimer(); } + }; + tick(); + this._timer = setInterval(tick, 1000); + } + + async _open() { + this._busy = true; + this._error = null; + try { + this._session = await jf(`${this.api}/pairing`, { method: 'POST', body: JSON.stringify({}) }); + this._startTimer(); + } catch (e) { + this._error = e.message; + this._session = null; + } finally { + this._busy = false; + } + } + + async _stop() { + this._stopTimer(); + const had = this._session; + this._session = null; + if (had) { try { await jf(`${this.api}/pairing`, { method: 'DELETE' }); } catch { /* ignore */ } } + } + + render() { + const expired = this._session && this._remain <= 0; + return html` +
+
+

Pair a device

+
+
+ ${this._error ? html`
${this._error}
` : nothing} + + ${!this._session ? html` +

+ Open a pairing window, then scan the QR code with the Skald mobile app. + The device is linked to you and works immediately — you can + reassign it to another user from the Mobile devices page. +

+ + ` : html` +
+ Pairing QR + ${expired + ? html`
Window expired
` + : html`
+ Scan within ${this._remain}s +
`} +
+ ${expired + ? html`` + : html``} +
+
+ `} +
+
`; + } +} diff --git a/crates/plugin-telegram-bot/src/auth.rs b/crates/plugin-telegram-bot/src/auth.rs index be82a31..7c743f6 100644 --- a/crates/plugin-telegram-bot/src/auth.rs +++ b/crates/plugin-telegram-bot/src/auth.rs @@ -110,7 +110,8 @@ pub(crate) async fn handle_pairing(bot: &Bot, chat_id: ChatId, shared: &ArcPairing required.\n\n\ Code: {code}\n\n\ - Ask the admin to authorize this chat using the telegram_pairing tool.", + Open the Plugins page in the Skald web app and paste this code \ + to link your account (or ask the admin).", ), ) .parse_mode(ParseMode::Html) @@ -124,6 +125,29 @@ pub(crate) fn generate_code() -> String { (0..6).map(|_| CHARS[rng.random_range(0..CHARS.len())] as char).collect() } +/// Turns a pairing code into a binding for `user_id` (the web self-service +/// flow — `Plugin::update_user_config`). Mirrors the `telegram_pairing` tool's +/// bind semantics: the pending entry is consumed and any existing binding for +/// that chat is replaced. Returns the bound `chat_id`. +pub(crate) fn apply_pairing_code( + cfg: &mut TelegramConfig, + code: &str, + user_id: &str, +) -> anyhow::Result { + let code = code.trim(); + let pos = cfg.pending_pairings.iter() + .position(|e| e.code.eq_ignore_ascii_case(code)) + .ok_or_else(|| anyhow::anyhow!("invalid or expired pairing code — send a message to the bot to get a new one"))?; + let chat_id = cfg.pending_pairings.remove(pos).chat_id; + cfg.bindings.retain(|b| b.chat_id != chat_id); + cfg.bindings.push(Binding { + chat_id, + user_id: user_id.to_string(), + display: None, + }); + Ok(chat_id) +} + // ── Config listener ──────────────────────────────────────────────────────────── /// Subscribes to the system bus and reloads the in-memory bindings whenever the @@ -160,3 +184,58 @@ pub(crate) async fn config_listener( } } } + +#[cfg(test)] +mod tests { + use super::*; + + fn cfg_with_pairing(code: &str, chat_id: i64) -> TelegramConfig { + TelegramConfig { + bindings: vec![], + pending_pairings: vec![PairingEntry { + code: code.to_string(), + chat_id, + issued_at: "2026-01-01T00:00:00+00:00".to_string(), + }], + } + } + + #[test] + fn pairing_code_creates_binding_and_consumes_entry() { + let mut cfg = cfg_with_pairing("ABC123", 42); + let chat_id = apply_pairing_code(&mut cfg, "ABC123", "u1").unwrap(); + assert_eq!(chat_id, 42); + assert!(cfg.pending_pairings.is_empty(), "the code must be consumed"); + assert_eq!(cfg.bindings.len(), 1); + assert_eq!(cfg.bindings[0].user_id, "u1"); + assert_eq!(cfg.bindings[0].chat_id, 42); + } + + #[test] + fn pairing_code_is_case_and_whitespace_insensitive() { + let mut cfg = cfg_with_pairing("ABC123", 42); + apply_pairing_code(&mut cfg, " abc123 ", "u1").unwrap(); + assert_eq!(cfg.bindings[0].user_id, "u1"); + } + + #[test] + fn pairing_replaces_an_existing_binding_for_the_same_chat() { + let mut cfg = cfg_with_pairing("ABC123", 42); + cfg.bindings.push(Binding { chat_id: 42, user_id: "old".into(), display: None }); + cfg.bindings.push(Binding { chat_id: 99, user_id: "other".into(), display: None }); + apply_pairing_code(&mut cfg, "ABC123", "u1").unwrap(); + assert_eq!(cfg.bindings.len(), 2); + assert!(cfg.bindings.iter().any(|b| b.chat_id == 42 && b.user_id == "u1")); + assert!(cfg.bindings.iter().any(|b| b.chat_id == 99 && b.user_id == "other"), + "bindings for other chats are untouched"); + } + + #[test] + fn unknown_code_fails_and_keeps_state() { + let mut cfg = cfg_with_pairing("ABC123", 42); + let err = apply_pairing_code(&mut cfg, "ZZZ999", "u1").unwrap_err(); + assert!(err.to_string().contains("invalid or expired")); + assert_eq!(cfg.pending_pairings.len(), 1, "the pending entry must survive a failed attempt"); + assert!(cfg.bindings.is_empty()); + } +} diff --git a/crates/plugin-telegram-bot/src/events.rs b/crates/plugin-telegram-bot/src/events.rs index e9248d8..29ffe74 100644 --- a/crates/plugin-telegram-bot/src/events.rs +++ b/crates/plugin-telegram-bot/src/events.rs @@ -60,6 +60,11 @@ pub(crate) async fn spawn_forwarders_for_bound_users( ) { let bindings = shared.bindings.read().await.clone(); for b in &bindings.bindings { + // Skip users whose access was revoked — don't spin up a forwarder for + // a chat the bot will refuse to serve anyway (inbound is gated too). + if !shared.user_authorized(&b.user_id).await { + continue; + } if let Some(handle) = shared.user_channel.resolve_user(&b.user_id).await { ensure_forwarder(bot.clone(), Arc::clone(shared), &b.user_id, b.chat_id, handle, cancel.clone()).await; } diff --git a/crates/plugin-telegram-bot/src/handlers.rs b/crates/plugin-telegram-bot/src/handlers.rs index dc42ba5..6942c25 100644 --- a/crates/plugin-telegram-bot/src/handlers.rs +++ b/crates/plugin-telegram-bot/src/handlers.rs @@ -124,6 +124,19 @@ pub(crate) async fn message_handler( } }; + // The chat is bound, but access is a separate admin-revocable grant. Gate + // here so a revoked user is refused immediately, without touching the + // binding (a re-grant restores service with no re-pairing). + if !shared.user_authorized(&user_id).await { + bot.send_message( + chat_id, + "⛔ Your access to this bot has been withdrawn by an administrator.", + ) + .await + .ok(); + return Ok(()); + } + // Resolve the user's per-user context (must be unlocked, §9). let handle = match shared.user_channel.resolve_user(&user_id).await { Some(h) => h, diff --git a/crates/plugin-telegram-bot/src/lib.rs b/crates/plugin-telegram-bot/src/lib.rs index 359d12d..5820fe8 100644 --- a/crates/plugin-telegram-bot/src/lib.rs +++ b/crates/plugin-telegram-bot/src/lib.rs @@ -11,10 +11,12 @@ /// /// # Pairing /// -/// Unknown chats receive a pairing code. The admin's agent calls the -/// `telegram_pairing` tool (category `Config`) to bind the `chat_id` to a -/// `user_id`. The binding is written to the config table; the resulting -/// `ConfigKeyUpdated` event reloads the in-memory cache instantly. +/// Unknown chats receive a pairing code. The user links their own account by +/// pasting the code in the Plugins page of the web app (the plugin's +/// `user_config_schema` / `update_user_config` hook); the admin's agent can +/// also bind a chat via the `telegram_pairing` tool (category `Config`). The +/// binding is written to the config table; the resulting `ConfigKeyUpdated` +/// event reloads the in-memory cache instantly. /// /// # Human-in-the-loop approvals /// @@ -53,6 +55,11 @@ mod handlers; mod helpers; mod tools; +/// The plugin id — the key into `plugin_access` / `plugin_user_configs` and the +/// value returned by [`Plugin::id`]. Kept in one place so the runtime access +/// check and the registration id can never drift apart. +pub(crate) const PLUGIN_ID: &str = "telegram"; + /// Injected as extra system context for every Telegram turn. /// Kept compact to minimise token overhead. pub(crate) const TELEGRAM_FORMAT_CONTEXT: &str = "\ @@ -127,6 +134,15 @@ impl TgShared { .find(|b| b.chat_id == chat_id) .map(|b| b.user_id.clone()) } + + /// Whether a bound `user_id` may still use this plugin. A binding only says + /// "this chat belongs to this user"; access is a separate, admin-revocable + /// grant (`plugin_access`). Enforced on every inbound message so a revoke + /// takes effect immediately — the binding is left intact so a re-grant + /// restores service without forcing the user to pair again. + pub(crate) async fn user_authorized(&self, user_id: &str) -> bool { + self.user_channel.plugin_access(PLUGIN_ID, user_id).await + } } // ── Plugin struct ───────────────────────────────────────────────────────────── @@ -161,7 +177,7 @@ impl TelegramPlugin { #[async_trait] impl Plugin for TelegramPlugin { - fn id(&self) -> &str { "telegram" } + fn id(&self) -> &str { PLUGIN_ID } fn name(&self) -> &str { "Telegram Bot" } fn description(&self) -> &str { "Private Telegram bot. Forwards messages to the LLM; supports HITL approval via inline keyboards." @@ -183,6 +199,39 @@ impl Plugin for TelegramPlugin { }) } + fn user_config_schema(&self) -> Value { + json!({ + "type": "object", + "properties": { + "pairing_code": { + "type": "string", + "title": "Pairing code", + "description": "Send any message to the bot — it replies with a 6-character code. Paste it here to link your Telegram chat." + } + }, + "required": ["pairing_code"] + }) + } + + /// Self-service pairing: the user pastes the code the bot replied with, + /// we turn it into a `chat_id → user_id` binding (same write path as the + /// `telegram_pairing` tool) and store a status blob for the UI. + async fn update_user_config(&self, user_id: &str, config: Value, ctx: &PluginContext) -> Result<()> { + let code = config.get("pairing_code").and_then(Value::as_str).unwrap_or("").trim(); + anyhow::ensure!(!code.is_empty(), "telegram: `pairing_code` is required"); + let shared = self.shared() + .ok_or_else(|| anyhow::anyhow!("telegram: the bot is not running — ask the admin to check the plugin"))? + .clone(); + let mut cfg = auth::load_config(&*shared.config).await.unwrap_or_default(); + let chat_id = auth::apply_pairing_code(&mut cfg, code, user_id)?; + auth::save_config(&*shared.config, &cfg).await?; + ctx.user_config + .set(self.id(), user_id, json!({ "linked": true, "chat_id": chat_id })) + .await?; + info!(user_id, chat_id, "telegram: user self-paired via the web UI"); + Ok(()) + } + fn as_any(&self) -> &dyn std::any::Any { self } fn as_arc_any(self: Arc) -> Arc { self } diff --git a/crates/skald-core/src/db/mod.rs b/crates/skald-core/src/db/mod.rs index b38a961..33fa19b 100644 --- a/crates/skald-core/src/db/mod.rs +++ b/crates/skald-core/src/db/mod.rs @@ -19,6 +19,8 @@ pub mod mcp_user_servers; pub mod memory_docs; pub mod oauth_providers; pub mod plugins; +pub mod plugin_access; +pub mod plugin_user_configs; pub mod role_capabilities; pub mod roles; pub mod scheduled_jobs; @@ -281,6 +283,36 @@ async fn create_registry_tables(pool: &SqlitePool) -> Result<()> { .execute(pool) .await?; + // Which users may see/configure each plugin. `plugin_id` is deliberately + // NOT a foreign key to plugins.id: plugin identity comes from compiled + // registration, and a `plugins` row is only created lazily on first + // toggle — a plugin never configured must still be grantable. + sqlx::query( + "CREATE TABLE IF NOT EXISTS plugin_access ( + plugin_id TEXT NOT NULL, + user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + PRIMARY KEY (plugin_id, user_id) + )", + ) + .execute(pool) + .await?; + + // Per-user plugin settings (e.g. Telegram's pairing status). Lives in + // `system.db` — admin-readable, never secrets. `plugin_id` not a FK for + // the same reason as plugin_access. + sqlx::query( + "CREATE TABLE IF NOT EXISTS plugin_user_configs ( + plugin_id TEXT NOT NULL, + user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + config TEXT NOT NULL DEFAULT '{}', + updated_at TEXT NOT NULL DEFAULT (datetime('now')), + PRIMARY KEY (plugin_id, user_id) + )", + ) + .execute(pool) + .await?; + sqlx::query( "CREATE TABLE IF NOT EXISTS tool_permission_groups ( id TEXT PRIMARY KEY, @@ -1094,4 +1126,87 @@ mod tests { let _ = std::fs::remove_dir_all(&dir); } + + /// `plugin_access` / `plugin_user_configs`: grant/revoke round-trip, JSON + /// blob round-trip, and the `users(id)` cascade. `plugin_id` deliberately + /// accepts ids with no `plugins` row (identity = compiled registration). + #[tokio::test] + async fn plugin_access_and_user_configs_round_trip() { + let dir = temp_dir("plugin-tables"); + let path = dir.join("system.db"); + let pool = init_system_pool(path.to_str().unwrap()).await.unwrap(); + + let mk_user = |id: &str| { + sqlx::query("INSERT INTO users (id, username, role_id, encrypted) VALUES (?, ?, 'admin', 0)") + .bind(id.to_string()).bind(id.to_string()) + .execute(&pool) + }; + mk_user("u1").await.unwrap(); + mk_user("u2").await.unwrap(); + + // No `plugins` row for "telegram" — grants must still work. + plugin_access::grant(&pool, "telegram", "u1").await.unwrap(); + plugin_access::grant(&pool, "telegram", "u1").await.unwrap(); // idempotent + plugin_access::grant(&pool, "telegram", "u2").await.unwrap(); + assert!(plugin_access::has_access(&pool, "telegram", "u1").await.unwrap()); + assert!(!plugin_access::has_access(&pool, "comfyui", "u1").await.unwrap()); + assert_eq!(plugin_access::plugin_ids_for_user(&pool, "u1").await.unwrap(), vec!["telegram"]); + assert_eq!(plugin_access::users_for_plugin(&pool, "telegram").await.unwrap(), vec!["u1", "u2"]); + + plugin_access::set_access(&pool, "telegram", &["u2".to_string()]).await.unwrap(); + assert!(!plugin_access::has_access(&pool, "telegram", "u1").await.unwrap()); + assert_eq!(plugin_access::users_for_plugin(&pool, "telegram").await.unwrap(), vec!["u2"]); + + plugin_user_configs::set(&pool, "telegram", "u2", &serde_json::json!({"linked": true})).await.unwrap(); + assert_eq!( + plugin_user_configs::get(&pool, "telegram", "u2").await.unwrap(), + Some(serde_json::json!({"linked": true})), + ); + plugin_user_configs::set(&pool, "telegram", "u2", &serde_json::json!({"linked": false})).await.unwrap(); + assert_eq!( + plugin_user_configs::get(&pool, "telegram", "u2").await.unwrap(), + Some(serde_json::json!({"linked": false})), + ); + assert_eq!(plugin_user_configs::get(&pool, "telegram", "u1").await.unwrap(), None); + + // Deleting the user cascades both tables. + sqlx::query("DELETE FROM users WHERE id = 'u2'").execute(&pool).await.unwrap(); + assert_eq!(plugin_access::users_for_plugin(&pool, "telegram").await.unwrap(), Vec::::new()); + assert_eq!(plugin_user_configs::get(&pool, "telegram", "u2").await.unwrap(), None); + + pool.close().await; + let _ = std::fs::remove_dir_all(&dir); + } + + /// `effective_access`: the runtime gate channels enforce. Admin holds every + /// plugin implicitly (even one they were never granted); a member needs an + /// explicit grant; an unknown user fails closed. + #[tokio::test] + async fn plugin_effective_access_admin_short_circuit_and_grants() { + let dir = temp_dir("plugin-effective-access"); + let path = dir.join("system.db"); + let pool = init_system_pool(path.to_str().unwrap()).await.unwrap(); + + // A non-admin role, plus one admin and one member user. + sqlx::query("INSERT INTO roles (id, label, permission_group) VALUES ('member', 'Member', 'default')") + .execute(&pool).await.unwrap(); + sqlx::query("INSERT INTO users (id, username, role_id, encrypted) VALUES ('adm', 'adm', 'admin', 0)") + .execute(&pool).await.unwrap(); + sqlx::query("INSERT INTO users (id, username, role_id, encrypted) VALUES ('mem', 'mem', 'member', 0)") + .execute(&pool).await.unwrap(); + + plugin_access::grant(&pool, "telegram", "mem").await.unwrap(); + + // Admin: every plugin, granted or not. + assert!(plugin_access::effective_access(&pool, "telegram", "adm").await.unwrap()); + assert!(plugin_access::effective_access(&pool, "comfyui", "adm").await.unwrap()); + // Member: only what they were granted. + assert!(plugin_access::effective_access(&pool, "telegram", "mem").await.unwrap()); + assert!(!plugin_access::effective_access(&pool, "comfyui", "mem").await.unwrap()); + // Unknown user → fail closed. + assert!(!plugin_access::effective_access(&pool, "telegram", "ghost").await.unwrap()); + + pool.close().await; + let _ = std::fs::remove_dir_all(&dir); + } } diff --git a/crates/skald-core/src/db/plugin_access.rs b/crates/skald-core/src/db/plugin_access.rs new file mode 100644 index 0000000..19ef70c --- /dev/null +++ b/crates/skald-core/src/db/plugin_access.rs @@ -0,0 +1,103 @@ +//! Which users may see and configure each plugin. +//! +//! Registry junction table in `system.db` — opt-in access: a plugin with no +//! rows here is visible to admins only. Mirrors `mcp_global_access`, except +//! `plugin_id` is a bare TEXT (not a FK to `plugins.id`): plugin identity +//! comes from compiled registration and a `plugins` row exists only after +//! the first toggle, so a never-configured plugin must still be grantable. + +use anyhow::Result; +use sqlx::SqlitePool; + +// ── Reads ──────────────────────────────────────────────────────────────────── + +/// The ids of the plugins a user has been granted access to. +pub async fn plugin_ids_for_user(pool: &SqlitePool, user_id: &str) -> Result> { + let rows = sqlx::query_as::<_, (String,)>( + "SELECT plugin_id FROM plugin_access WHERE user_id = ? ORDER BY plugin_id", + ) + .bind(user_id) + .fetch_all(pool) + .await?; + Ok(rows.into_iter().map(|(p,)| p).collect()) +} + +/// The ids of the users granted access to a given plugin. +pub async fn users_for_plugin(pool: &SqlitePool, plugin_id: &str) -> Result> { + let rows = sqlx::query_as::<_, (String,)>( + "SELECT user_id FROM plugin_access WHERE plugin_id = ? ORDER BY user_id", + ) + .bind(plugin_id) + .fetch_all(pool) + .await?; + Ok(rows.into_iter().map(|(u,)| u).collect()) +} + +pub async fn has_access(pool: &SqlitePool, plugin_id: &str, user_id: &str) -> Result { + let row = sqlx::query_as::<_, (i64,)>( + "SELECT 1 FROM plugin_access WHERE plugin_id = ? AND user_id = ?", + ) + .bind(plugin_id) + .bind(user_id) + .fetch_optional(pool) + .await?; + Ok(row.is_some()) +} + +/// The effective runtime access decision for a channel adapter: the admin role +/// holds every plugin implicitly (mirroring the web `/plugins/mine` view), +/// otherwise the user must be granted in `plugin_access`. An unknown user id +/// resolves to `false`. Errors propagate — the caller fails closed. +pub async fn effective_access(pool: &SqlitePool, plugin_id: &str, user_id: &str) -> Result { + let role = sqlx::query_as::<_, (String,)>("SELECT role_id FROM users WHERE id = ?") + .bind(user_id) + .fetch_optional(pool) + .await?; + match role { + Some((r,)) if r == crate::db::roles::ADMIN_ROLE_ID => Ok(true), + Some(_) => has_access(pool, plugin_id, user_id).await, + None => Ok(false), + } +} + +// ── Writes ─────────────────────────────────────────────────────────────────── + +/// Grants a user access to a plugin. Idempotent on the PK. +pub async fn grant(pool: &SqlitePool, plugin_id: &str, user_id: &str) -> Result<()> { + sqlx::query( + "INSERT OR IGNORE INTO plugin_access (plugin_id, user_id) VALUES (?, ?)", + ) + .bind(plugin_id) + .bind(user_id) + .execute(pool) + .await?; + Ok(()) +} + +pub async fn revoke(pool: &SqlitePool, plugin_id: &str, user_id: &str) -> Result<()> { + sqlx::query("DELETE FROM plugin_access WHERE plugin_id = ? AND user_id = ?") + .bind(plugin_id) + .bind(user_id) + .execute(pool) + .await?; + Ok(()) +} + +/// Replaces the full access list for a plugin in one shot (the admin UI's +/// "who can use this" checklist). +pub async fn set_access(pool: &SqlitePool, plugin_id: &str, user_ids: &[String]) -> Result<()> { + let mut tx = pool.begin().await?; + sqlx::query("DELETE FROM plugin_access WHERE plugin_id = ?") + .bind(plugin_id) + .execute(&mut *tx) + .await?; + for user_id in user_ids { + sqlx::query("INSERT OR IGNORE INTO plugin_access (plugin_id, user_id) VALUES (?, ?)") + .bind(plugin_id) + .bind(user_id) + .execute(&mut *tx) + .await?; + } + tx.commit().await?; + Ok(()) +} diff --git a/crates/skald-core/src/db/plugin_user_configs.rs b/crates/skald-core/src/db/plugin_user_configs.rs new file mode 100644 index 0000000..28f9448 --- /dev/null +++ b/crates/skald-core/src/db/plugin_user_configs.rs @@ -0,0 +1,49 @@ +//! Per-user plugin configuration blobs (`plugin_user_configs` table). +//! +//! Registry table in `system.db` — **admin-readable, never secrets**. A plugin +//! with a non-empty `user_config_schema()` lets each granted user submit their +//! own settings from the UI (e.g. Telegram's pairing code); the plugin's +//! `update_user_config` hook validates and stores here. `plugin_id` is a bare +//! TEXT for the same reason as `plugin_access`. + +use anyhow::Result; +use serde_json::Value; +use sqlx::SqlitePool; + +pub async fn get(pool: &SqlitePool, plugin_id: &str, user_id: &str) -> Result> { + let row = sqlx::query_as::<_, (String,)>( + "SELECT config FROM plugin_user_configs WHERE plugin_id = ? AND user_id = ?", + ) + .bind(plugin_id) + .bind(user_id) + .fetch_optional(pool) + .await?; + match row { + None => Ok(None), + Some((json,)) => Ok(Some(serde_json::from_str(&json)?)), + } +} + +pub async fn set(pool: &SqlitePool, plugin_id: &str, user_id: &str, config: &Value) -> Result<()> { + sqlx::query( + "INSERT INTO plugin_user_configs (plugin_id, user_id, config, updated_at) + VALUES (?1, ?2, ?3, datetime('now')) + ON CONFLICT(plugin_id, user_id) + DO UPDATE SET config = excluded.config, updated_at = excluded.updated_at", + ) + .bind(plugin_id) + .bind(user_id) + .bind(serde_json::to_string(config)?) + .execute(pool) + .await?; + Ok(()) +} + +pub async fn delete(pool: &SqlitePool, plugin_id: &str, user_id: &str) -> Result<()> { + sqlx::query("DELETE FROM plugin_user_configs WHERE plugin_id = ? AND user_id = ?") + .bind(plugin_id) + .bind(user_id) + .execute(pool) + .await?; + Ok(()) +} diff --git a/crates/skald-core/src/db/role_capabilities.rs b/crates/skald-core/src/db/role_capabilities.rs index 7633591..265edbd 100644 --- a/crates/skald-core/src/db/role_capabilities.rs +++ b/crates/skald-core/src/db/role_capabilities.rs @@ -31,6 +31,11 @@ pub const MANAGE_CATALOG: &str = "mcp.manage_catalog"; /// is a single [`grant`], no code change. pub const MANAGE_SHARED_FOLDERS: &str = "folders.manage"; +/// Enable/disable plugins, edit their instance-wide config and grant per-user +/// access (the `plugin_access` table). Admin-only for now — same implicit-hold +/// pattern as [`MANAGE_SHARED_FOLDERS`]. +pub const MANAGE_PLUGINS: &str = "plugin.manage"; + /// The default capabilities of an ordinary (non-admin) user role. pub const DEFAULT_USER_CAPABILITIES: &[&str] = &[REGISTER_REMOTE, REGISTER_LOCAL_FROM_CATALOG]; diff --git a/crates/skald-core/src/memory/mod.rs b/crates/skald-core/src/memory/mod.rs index fdc133f..1ee2cf7 100644 --- a/crates/skald-core/src/memory/mod.rs +++ b/crates/skald-core/src/memory/mod.rs @@ -69,12 +69,12 @@ impl MemoryManager { /// Returns memory context to inject into the system prompt for the upcoming /// turn. Returns `None` if no backend is registered or the backend is /// unavailable / has nothing to say. - pub async fn query_context(&self, session_id: i64, user_message: &str) -> Option { + pub async fn query_context(&self, user_id: &str, session_id: i64, user_message: &str) -> Option { let backend = self.backend.read().await.clone()?; if !backend.is_available() { return None; } - backend.query_context(session_id, user_message).await + backend.query_context(user_id, session_id, user_message).await } /// Returns the per-turn LLM tools exposed by the active backend. diff --git a/crates/skald-core/src/plugin/mod.rs b/crates/skald-core/src/plugin/mod.rs index ccb43fd..d70f07e 100644 --- a/crates/skald-core/src/plugin/mod.rs +++ b/crates/skald-core/src/plugin/mod.rs @@ -2,7 +2,7 @@ // here would make the core depend on every plugin — and, through // `plugin-transcribe-whisper-local`, on a C build — for no gain: the consumer // constructs the plugin list and passes it to `Skald::new`. -pub use core_api::plugin::{Plugin, PluginContext, RouterFactory}; +pub use core_api::plugin::{Plugin, PluginContext, PluginPage, RouterFactory}; use std::collections::HashMap; use std::sync::{Arc, OnceLock}; @@ -12,6 +12,7 @@ const PLUGIN_START_TIMEOUT_SECS: u64 = 30; const PLUGIN_STOP_TIMEOUT_SECS: u64 = 5; use anyhow::Result; +use async_trait::async_trait; use serde::Serialize; use serde_json::{Value, json}; use sqlx::SqlitePool; @@ -19,21 +20,78 @@ use tokio::sync::Mutex; use tokio::time::timeout; use tracing::{error, info, warn}; -use crate::db::plugins as db; +use crate::db::{plugin_access, plugin_user_configs, plugins as db}; use crate::skald::Skald; // ── Public plugin info (returned by list_items tool and REST API) ───────────── #[derive(Debug, Clone, Serialize)] pub struct PluginInfo { - pub id: String, - pub name: String, - pub description: String, - pub enabled: bool, - pub running: bool, - pub config: Value, - pub config_schema: Value, - pub runtime_status: Option, + pub id: String, + pub name: String, + pub description: String, + pub enabled: bool, + pub running: bool, + pub config: Value, + pub config_schema: Value, + pub user_config_schema: Value, + /// Whether the plugin contributes an `http_router()` — its routes are + /// mounted at boot and gated at runtime, so they serve as soon as the + /// plugin is enabled (no restart). + pub has_router: bool, + /// Whether the plugin gates access through its own binding lifecycle — the + /// admin UI hides the "User access" checklist when true (see the trait). + pub manages_own_access: bool, + pub runtime_status: Option, +} + +/// One user's view of a plugin they may use — served by `GET /api/plugins/mine`. +#[derive(Debug, Clone, Serialize)] +pub struct UserPluginView { + pub id: String, + pub name: String, + pub description: String, + pub user_config_schema: Value, + pub user_config: Value, +} + +/// A plugin-contributed web page as seen by one user — served by +/// `GET /api/plugins/pages`. `entry_url` is already resolved against the +/// plugin's router mount, so the frontend can `import()` it directly. +#[derive(Debug, Clone, Serialize)] +pub struct PluginPageInfo { + pub plugin_id: String, + pub page_id: String, + pub title: String, + pub icon: String, + pub priority: i32, + pub entry_url: String, + /// Fragment-contract version the host speaks. Always 1 for now — bump when + /// the contract changes so old hosts can refuse new fragments cleanly. + pub api_version: u32, +} + +// ── Per-user config store (the PluginUserConfigApi injected into PluginContext) ─ + +/// `PluginUserConfigApi` over the system pool. Admin-readable by design — +/// see `db::plugin_user_configs`. +struct UserConfigStore { + db: Arc, +} + +#[async_trait] +impl core_api::user_plugin_config::PluginUserConfigApi for UserConfigStore { + async fn get(&self, plugin_id: &str, user_id: &str) -> Result> { + plugin_user_configs::get(&self.db, plugin_id, user_id).await + } + + async fn set(&self, plugin_id: &str, user_id: &str, config: Value) -> Result<()> { + plugin_user_configs::set(&self.db, plugin_id, user_id, &config).await + } + + async fn delete(&self, plugin_id: &str, user_id: &str) -> Result<()> { + plugin_user_configs::delete(&self.db, plugin_id, user_id).await + } } // ── PluginManager ───────────────────────────────────────────────────────────── @@ -41,6 +99,7 @@ pub struct PluginInfo { pub struct PluginManager { plugins: Vec>, db: Arc, + user_config: Arc, skald: OnceLock>, /// Provided by WebFrontend before start_enabled() is called. router_factory: OnceLock, @@ -54,6 +113,7 @@ impl PluginManager { pub fn new(db: Arc) -> Self { Self { plugins: Vec::new(), + user_config: Arc::new(UserConfigStore { db: Arc::clone(&db) }), db, skald: OnceLock::new(), router_factory: OnceLock::new(), @@ -109,30 +169,25 @@ impl PluginManager { location: Arc::clone(skald.location_manager()) as _, system_bus: Arc::clone(skald.system_bus()), user_channel: self.skald()? as Arc, + user_config: Arc::clone(&self.user_config) as _, web_port, remote_slot: Arc::clone(skald.remote()), router_factory, }) } - /// Collects the HTTP routers contributed by enabled plugins (plugin.md §12.3). - /// Returns `(plugin_id, router)` pairs; the caller (`WebFrontend::start`) - /// nests each under `/api/plugin//`. Only plugins with `enabled=true` in - /// the DB and a non-`None` `http_router()` are included. + /// Collects the HTTP routers contributed by **every** registered plugin — + /// enabled or not. Returns `(plugin_id, router)` pairs; the caller + /// (`WebFrontend::start`) nests each under `/api/plugin//` behind the + /// auth + enabled gates, so a disabled plugin's routes answer 404 and + /// enabling one at runtime serves them immediately (no restart). /// - /// Call this AFTER `start_enabled()` so a plugin's router can close over state - /// initialised during `reload`/`start`. + /// Call this AFTER `start_enabled()` so a started plugin's router can close + /// over state initialised during `reload`/`start`. The router must still be + /// safe to build for a plugin that never started (see `Plugin::http_router`). pub async fn collect_plugin_routers(&self) -> Vec<(String, axum::Router)> { let mut out = Vec::new(); for plugin in &self.plugins { - match db::get(&self.db, plugin.id()).await { - Ok(Some(row)) if row.enabled => {} - Ok(_) => continue, - Err(e) => { - warn!(plugin = plugin.id(), error = %e, "collect_plugin_routers: DB read failed; skipping"); - continue; - } - } if let Some(router) = plugin.http_router() { info!(plugin = plugin.id(), "plugin contributed an HTTP router → /api/plugin/{}", plugin.id()); out.push((plugin.id().to_string(), router)); @@ -314,14 +369,17 @@ impl PluginManager { .map(|r| (r.enabled, r.config)) .unwrap_or((false, "{}".to_string())); out.push(PluginInfo { - id: plugin.id().to_string(), - name: plugin.name().to_string(), - description: plugin.description().to_string(), + id: plugin.id().to_string(), + name: plugin.name().to_string(), + description: plugin.description().to_string(), enabled, - running: plugin.is_running(), - config: serde_json::from_str(&config_json).unwrap_or(json!({})), - config_schema: plugin.config_schema(), - runtime_status: plugin.runtime_status(), + running: plugin.is_running(), + config: serde_json::from_str(&config_json).unwrap_or(json!({})), + config_schema: plugin.config_schema(), + user_config_schema: plugin.user_config_schema(), + has_router: plugin.http_router().is_some(), + manages_own_access: plugin.manages_own_access(), + runtime_status: plugin.runtime_status(), }); } Ok(out) @@ -334,6 +392,125 @@ impl PluginManager { &self.plugins } + // ── Per-user access & configuration ─────────────────────────────────────── + + /// The plugins a user sees in their UI: **enabled** and granted in + /// `plugin_access` (admins see every enabled plugin). Each entry carries + /// the user's current config blob for the schema-driven form. + pub async fn list_accessible(&self, user_id: &str, is_admin: bool) -> Result> { + let granted: std::collections::HashSet = if is_admin { + std::collections::HashSet::new() + } else { + plugin_access::plugin_ids_for_user(&self.db, user_id).await?.into_iter().collect() + }; + let mut out = Vec::new(); + for plugin in &self.plugins { + // Binding-managed plugins (e.g. mobile-connector) aren't configured + // from the "My plugins" view — they own their own pairing UI. + if plugin.manages_own_access() { + continue; + } + let enabled = db::get(&self.db, plugin.id()).await? + .map(|r| r.enabled) + .unwrap_or(false); + if !enabled || (!is_admin && !granted.contains(plugin.id())) { + continue; + } + let user_config = plugin_user_configs::get(&self.db, plugin.id(), user_id) + .await? + .unwrap_or(json!({})); + out.push(UserPluginView { + id: plugin.id().to_string(), + name: plugin.name().to_string(), + description: plugin.description().to_string(), + user_config_schema: plugin.user_config_schema(), + user_config, + }); + } + Ok(out) + } + + pub async fn has_access(&self, id: &str, user_id: &str) -> Result { + plugin_access::has_access(&self.db, id, user_id).await + } + + /// The web pages a user sees in the frontend menu: every `web_pages()` + /// entry of every **enabled** plugin, filtered by audience — `admin_only` + /// pages go to the admin role only; the others require the `plugin_access` + /// grant (admins see all). Binding-managed plugins (`manages_own_access`) + /// keep their pages admin-only unless the page says otherwise, mirroring + /// `list_accessible`. + pub async fn web_pages_for(&self, user_id: &str, is_admin: bool) -> Result> { + let granted: std::collections::HashSet = if is_admin { + std::collections::HashSet::new() + } else { + plugin_access::plugin_ids_for_user(&self.db, user_id).await?.into_iter().collect() + }; + let mut out = Vec::new(); + for plugin in &self.plugins { + let pages = plugin.web_pages(); + if pages.is_empty() { + continue; + } + if !self.is_enabled(plugin.id()).await? { + continue; + } + let owns_access = plugin.manages_own_access(); + for page in pages { + let visible = if is_admin { + true + } else if page.admin_only || owns_access { + false + } else { + granted.contains(plugin.id()) + }; + if visible { + out.push(PluginPageInfo { + plugin_id: plugin.id().to_string(), + page_id: page.page_id.to_string(), + title: page.title, + icon: page.icon.to_string(), + priority: page.priority, + entry_url: format!("/api/plugin/{}/{}", plugin.id(), page.entry), + api_version: 1, + }); + } + } + } + out.sort_by_key(|p| p.priority); + Ok(out) + } + + pub async fn is_enabled(&self, id: &str) -> Result { + Ok(db::get(&self.db, id).await?.map(|r| r.enabled).unwrap_or(false)) + } + + /// The user ids granted access to a plugin (admin UI checklist). + pub async fn list_grants(&self, id: &str) -> Result> { + self.find(id)?; + plugin_access::users_for_plugin(&self.db, id).await + } + + pub async fn set_grants(&self, id: &str, user_ids: &[String]) -> Result<()> { + self.find(id)?; + plugin_access::set_access(&self.db, id, user_ids).await + } + + /// Applies a user's per-plugin config submission. The plugin must be + /// enabled, declare a non-empty `user_config_schema`, and the caller must + /// hold access (enforced by the API layer). + pub async fn update_user_config(&self, id: &str, user_id: &str, config: Value) -> Result<()> { + let plugin = self.find(id)?; + if !self.is_enabled(id).await? { + anyhow::bail!("plugin is not enabled: {id}"); + } + if plugin.user_config_schema().as_object().is_none_or(|s| s.is_empty()) { + anyhow::bail!("plugin has no per-user configuration: {id}"); + } + let skald = self.skald()?; + plugin.update_user_config(user_id, config, &self.build_context(&skald)?).await + } + pub fn get_plugin_typed(&self, id: &str) -> Option> { self.plugins.iter() .find(|p| p.id() == id) @@ -347,3 +524,105 @@ impl PluginManager { .ok_or_else(|| anyhow::anyhow!("plugin not found: {id}")) } } + +#[cfg(test)] +mod tests { + use super::*; + use core_api::plugin::PluginPage; + + struct FakePlugin { + id: &'static str, + pages: Vec, + owns_access: bool, + } + + #[async_trait] + impl Plugin for FakePlugin { + fn id(&self) -> &str { self.id } + fn name(&self) -> &str { self.id } + fn description(&self) -> &str { "" } + fn is_running(&self) -> bool { false } + fn manages_own_access(&self) -> bool { self.owns_access } + fn web_pages(&self) -> Vec { self.pages.clone() } + async fn reload(&self, _enabled: bool, _config: Value, _ctx: PluginContext) -> Result<()> { Ok(()) } + async fn start(&self, _ctx: PluginContext) -> Result<()> { Ok(()) } + async fn stop(&self) -> Result<()> { Ok(()) } + fn as_any(&self) -> &dyn std::any::Any { self } + fn as_arc_any(self: Arc) -> Arc { self } + } + + fn page(page_id: &'static str, admin_only: bool, priority: i32) -> PluginPage { + PluginPage { + page_id, + title: page_id.to_string(), + icon: "puzzle", + entry: format!("web/{page_id}.js"), + admin_only, + priority, + } + } + + async fn test_manager(tag: &str) -> PluginManager { + let nanos = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos(); + let path = std::env::temp_dir() + .join(format!("skald-plugin-test-{tag}-{}-{nanos}", std::process::id())) + .join("system.db"); + let pool = crate::db::init_system_pool(path.to_str().unwrap()).await.unwrap(); + PluginManager::new(Arc::new(pool)) + } + + #[tokio::test] + async fn web_pages_for_filters_by_enabled_audience_and_access() { + let mut mgr = test_manager("pages-filter").await; + mgr.register_arc(Arc::new(FakePlugin { + id: "alpha", + pages: vec![page("admin-console", true, 10), page("user-dash", false, 20)], + owns_access: false, + })); + mgr.register_arc(Arc::new(FakePlugin { + id: "beta", + pages: vec![page("off-page", false, 5)], + owns_access: false, + })); + mgr.register_arc(Arc::new(FakePlugin { + id: "gamma", + pages: vec![page("pairing", false, 15)], + owns_access: true, + })); + // alpha + gamma enabled, beta disabled; user u1 holds grants on both. + db::upsert(&mgr.db, "alpha", true, "{}").await.unwrap(); + db::upsert(&mgr.db, "beta", false, "{}").await.unwrap(); + db::upsert(&mgr.db, "gamma", true, "{}").await.unwrap(); + for (id, username) in [("u1", "user-one"), ("u2", "user-two")] { + sqlx::query("INSERT INTO users (id, username, role_id, encrypted) VALUES (?, ?, 'admin', 0)") + .bind(id).bind(username).execute(&*mgr.db).await.unwrap(); + } + plugin_access::grant(&mgr.db, "alpha", "u1").await.unwrap(); + plugin_access::grant(&mgr.db, "gamma", "u1").await.unwrap(); + + // Admin: everything enabled, priority-ascending. + let admin = mgr.web_pages_for("admin-user", true).await.unwrap(); + let got: Vec<(&str, &str)> = admin.iter() + .map(|p| (p.plugin_id.as_str(), p.page_id.as_str())).collect(); + assert_eq!(got, vec![ + ("alpha", "admin-console"), + ("gamma", "pairing"), + ("alpha", "user-dash"), + ]); + assert_eq!(admin[0].entry_url, "/api/plugin/alpha/web/admin-console.js"); + assert_eq!(admin[0].api_version, 1); + + // Non-admin: only the non-admin_only page of a granted, enabled, + // non-binding-managed plugin — beta is disabled, gamma manages its own + // access, alpha's admin console is admin_only. + let user = mgr.web_pages_for("u1", false).await.unwrap(); + let got: Vec<(&str, &str)> = user.iter() + .map(|p| (p.plugin_id.as_str(), p.page_id.as_str())).collect(); + assert_eq!(got, vec![("alpha", "user-dash")]); + + // A user with no grants sees nothing. + let stranger = mgr.web_pages_for("u2", false).await.unwrap(); + assert!(stranger.is_empty()); + } +} diff --git a/crates/skald-core/src/session/handler/llm_loop.rs b/crates/skald-core/src/session/handler/llm_loop.rs index 8779f2f..9072625 100644 --- a/crates/skald-core/src/session/handler/llm_loop.rs +++ b/crates/skald-core/src/session/handler/llm_loop.rs @@ -408,9 +408,21 @@ impl ChatSessionHandler { Box::pin(async move { handler(args).await.map(ToolResult::Text) }), ))); } - // Memory + image tools (registered ad-hoc on the config). + // The ToolContext carries this session's id, owner user id and owner pool + // so owner-bound tools (cron management, the Honcho memory peer) act on the + // caller's own data. Built once and shared by memory tools and the registry. + let ctx = ToolContext { + session_id: self.session_id, + user_id: self.user_id.clone(), + pool: Arc::clone(&self.db), + // Snapshot the fs cell for the duration of this tool call — a concurrent + // shared-folder remount swaps the cell, the next call picks it up (§6). + fs: self.fs.load(), + }; + // Memory + image tools (registered ad-hoc on the config). Memory tools route + // through `run_with` so the Honcho tools reach the caller's own peer. if let Some(tool) = config.memory_tools.iter().find(|t| t.name() == name) { - return Some(tool.run(args)); + return Some(tool.run_with(&ctx, args)); } if let Some(tool) = config.image_tools.iter().find(|t| t.name() == name) { return Some(tool.run(args)); @@ -426,15 +438,6 @@ impl ChatSessionHandler { } // Built-in registry tools (incl. execute_cmd, whose SimpleExecution kills // the child via kill_on_drop when the work future is dropped on /stop). - // The ToolContext carries this session's id and owner pool so owner-bound - // registry tools (e.g. cron management) act on the caller's own database. - let ctx = ToolContext { - session_id: self.session_id, - pool: Arc::clone(&self.db), - // Snapshot the fs cell for the duration of this tool call — a concurrent - // shared-folder remount swaps the cell, the next call picks it up (§6). - fs: self.fs.load(), - }; self.tools.run(name, &ctx, args) } } diff --git a/crates/skald-core/src/session/handler/mod.rs b/crates/skald-core/src/session/handler/mod.rs index 3a28a28..2c4d51a 100644 --- a/crates/skald-core/src/session/handler/mod.rs +++ b/crates/skald-core/src/session/handler/mod.rs @@ -547,7 +547,7 @@ impl ChatSessionHandler { // providers with prefix caching (e.g. Alibaba/DeepSeek via OpenRouter) // to cache the stable system prompt across turns even though Honcho // memories change on every call. - let honcho_dynamic = match self.memory_manager.query_context(self.session_id, content).await { + let honcho_dynamic = match self.memory_manager.query_context(&self.user_id, self.session_id, content).await { Some(mem_ctx) => { trace!( session_id = self.session_id, @@ -659,6 +659,7 @@ impl ChatSessionHandler { self.event_bus.user_message(ChatEvent { session_id: self.session_id, stack_id: stack.id, + user_id: self.user_id.clone(), message_id: user_message_id, role: ChatEventRole::User, content: user_content, @@ -671,6 +672,7 @@ impl ChatSessionHandler { self.event_bus.assistant_response(ChatEvent { session_id: self.session_id, stack_id: stack.id, + user_id: self.user_id.clone(), message_id, role: ChatEventRole::Assistant, content, diff --git a/crates/skald-core/src/skald/accessors.rs b/crates/skald-core/src/skald/accessors.rs index 98ffbbb..d780f3a 100644 --- a/crates/skald-core/src/skald/accessors.rs +++ b/crates/skald-core/src/skald/accessors.rs @@ -177,4 +177,16 @@ impl UserChannelApi for Skald { let ctx = self.user_context(user_id).await?; Some(std::sync::Arc::new(UserContextHandle::new(ctx))) } + + async fn plugin_access(&self, plugin_id: &str, user_id: &str) -> bool { + // Admin short-circuit + grant lookup live in `db::plugin_access`; a + // lookup error fails closed. + crate::db::plugin_access::effective_access(self.db(), plugin_id, user_id) + .await + .unwrap_or(false) + } + + async fn user_for_session(&self, token: &str) -> Option { + self.sessions().user_of(token) + } } diff --git a/crates/skald-core/src/tools/fs/mod.rs b/crates/skald-core/src/tools/fs/mod.rs index 92c2d52..b61b927 100644 --- a/crates/skald-core/src/tools/fs/mod.rs +++ b/crates/skald-core/src/tools/fs/mod.rs @@ -375,7 +375,7 @@ mod tests { let write = WriteFile::new(Arc::clone(&shared)); let read = ReadFile::new(Arc::clone(&shared)); let list = ListFiles::new(Arc::clone(&shared)); - let ctx = ToolContext { session_id: 1, pool: Arc::clone(&user), fs: test_fs() }; + let ctx = ToolContext { session_id: 1, user_id: "u_test".into(), pool: Arc::clone(&user), fs: test_fs() }; // Private write lands in the user pool — and never in the shared one. let out = drive(&write, &ctx, json!({"path":"user-memory/spesa.md","content":"latte\npane"})) @@ -425,7 +425,7 @@ mod tests { let insert = InsertAtLine::new(Arc::clone(&shared)); let replace = ReplaceLines::new(Arc::clone(&shared)); let search = SearchFile::new(Arc::clone(&shared)); - let ctx = ToolContext { session_id: 1, pool: Arc::clone(&user), fs: test_fs() }; + let ctx = ToolContext { session_id: 1, user_id: "u_test".into(), pool: Arc::clone(&user), fs: test_fs() }; async fn note(pool: &SqlitePool, path: &str) -> String { crate::db::memory_docs::get(pool, path).await.unwrap().unwrap().content @@ -470,7 +470,7 @@ mod tests { let write = WriteFile::new(Arc::clone(&shared)); let search = MemorySearch::new(Arc::clone(&shared)); - let ctx = ToolContext { session_id: 1, pool: Arc::clone(&user), fs: test_fs() }; + let ctx = ToolContext { session_id: 1, user_id: "u_test".into(), pool: Arc::clone(&user), fs: test_fs() }; // one note in each store, both mentioning "wifi" drive(&write, &ctx, json!({"path":"user-memory/rete.md","content":"la mia wifi privata"})) diff --git a/src/frontend/api/caps.rs b/src/frontend/api/caps.rs new file mode 100644 index 0000000..6ef200f --- /dev/null +++ b/src/frontend/api/caps.rs @@ -0,0 +1,17 @@ +//! Shared role-capability gate for API handlers. + +use skald_core::db::role_capabilities; +use skald_core::skald::Skald; + +use super::ApiError; + +/// Fails with 403 unless the caller's role holds `cap` (admin holds everything). +pub async fn require_cap(skald: &Skald, user_id: &str, cap: &str) -> Result<(), ApiError> { + let user = skald_core::db::users::get(skald.db(), user_id).await? + .ok_or_else(|| ApiError::unauthorized("unknown user"))?; + if role_capabilities::has(skald.db(), &user.role_id, cap).await? { + Ok(()) + } else { + Err(ApiError::forbidden(format!("your role lacks the capability `{cap}`"))) + } +} diff --git a/src/frontend/api/guard.rs b/src/frontend/api/guard.rs index 39c5d35..ebf5dc8 100644 --- a/src/frontend/api/guard.rs +++ b/src/frontend/api/guard.rs @@ -74,9 +74,36 @@ pub async fn require_auth( match session_token(req.headers()).and_then(|t| skald.sessions().user_of(&t)) { Some(user_id) => { + // `AuthUser` is the bin-private identity for host handlers; `Caller` + // is the core-api mirror so plugin routers (which cannot name + // bin-crate types) can identify the caller too. + req.extensions_mut().insert(core_api::plugin::Caller { user_id: user_id.clone() }); req.extensions_mut().insert(AuthUser { user_id }); next.run(req).await } None => StatusCode::UNAUTHORIZED.into_response(), } } + +/// Enabled-gate for plugin-contributed routers (`/api/plugin//…`). +/// +/// Plugin routers are all mounted at boot — including those of disabled +/// plugins, whose `http_router()` must be safe to build before `start`. This +/// gate re-checks the enabled flag in the DB on **every** request, so a +/// disabled plugin answers 404 (it does not reveal it exists) and enabling one +/// at runtime serves its routes immediately, with no restart. Runs inside +/// `require_auth`, so by this point the caller is a logged-in user. +pub async fn plugin_enabled_gate( + State((skald, plugin_id)): State<(Arc, String)>, + req: Request, + next: Next, +) -> Response { + match skald.plugin_manager().is_enabled(&plugin_id).await { + Ok(true) => next.run(req).await, + Ok(false) => StatusCode::NOT_FOUND.into_response(), + Err(e) => { + tracing::warn!(plugin = %plugin_id, error = %e, "plugin enabled-gate: DB read failed"); + StatusCode::NOT_FOUND.into_response() + } + } +} diff --git a/src/frontend/api/mcp.rs b/src/frontend/api/mcp.rs index 586a096..44b2809 100644 --- a/src/frontend/api/mcp.rs +++ b/src/frontend/api/mcp.rs @@ -20,22 +20,12 @@ use serde_json::{json, Value}; use skald_core::db::{mcp_catalog, mcp_global_access, mcp_global_servers, mcp_user_servers, oauth_providers, role_capabilities}; use skald_core::skald::Skald; +use super::caps::require_cap; use super::guard::AuthUser; use super::{require_context, ApiError}; // ── helpers ─────────────────────────────────────────────────────────────────── -/// Fails with 403 unless the caller's role holds `cap` (admin holds everything). -async fn require_cap(skald: &Skald, user_id: &str, cap: &str) -> Result<(), ApiError> { - let user = skald_core::db::users::get(skald.db(), user_id).await? - .ok_or_else(|| ApiError::unauthorized("unknown user"))?; - if role_capabilities::has(skald.db(), &user.role_id, cap).await? { - Ok(()) - } else { - Err(ApiError::forbidden(format!("your role lacks the capability `{cap}`"))) - } -} - fn to_json_opt(v: &Option) -> Option { v.as_ref().and_then(|x| serde_json::to_string(x).ok()) } diff --git a/src/frontend/api/mod.rs b/src/frontend/api/mod.rs index ea52f54..0150c20 100644 --- a/src/frontend/api/mod.rs +++ b/src/frontend/api/mod.rs @@ -3,6 +3,7 @@ pub mod auth; pub mod commands; pub mod config; pub mod approval; +pub mod caps; pub mod cron; pub mod dev; pub mod file_watch; @@ -170,9 +171,13 @@ pub fn router() -> Router> { // Config properties .route("/config", get(config::list_properties)) .route("/config/{key}", put(config::set_property)) - // Plugins + // Plugins — admin: manage + access grants; user: own view + own config .route("/plugins", get(plugins::list)) + .route("/plugins/mine", get(plugins::mine)) + .route("/plugins/pages", get(plugins::pages)) .route("/plugins/{id}", put(plugins::update)) + .route("/plugins/{id}/access", get(plugins::get_access).put(plugins::set_access)) + .route("/plugins/{id}/my-config", put(plugins::update_my_config)) // Roles .route("/roles", get(roles::list).post(roles::create)) .route("/roles/{id}", put(roles::update).delete(roles::delete)) diff --git a/src/frontend/api/plugins.rs b/src/frontend/api/plugins.rs index 4dda590..0968ea7 100644 --- a/src/frontend/api/plugins.rs +++ b/src/frontend/api/plugins.rs @@ -1,16 +1,36 @@ +//! Plugin management API. +//! +//! Two audiences, mirroring the Connectors split: +//! - **Admin** (`plugin.manage` capability): enable/disable, instance-wide +//! config, and the per-user access grants (`plugin_access`). +//! - **Any user**: sees the plugins granted to them (`/plugins/mine`) and +//! edits their own per-user config when the plugin declares a +//! `user_config_schema` (e.g. Telegram's pairing code). + use axum::{ - extract::{Path, State}, + extract::{Extension, Path, State}, response::IntoResponse, Json, }; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use serde_json::Value; use std::sync::Arc; + +use skald_core::db::{role_capabilities, roles::ADMIN_ROLE_ID, users}; use skald_core::skald::Skald; + +use super::caps::require_cap; +use super::guard::AuthUser; use super::ApiError; -pub async fn list(State(skald): State>) -> Result { +// ── Admin: enable/disable + instance-wide config ───────────────────────────── + +pub async fn list( + State(skald): State>, + Extension(auth): Extension, +) -> Result { + require_cap(&skald, &auth.user_id, role_capabilities::MANAGE_PLUGINS).await?; let plugins = skald.plugin_manager().list().await?; Ok(Json(plugins)) } @@ -22,10 +42,107 @@ pub struct UpdateBody { } pub async fn update( - State(skald): State>, - Path(id): Path, - Json(body): Json, + State(skald): State>, + Extension(auth): Extension, + Path(id): Path, + Json(body): Json, ) -> Result { + require_cap(&skald, &auth.user_id, role_capabilities::MANAGE_PLUGINS).await?; skald.plugin_manager().update_config(&id, body.enabled, body.config).await?; Ok(()) } + +// ── Admin: per-user access grants ───────────────────────────────────────────── + +#[derive(Serialize)] +pub struct AccessEntry { + pub user_id: String, + pub username: String, + pub role_id: String, + pub granted: bool, +} + +pub async fn get_access( + State(skald): State>, + Extension(auth): Extension, + Path(id): Path, +) -> Result { + require_cap(&skald, &auth.user_id, role_capabilities::MANAGE_PLUGINS).await?; + let granted: std::collections::HashSet = + skald.plugin_manager().list_grants(&id).await?.into_iter().collect(); + let entries: Vec = users::list(skald.db()) + .await? + .iter() + .map(|u| AccessEntry { + granted: granted.contains(&u.id), + user_id: u.id.clone(), + username: u.username.clone(), + role_id: u.role_id.clone(), + }) + .collect(); + Ok(Json(entries)) +} + +#[derive(Deserialize)] +pub struct SetAccessBody { + pub user_ids: Vec, +} + +pub async fn set_access( + State(skald): State>, + Extension(auth): Extension, + Path(id): Path, + Json(body): Json, +) -> Result { + require_cap(&skald, &auth.user_id, role_capabilities::MANAGE_PLUGINS).await?; + skald.plugin_manager().set_grants(&id, &body.user_ids).await?; + Ok(()) +} + +// ── User: my plugins + my per-user config ──────────────────────────────────── + +/// Whether the caller is on the built-in admin role (admins implicitly hold +/// access to every enabled plugin). +async fn is_admin(skald: &Skald, user_id: &str) -> Result { + let user = users::get(skald.db(), user_id).await? + .ok_or_else(|| ApiError::unauthorized("unknown user"))?; + Ok(user.role_id == ADMIN_ROLE_ID) +} + +pub async fn mine( + State(skald): State>, + Extension(auth): Extension, +) -> Result { + let admin = is_admin(&skald, &auth.user_id).await?; + let plugins = skald.plugin_manager().list_accessible(&auth.user_id, admin).await?; + Ok(Json(plugins)) +} + +/// The plugin-contributed web pages visible to the caller (menu entries). +/// Admin sees everything; everyone else sees the non-`admin_only` pages of +/// enabled plugins they hold a `plugin_access` grant for. +pub async fn pages( + State(skald): State>, + Extension(auth): Extension, +) -> Result { + let admin = is_admin(&skald, &auth.user_id).await?; + let pages = skald.plugin_manager().web_pages_for(&auth.user_id, admin).await?; + Ok(Json(pages)) +} + +pub async fn update_my_config( + State(skald): State>, + Extension(auth): Extension, + Path(id): Path, + Json(config): Json, +) -> Result { + let admin = is_admin(&skald, &auth.user_id).await?; + if !admin && !skald.plugin_manager().has_access(&id, &auth.user_id).await? { + return Err(ApiError::forbidden("you have no access to this plugin")); + } + skald.plugin_manager() + .update_user_config(&id, &auth.user_id, config) + .await + .map_err(|e| ApiError::bad_request(e.to_string()))?; + Ok(()) +} diff --git a/src/frontend/server.rs b/src/frontend/server.rs index fe3a385..920e74a 100644 --- a/src/frontend/server.rs +++ b/src/frontend/server.rs @@ -84,9 +84,29 @@ impl WebServer { // stateless plugin routers via `nest`. let mut router = Router::new() .nest("/api", api) - .with_state(skald); + .with_state(skald.clone()); + // Every plugin router is mounted — enabled or not (they must be safe to + // build pre-start). Two shared gates wrap each one: `require_auth` + // (outermost — same session-cookie gate as /api) and the enabled-gate, + // which re-checks the DB flag per request so enable/disable takes effect + // without a restart. Plugin responses also get `Cache-Control: no-cache`: + // they live under /api (no cache headers otherwise) and the browser must + // never serve a stale page fragment after a rebuild. for (id, plugin_router) in plugin_routers { - router = router.nest(&format!("/api/plugin/{id}"), plugin_router); + let gated = plugin_router + .layer(SetResponseHeaderLayer::overriding( + header::CACHE_CONTROL, + HeaderValue::from_static("no-cache"), + )) + .layer(axum::middleware::from_fn_with_state( + (Arc::clone(&skald), id.clone()), + api::guard::plugin_enabled_gate, + )) + .layer(axum::middleware::from_fn_with_state( + Arc::clone(&skald), + api::guard::require_auth, + )); + router = router.nest(&format!("/api/plugin/{id}"), gated); } // Serve the data/ directory under /data/ (accessible via URL), behind the // same session-cookie gate as /api — uploads are private user content. diff --git a/web/app.js b/web/app.js index 14507ca..b066d70 100644 --- a/web/app.js +++ b/web/app.js @@ -14,6 +14,10 @@ import { RolesPage } from './components/roles-page.js'; import { SharedFoldersPage } from './components/shared-folders.js'; import { ConnectorsPage } from './components/connectors.js'; import { ConnectorDetailPage } from './components/connector-detail.js'; +import { PluginsPage } from './components/plugins-page.js'; +import { PluginPageHost } from './components/plugin-page-host.js'; +import { PluginCatalogPage } from './components/plugin-catalog.js'; +import { PluginDetailPage } from './components/plugin-detail.js'; import { MarketplacePage } from './components/marketplace.js'; import { CatalogPage } from './components/catalog.js'; import { ProfilePage } from './components/profile-page.js'; @@ -51,6 +55,10 @@ customElements.define('roles-page', RolesPage); customElements.define('shared-folders-page', SharedFoldersPage); customElements.define('connectors-page', ConnectorsPage); customElements.define('connector-detail-page', ConnectorDetailPage); +customElements.define('plugins-page', PluginsPage); +customElements.define('plugin-page-host', PluginPageHost); +customElements.define('plugin-catalog-page', PluginCatalogPage); +customElements.define('plugin-detail-page', PluginDetailPage); customElements.define('marketplace-page', MarketplacePage); customElements.define('catalog-page', CatalogPage); customElements.define('profile-page', ProfilePage); diff --git a/web/assets/mascot.png b/web/assets/mascot.png deleted file mode 100644 index 01b714021eddc85af60e1ab41234d396d92fdf52..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 283911 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 z%0A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%0DxE%HCp0(0w-XuvhT}M&Y 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 -PvF+0pTf?3i_>-y+ zwQ5#6HklqWKG&_r#C<~MwC;4h3rtPkBKY6oeLRk-Zz4?N^L~FS@~27=mn)LLmcNzG zd1Tn6`CC`=cl@p~BPanb4m(s=H)#MZBZUX4Vg)qG@N(?zx%jiM>YwasHU$*@3dj9x zkNDHzj|OK_jcRwXxG%dTcL**tExjRr2MeL-eaGlu4%CN3*uLdRLP?F4(%c9_PtTX zv}30{{KS40&U_xwz99S?x4RlXv!vR+=F;xEf1fH4I125Ib@vrNigo`06!?lq9w+gR zt*C}bju5q;VLywr#fAA*W|3f6CcN(8oTgg z+ck&8{WjxQlJYnv(X=EokrRFG(j4RR9qaLv$587Z3cP#cpMiJR*K4PEf5zISv>J2B z!X%BSX%~ZlMjUpp+083Xg3|gmu(3$+*~CeM{(4vDzwMLzHT)y-_xu#M;ystdZ8BXz zU+CIqyJMzBtw|gYzA+5ONTOku-yiW;x0Yw|$$6?Z%#?arH+H*p``Oor#K#keg$y)Q zU9Eku>t+6D$(|tnqC6F#>G4=afBmJX7|2~GO2m@y2Ol;#LB~>Sh}Jb9h+h($cZ}~p zvb47Qx-Oq74IuQILCOCBbYNGee$<`^@F#%2KG^tEziooKNsX(x8c z$KGrK$;qx7=1c>R%DbV&n5^p!?LHXYt1m9>@$>q)ye2K-T((PZGNM@N_L_Lo?QG+U z*;8V}8UJz@kY# z2h>zeFFbYaT+;WNZ=v&Xaz&N7b~z`Hdb?!dh}t{zSy8bKk9D3NCySj zuw0IdlaY*e$*d`C{8Oy!4WnM&NY`fN!=&^70LxZWBa(UKg(RN%9eAfdjQmj!i~Wsj z7;AO6Xq1~>#x!Q<4URHSGJ5ps#X%;4 z>M17}tLTjI_)ljiXr#P|ue>F5rvTcG?W9D&W!WUaHUY-c22M^gJ*zubkHZ!od@HW~ zu0d!X7PgG+GZq73$Q%X9&#o&@^WlE8XL~JvBpq$Tht(w$K~6k*9|P5R!`2#6oH;PXWuzF zs~V4u`~wb^6_1C!H2!bc;f`6ZB0vPY3d-D;EEu*&BO69IBvwuI8m_$pPp4^8GD{2j+^lF#M;EAEv!NX zhDlt^;YdUZSnd17;nabSbH!gYW2+phQ>L#cV{01hwo zJqBH8#CnW2axJ>N7Ggy*N?G=S*N#4JGsZe*qL%K`T|!f*0c!O0KrW8__d=F zf96+<{{TuRsE@=LpWrj(PY^Nl2*q;xixPS3T{nn$m5KGPTK%$XIXg4NlUFmQBP0Xb ztDb6k$;$vL3Pxi|1N=fSMH?}cHN_Un!8a80(G=ONvX+vY6 z6r~TXAY5~w%u`kY%5o`b5Odm`6^P>p@u#K0ScuLK=UO^_*o8yqrDrNIV0{faNmI$j zO(y#dI~{(b@t@))$A%lhsp9+p0PN`jSG5-hX$Po0?_bd9!@QF%NfhyCF~4Ou#r#nuRYS+Yvc2-vq8KO z6)JE+&V4I6Ee=ZYyjnj=d`$&}`tGG^cp4X5$qaXKscs7r1E}PTcN~vHT;s{&4+&}s zslwxIidHL#$arU5@-xtnPD?1Z#V35D3JvCFD_$Ufz`?Y2)t&*by2>t7z9&r+j1g zv7~Hv=b!aw0fXQxigudZ19JMlHtx0)RC>337#qCsNom{vAC zvp)c1BZ2ACq1S#9c&kQ=Y1Y+l;)+RBO=t^;MA{6i$0_p;oe$?#HSIz@TT*N2bf_%D zB8Gz89hn0KuulY@O-Z77n_kg@i%ZpKg<3X7M3C(Ya1RHLa6c-}KG7s-;W+#c?=fxk zw=#K3gb;AR@z14qp8)&}JZJF^(#~jn%WG5ii>^4@r-AsKQooP9H{xa^#-1HPaLWW= zZm!=pgt$qn2@XMlLB15LS0qTW+?`P6as;;qb! z=8z9Vzy`WqC*j|V?yQpaY!W1t0|>jiSej3DRyj4i=Nf+$taL`4nM=X?KAnAUErz3} zqethh&DF@jk~mNe;2ioB`Bt`_;LRrIM7r@!zNF&pYH~Cdks*DNMy&goj#sb;rycXg zRI=)R6}q{xy0(%DSsEGQa6tg}JoEi&mR>Nlw2@@C7dF!Q4;-?>+kwc&aB;>ujP@1F zRu!eKH>0=p^DwDSrtvj?80xyk=Z!wsa)v;V$u#chPXJ_d+x#oQ^__Q6y4G)RWwW)A zT;5AF3z)$qvmXBdLTl*{hf|9W4tR%LZBhy0j?g4(+mN|A>$@F@A5+H_<+|sFd?-?B zbsq=K0Z5)kYgxz&p+`J|Fi8X2sZ@uwl1pnZ*Y!E^_^MPW)1@fB=aGiFP9}8#~ac{Bl8Y^QyRn$dEKAGv%*2ws2reA6=W2I`+#QtW)TlhAH zY$+hJlZ_*El^P}9}`x3Q8{ zWR$k@%EOf^NKnJj^#+#8PY2lpDP3kb@G}a{3CB!;<#InNt)}SmU0y01Nu))~%CJY4 z*ak`V1JH6LS|-2*#YP` zW}Wdm*ssB@EfYDL#1c0IbP-~TRHYfygq`(6p$F|Ec;)7aHlpcmZE~*P!n6F1o;cmm zpQ-IxQ21k1)O9%YYa1qen~8)l#<7?6ln!-Rdyg05H-3Ng2=K#cux1J{p_E zKeShdG{{qN8n~5L9el9Tf9$pIR&b{YP4rFu+Mc!+9@h}5zaxS8!Ql;SK=^~IX&RQD zBu!^3+n$U+cN(Wrk=nVa^bJDo0$EtgG3VuqGRNP6UEjnAwHUl*s?Ts`jpWoNc2@)t zOOwbobHJJhif$o;RPgJ=9wWMW9$B`0hiptZ^{k~yX?M70ZBVH>X`_4WaGo0Q)~g4I z>~3`*3tik>#dMExZ{#rZV+;!~BhtHz%{$@T+O(f%_<5;WBVK3ASaLkAzi=oZgPam~ zbj5U+)*ljNv`8;3tmWNo(Eu+xt`~7XH>NrEHO)4=smVW^CW#_6UU7LXXKor?24mD` z9X)DQK3AdPdr9J-GPas&B2Vn^0lu5%-RYWwIk!>fiz$Fc-Tt!zOkgP;uyMvK6lwk& zzP9@;Y$&cILSVk_pMv66W?7MtpPM!+kZOEE!m}#MrhGSPC!w? z1fFq<=IpNZI}5g(Ou3RKcy=?#)Q?JbX=1Yqv zF?N*(<*Y9dXc|9>^movFQ=!^wR#FT-rTx0g8T=R;`gcUrZRL;2o&{mIm0^%-uZTVZ z_(#Pk)7i$>6QARRWl8s1@?(tUdYux)(SGejd}X410h8gbtll8~o&G9#qv7q@edY}d zQE6;sJe)nO#iJwGVyt-V{?|VrJbU1+JNAz8cZ4;s3-~8hmsz#F(X`1d?=SCsqLBIU z?lFyomdawjgYj4F)$s#D4Rfa1E~5hTy3pl-_fySr7dj@VVKYH{XCyAXGDwA3es${7 z&M`jC#ZLNLUH<*f2a{#Bt17XAx_8t3jyK{T!as%o02J(-P56uO_eR#?V18XoPL4u9 z-C{Q{;6-@Ph`->Se+m_za9JfKlR3azz<+WeY2r{%l<6!7lbXm zNn?8gO>&#XluL)#pUK_cI0sjD45V9Y9iq8j};&5~Q#a9>P z&bpX97A4wIO|QK8Z(aS7e{3HKO49f@;unQHTW+~44zFtV7X*54O~_B>T*j##C$Rib6;s#PdU8Hp)zOxI*S?v#s zN`9;SjvAR38T=BIes}!O&%Y7=)4m?C&~NRh({=j|Kw3y4v(&sja|4++kOwJ_zV)5p z{{V}h4m6zx&q$xeER6-aBl)&h3O5Dks(RPxMcgSN#lnXjw}9WysWy|RIVMdy-Lu)C zQ;*WUczBPFi@d1E?`I_Vc2z&+@8SOd1mi#8i6ct!*X=#=L&kbt>~~h0f5V#_IOmA* zx_icEACy)45uxZeUKH~e!rm&^H0b1g?-ib_A}MaU+72^coxTeFqJAWQ!9PAB{7?AT z55V0g#XcDETow;K#ixiC!s^B=SlL8=QH-mWXD2zYUhvoa6|=;CA=54ONq=G=7^HVL z>nvv5#5NKgthoma%6fBE9CjxaH(Y1Mv`W6 z#OBkD;H1}-Z~7lgYMvF+{6gzLh_3JNks~vG`-)t>YRXJbxU{f31AR zf8eM3tluV;`xAUhm4{EY-D&pYKUSF4E5Gok>Rh{KIuUpESk^63pjxKh}Df_ zJjT3Z%BtCaiGP`_DsdG>%N0`Il0M1O{vUWs&~J;ww!EHqLKyzFyJ6w|9?%abOSX)T za+c~prF(3_ruOld>LEo2sA^xeSNs&O#PNA=@xS4} zh1I%`5BLU4xfkg7bCLM%uR5kG4g1ws-+#*Ec}Ec99o;#6-M=<|hsQ6LJhc8Efk$7; zsrZA$+6RT~n^Eyjy=A7`s_uF98@S*_9_7dt`K_(X`&4+|HcyG4x0l4*=$!uY(?Fin zMTGi{T0|HR_!V=}e$0OcudY{8@os^6;#;A~y76AKbE!E#r_Ygx>0Hvr;Uo5&^k3&= zdR!rz)pw}lAD#aIFY`Xm_^bZ_1v>qNJ{aFVldAsE+Geq+43F%e2iwc6T$cXs+f{W3 z{pJPR6!4|E zwD7Nnbp0|V7!lZ6M+pA_$3Uw;JhBx2UP)X0 z%%2wckZxBin$4roejQqS0sqJ0=0ERvYSj5pE z5>C>{(yE(i+Y5q61oy8u6Pnhnxo15)EsuW>lx5j(y-o=}`@h8ez5T0o71cj#zZSY2 zuAgmZVx$g*VhqFcYV+yaCc2M}I-Q=m`#}6f*DMq*y~m9&lGp?P04(Ek8js0qlelb? zfA$bxsl#((C%>I2n3$F;-d30o<}w6 zv&BgkAZ)i4t)`Kc1EK3!u*w7V>suO84}sh1O+89UCUzbaP}dj-ot64s@g4#-$i_eB z6<~kzRyzFF@Pum9gWqm3iv2M7prJfMibx;vE3x1o`gV@Al6rjq07JVDy~c8heI1M! z2=VS|V%WTlFRpX_Ju9EQ0g$g^FRvz}Oc)*ax%#xQ*iV)r)C z+qn4O;+q&24L081+$yLz3_&6x7IC{Afs!$d^TlN___wUvOX3>~n~Q69vUf6v5%0HD zNSLO6W&tBrBy|gtd8`dcZDPZZL0XZo#w|<3R>ID0SZNCbt+re0DxK`$ABzevxi_G&&cM7{f=c@tfnm0cV z{u^mFhHY=h-gK;p(LrUmKkG_3C3kWWcaEJvJdSiC_)p@!e^!p)#Xc;GFZ8FhxV^l% zc~%yVC6kso1xkPvgMwH6+Kl)=;a`VsN_d~f!sh9%BxQ$9A=WF6s!>TmL#fUWMeIy9j}axdt)ZLYcCu8576}=?JFMv&v;{=28J198%l`S zN~F6m0J%ak58eDLHrL}PjP2~8(L4*`<+f<0osGiB<}{{7!v;VC5H~gh2F7^BQ;eIx zE&W*!RjWCjE8*XYH5eqmhRCg)FubzeHg>33yyCobyJtAStJ+tGd=qJeHxb?46*5fGe4&(c$|xI% z$87Ufhr_P{cshAs#r`FR>3(5uu)f{H3$@CwToIG(i--7YYj9UL4I*kHTN)0Nd#GFsds}96xmCztm3I&ZT9dfJhxrj z@ImS8S%XK?{4UFDb*RTaq4T8BLx)I#kB)Q6+`|Xjujop{XvH|mNnIT(E-uXNygXYf zGhZkF0KrQBe~Vf`JmbRgf9UzE>mLrOE}@kDdsoUo@KT6&{9Dqc2d{==0Ds$QsE@=f zO8vt=LGc`6SdsZxE2*j6a(jyFyhyon2=82;rT}6+`K+Xm5=-7PWjJCv?@{o1BBqom zB;&m^U?QHzWKOG`U{iLlps9=?@=2uf2dSjMi)!aMrP@6ON#vYkcj-tX$0Yug)vN;m z1GO*9+*6AB3QdG`IiaKj3NN^%^2e=6Q1%9oFVxbo3aZ%PW`Vn)Q%_!a0~BE6fzN!J zrjRk4Wd7;=sdlkH&Yl4y1khJKO-KyFo~QJn1yh=M8Q_Xi1pqT6AajpeR6U1Eb}`b7 z?c@qN0A`MTXe>{!(uzO;kid{gs}^@3XShkcw0(>lY>Sm_5yAGV^z~pdiU8ZV)>}~2 zB)@A|Ve?;ch6TbWP84<(r={v%Sox0?%tt00Ca&7v5ys>7r1d6CnD#G&UmbjN@OQ*R z#9jo~TGm!iF6Jjt@Z87VIb&`lQ|NgeK?j=q1K{uNHSpKtHK+DNUqRuGe&=e-rCr?Z zu)cip)Ps8GQl!^_~neL9{4}btER20>Goqyl_R^e2b(L%{K&`qz0muq74vt&Kif;<2Y~G2 z@UM!JFBABFFcoyoR#x+ESN@&u23{}sPUQapXT5zL@cZHq!Y_^JuzWZ1TEnWyGyebx z&baFZ=5dlDZ25Npf3SCdvMZiSaeAI);ZqL(0C=?Sw*LTtRK3xBDX$au3&zqek4P_= zy}KXwWdke!00JM4V(Xp|@h^sg&8lfqBo04ybr}*vC;PH4Rlhpvb>9tNY1gvZUfD|w zu@WSTSjw`WaCxfwUZJn}c(~GU=7Ai3_94u25B89Pt@w&cM$nsT>rwIz#G&dCY+vM0;%mr1Wj_RIelhWE{x#J6 zJ#S_-Wd-#M71}(<1YpITq~H_s;=RY>4~Na@j!zC=t^92eP>43j2foga z**>S$|`OxbnA8Hs_s=0Nhyo*z7s4O~w&? z*7=>X!qAkttaSbd(>yb*c*{>|EyKsTq?Q(K!a|7520h5HZTNTlK6rcLw}gM;Eb&IS zb@t2f<`V+~kO9yU>B!^ISI#yw-{^YedWM-N&1)!dK@0&MLC0RWuTar`F6o*^gK=l1 zc>d!>w!2gItu5epC{IxvFHwVm(~c|0&FEulMlTOGo|@|Yd-;9VJLvnAoK&p)oY%zv z01Kb&8(XKiS*EnOn$70~A2uYxL!PGq;Co`c)LI6)@`?F(V=LdD)z*05;#z8cRhETr z>1S<`7oGukV~x(ha0$mw!xfFDc#78MR!hwy*_paN&7>YZE7ij))acqs#m>^$wd2XZ zvc3UoB>+afy@^7OIN!(UpRIU=T9&qiELxPoa6>1Ya6#aYp#WE^{?MNim%u*{bZaEg zEwA2vMZ(2$$IJO6#_gj7;I{*c@s$0i{B;uU)3gl&4WlP!jvD}D`^Vq$#X5MJbX1h1 zd%K*OjxPg7w=G@m+UIxS{{R?xj@4k0%!WuyGR)Be+nJS%6OK+)bLquO__>+xWMR3S zmhF;HKT7kBQ}&to&#&s%8daXBr&}|RE;NHpi4~ZW%VZzvS(5(LKNmE+>1;11)2C=< zD&kE#KA@jUpRD1ft*rk5uOosO%$@457wCK6gd=?-3+-MT=oRJDCbdKp?Q&$ok&jFh z?O0d(Ma7in+UgCm00|ifAdHSj*0`Mq_Q0A?3{9bUm*KvZb8$0eH5eGg@%yp|x2%g@8pB%B?S9V^CB)!A?>3F)loNrR;|I4&npG*Pjjq0(E&gVZ#9=CF39p`o zmDAzVyfblrSD2apCtq+vt5h8riSseUe4d0!1Q3v4hZ_E|BHRpPAuk%WM%lauLN9~rc1 z?z|yoX=?zJ>`er69FLT`IsSjjw5R-dKFrpQKYc#Ons|Rp(QWig+l@|nR^{Q@Xz}z` zcJRxo-o>eegTSH>9$m4NxIUM&S`U-!G zq`3ajIxAd>qOr_zFi<%-X2}O9<|nOP_-)~D7u{Z7_`kx^-^4U1R$I%RDZsdfISE2k zZ&F6x+q&_ZmnBYuS6VGE_!WxJu-c$jY!E| zYwUUEnKYjg_03k&_gAyf?PI%=sF#5eJVHFXn|W6mQa)B5po3n``#OKY zN}EGVPYZt3aV3#*BewC)lpp9fC!qUEFE-L1?aC9+%U_mphXiA2&E_~C-qV^*+g6`l z7VFUa?5{SVR)kZMv)S}#-u@kcHgVUcYpB!hh{4AvwQ;@y@eYmR9V*{f(Dh4uyE~K* z121&g6nb!K^d|=`-mRDcniB+~B-p@LS>k0ErpomqlAyh&h_m<^4Ju@_!os%sQQ> z>HImVCCYL{QVp&@p|7EDNCO$<^H}VYekt16-PlR?yL50*%eZnnSBpuaT1h*MDy~jhj|vA; zeXG%o||fSHhus+hE^p`JL9Q1#eU(y`E2lcKev*! z>btM-t-k&KXXhM6ig=8}mODpJpHswtX-Sh#)BG#0z@QB~#@c-Qfa#amf1Q00ZslN9 zlbrh3&VRK1thc`fHEZ}(v+2G?^5Akjsa47Rfv=)$X6`hfSd>Nx{xO#PyH_mn zFo5MKL0(SBlSOAW$NHF=v(SOXZ0J7?{8Qo^k0(vjq>z)+ILWV}{4MZz!5Sb}T`*iY z{wewWE4R|@Eo^{}(%LwXe++08ewE2QY~GPMYGbKEd#dd4UkZN7UM18*&Eh-g?GW`= zJDc(!<6eiMe$L+yJT(xOOF1v52PLi!7e2sOr6>U~7axUE)W{38b`{Gil_$G6WsbyD zc23L4#r#?YFBgKNLT_%mPUW;Rc|jYe8POwp4bAfM`pTqnFWe;h80YG1}ir3O=11ZP*%g1}6=9Yt5B$2vUz&ani!$SH@Q=ru{Wo*CHG1W{AlWOBi8|$iphb)DUSK;!ln&^w_jrKg2WJ!31iEEx<JCyen}RZeT&x6E*9Jxy&_;h&3s!F8>8hhK@symrq&nLD{*IROUZHs=E; zBy{6HE@VCn_*LQm05|>;4;)2%<=jUSrL^jy#`aks_j}uDMGT}2l5vhX;V-ot%dv~A zsbnJ<5=dBZFgl*VV0u+1W&4T7IuA-*l_a&7+&9U6j?%-%Uk`L|4#gaP7F&&0TS=n4 zXNM^hw$LP3c3|Ho<=e0TMt2&T_v2on7N2cxq-fUGvL==+rcCVU0jM)C(z06(Q@nz59n7^|};CfuVflUKC8CsNg?5b4%W zD;HHIR|=(w11xx4k_Jfvx)?N%h29i({{V!CSbbV!ZjuNrTPeGLOsT-!nCeb4ax;po z-x0NOWoxJCw$SP6XFRgoy|_EUcMXq0fT}ng<23oNvI=z^-ih0}sW*VFJS%Rqc&}D~ zOC_TC_%S{cvDGu;^_xG-^Lq^&H<0GIo&FJc+ zs(=aYU0#bG2yUF#iP+j2opyk(0{;M>)$)h@6rBG6#njTj>{^Te07p$@4V znE8*f$f#$DEzF6O;~C{n}y> zQG@AESjp-s!ag!;TTehDqNMWFbw64{i`ag&Xq-$%Y5-+9 zP6jE#Q>SWQlpNrG6!ZX7v*rgU(-kaJbJ!Y;?K~|g!00Ifatn>8Zfj1$=6N@HDDAO# zHpR}+JDhg+tVqb~`PIukQu4>l7FSY5=SE|f%WQ8$)laBClnJA!(>0ch8IwNQ>sd!CQJZN9eJs7A;CQlsJ<0`+n*DBEd)9bj2hp? znoJ-n+H1w0Xp_<_l|E{F0$E3;d*_4xGycvW7A=u<-xW#WO;St`?9UT0@)?h4?$B+= z{d82X&)qLomm@9I^{(H*G2D2QU$M}w!D%lpqlH&JOE4eSlyK^#l3h+e?Alc(ls(qp zPx9FPrT8`Qk`IQTvY+|*`iGq>Tey&IZWM-6K?MLg_pC1$F}Bb&(mwMUWCV5P zv7FZ@;;)JFd?D}$ggh7Eo92P6G`!wL8*sfAFhL|R+PMQA>CgK^=$em(wB1L>{t}Ae zTH;s?`h3C}VhR<3Dn`cLoc8ZtmKOtyp&PZ#oS&1n+dod{*3!aO!cq2Vb6dCh8jxzb z(b7DN=3BU)PnJ8#grQdie5JWxz|?bF=&C%apt!VgkDA%Hs6M+J1bXn*(fA$wA@Lti zivIw?Hw$qRU8HlvaV${F8+F^Yo95#@inZ~7!xCEf-%*+?2Z{%rm~$QmFe&?ZEJw4o zp1-G0nT%?C{H<8!bbAXmzc2lz1=MCX+ZOW^leGT;3*Zy^my*POAY(Y65H9Iv`%Ni1y{BY-pS&o)HUlAL!@8n zs?e+MKV@?f$m2NWbB}KI4Xk=pwxnsA>9yY6$8`g9{{R9^=bvFjr8!e+>3_&^mpQ`z z-&&F?cxEL~rdV#S85e=`xaxOk~!W*vL>qU=r#=`=4A>RJ`*>KZHLnjpg_-du$RPIw16BD-&j^9>KeHr5QFz17^3h4ob%LH>2@ zVe0!ysefPAhuC4N)5OwMV62*Yc6%Ndt|QMhl9QaVAl7A;pZ48W+RQ1;w+N(~^kbyxmJZEA! zKOyKwbM8laaFqEjs%N76jQ|$X`cr#E^jP!Ybmvx{WC?ix6)LInT)`*5ObE=GEcR7o{O$p z>6RAyOxjErHul%eau#N7?n>?2SRR3a$8^S`5#;SOS1Sq`&W2lPxv44(JVCY z6nMy--NnmC<`Mp?;0~ib zMpTiC_D|Vw{t7>Hq=2>;IUa>@2NdyN$=|)^8C*N_#xvzh~EQsF{*ygUlS$pv6;8rcw1Xm zyU^~*{^I9!8UFGg!oIcmY5xEPVA1?392Ot7Pr}Vl!KH{%;$0*iOGcRg0DH}hZ-^g8 z99MPX5BMj)?9Ka4=r8dj_GIzhhJkl+my~GAF0`qf^NFo8t!&4?mQaqRO?=1kAK@>= z-`T^*+K0n$70C^*rDFtB%9c}k_E)yjf@768fxR1R0<55^Rs?rGCNqXOLyKxkJg$}_ zs#-r@%=&H|;~XAwFJlQf)mzh=*G=u|_xT^9{{RU7K71$m&!We!{3`g1qxgc-L=EQY zSLrt2_mmyW_<>!-T9jmd^S5gJh|>NZd=K#^urmQX*Vk0-eidR zZO^LquWs;d_wDcCy&-iEf&T!uKAGdKF+bI59}_eY=Yh}3_PcyGec5aC%-_OH-w7uy zB}F*-=>Gjz-(&7+RCRVo=yAM>mxIY3l~U$#pptW%_>W5czJG1+1V}CZE&ju@>an2t z@B9hkYkQ?(>LQe*{uR3q{1wyS_LsIFiGK)wC-_oAet+#>0o_R&{{X-!k_F1lC6(RU^>w$nnqc0ZI})#WUkHdC{qKl}z?z@?$R3zJ@3;;`W-v+*1{j@$P>K_jN5}yY6N^JP2#`B#cLXAHBYWz=e?5KqE>P2Gs z^Zp8d@b6fXQ}E~Ehl_kgJ2CRvw8Ld?$Ni}%#~(mz$NmI(*WwTDDf?G=vt9VFqgcnN zXucb_eIht6i$S76B%~OJ9LU3#Z?9_m42Og>Ty`P~ttmpAYVJzkcbT>;G{e!u^3!*b z=^hU7hMC|`2Wg%V(a?RHO|(cOIKds)3*qKp&Xo# zTIYOe@z>z5!ht2M*W<+6qJlobWI%yikG|mg*Mxjg{iS{)d{EtCb9$O?mOfO{^z5Pq z;{k}}x>xSiv3MK|6*cx!NiQwE56>|;tTq|*Rh8x0YW{CzZ+_IDAAA|%ABUbH@TZ5N zO<2C0Exq=jf7K~v+l2o13G35}^dH#c_LcBQjXn$bSK)_>wbau*VWDYp+TCe7pz|0=o+$V);H`U7@cotW zpGAN%o;*l}V_s3zhEtsMHS$@GNVuuatmnxnCCjFkZ)SGn8Qpa`&dS%=`<~X!O5$ir*H#9hHya_r`A!>5!-0)Ac4zO4xe^m~I2;)$2Y5{lEVJ zXnh@0-&xSS7vc@021S;AI%fKVy)$10jo^&-oBi5TUQhIPKIVpdgPOFinffK*4L;J< z(nW-BW6nn_UWupe+7Nnm&3p&o-}ot?jcgT8tNSnO6A{(4J$l;YegTS_?=;`|D*l^j zpYb#OmVPQn>-K*REW`7S&3yfS6XG~;g`eGj$cot9HCf$A`(D@NkYsh}D^|^U&#ysW z6x;s*!CgEc2l_Ycx$%}~?6G(jQ~H+0Scm=!vEyx7{?FkL*k9un(~*}rWc_$UvK6{GMM>}wv3 zt-@S*x5dyx7wV9PZ^FE@;#b7)+k3<&SUxWP(w-miWv?P_GvWS{XS&8d@7k`86#Ip* zpul)>#aQVj?0HxBq`xcNhbK*5&+h*KGwbh+e;)o7{3g)m@ejm59BKMZoMS$9^}?WU zWm0nI&;UuV&JXw~-|czhAKFXdQm25l*>pbx=(_c#%ya6|6uR*Y#!aeLF0n$n$ldcX z&jS@(Q1G9Jrq+hJqiDLOx2j1psPPYqucXwkrjdvML>nrElft5dRKIQS+0)|J!(WCT z2)p>NW&MZ!z2a4p-@~_0{+%RHz|wj53JxJ$;4DYxl3gBl0oP%GczCBi3=)pa7 z+aiDP8ak%6Uq8_F@V^AUM;$HXHYqyIZp>wf#)V|s0Y1K>xqVH^&JWYxy0*Q7%v>zC z-fx`=jdDlIPkQDxI5#Q4>MA6)H8MPBtwpWNYAvjU329*!vZ#HxYlzk4^8ZKB<|WD+m#2n7kiY>|z*<2@+<0EJ`X`CUHMrVCVX%&&7O84Dkp zNC1-9PDleIAaym1ojB=nDpfiok(MUh0(`C7j#af%(GU}Eyo8f(bTM|B#quksqAQ|2$Nm#)k0>`4{kOpy+ zUA5PZei>*MHx`}{@YIuBT+D zG;3HPml1i=N+Bpvv&oWh(WwKVFb}X z>zd}hE8J?DoYORtyo{{)8{M}v05};uoK|$Uj@)AuDs@_1^81c?Qk34Zvhq4zQ{pb3 zb(YjTC!no_VhG}ia;jHj?%qNH$-qm;mb-D{7Xwn5#|WNTRx5PEp^>*f(VpkF zaafB1M#vRn5rL1=wuEC9WL#)drmT?Hw~jg3MV*0;%#0j%%~`dKC>bA0eV(6ps9b%X z#?|JOjQMdc5atF(k>+M$O6}u`hlX?^_ zk)(A*LRpRg10>eT&^2u`)o=AHGXgsVPcVSQmB={HO22h`;JXWUw(!M`%#tq6x0eJA zvPe$vTr%TqhYI>cQhODH! zogqQ)=dfD&C;kd(Jg#WuG{{X{3582OnVbrgd7$LWNi-`%rlVITo zJmlaH#=Z#u0D_Wuvfp0)p|5PMnSbI@rQ2CbRPp8oGBE4d$&aWtG~4!`fr@L`v*mvh zFfQ0V`d25ZAYwzvNSrO0yJt_Mbe+?TCUOzf67D6Bd=hLklMsu9f{KNkM ztw3D%#(ikJ9+m^Q(vTI-1}WPPc^Sui&_K^1)QMOXijHaO+55bD(=bn={AySPWw7S} z_ssy&G)xPO`f*w|QjzjEAbRzxDPwYovn+d~V}Q$7EY~OH=~ZM-=H{5CqzZ?V*|;XY zi~j(EfBZU|$G;wI?k*uF-$S;OP`D*W1%6cP{?Q+meC?v-5tRq(IIp$8;F8u}Z;U)^ zbgh<855N>>jwU|-)!TuJc!;;q^s=f7u0EQ5@AqnZSBv$rtLpa-$%%f@L2Td!UzI@T z+x4q{5WIp-Cs5IK74rtJzh$t={{T~Q7!pSQz>~1?(d#xV&kkzW^4RLKeTHczZI>&P z5G4Nq80tInI#k{+@RqsZjUvVFqdr+_CKJHhqxp!~Lk==RycbqYg4_g{-14i zVcVx_^S>2Bm+%i8StO1(wQms_v6RWS515wy|l8aM47plTLnd|TmtrN|9) zq|6ZUoRb^m9(@R|=fKyV0rBF`KZQJ6qFZlKRO@bBfmJWDy9Kc zrrjpu?$>prvE|mw;T<6+kEV6c0qbx(NpGjT@pThhe)TqJC%l4=Js{RW@ z`$OyA6Y=hot?AR-m=pU(#Ur_ZgBqS=%DUq{vs~7dsQf4K(GB2Fi=H05k;ltzb#!d! z`fRuhlk_IMD$Y=yrKZQpX7!@6^&LpHuT`htv6A|ZnQ-K4o)puhCpmj`Yj*zt$Yd|+ zP)(U<{=;NEuq+ApTjw; zml|WL?JM~DR3BzBw@%%~9M&{f=*o-X&k)-;@3gyw134xJKd7a%G6{-F!zttugI17S zM<)AwnGpW~u;gR%t5Iq?i94sdX*ua9&;08}Q37(lr?CFW>2EHtuG>mRWzd|)J=R5U z>0NikjcV(}7b50O&2McQK^LIoC-XV4DEL9+cksuGA=UPv^1xX_r~#utdq04$Veu`b zA7Dn)+{tTl-gFK#hC(>cxas&;t$>noKCdUJr-M;nWxM>(I=rynNh7aXY&I=tq3U-) zV{}OhoE98&S9E+s;SEz*(scU?XTFO^yjW#Vl$2mwsL14hhAWZy zui?Ev!+Kij+HR43WVcssH`#RznGqC~kCF!N3FiYf+e;Znc*llc60>_-U!DH|1KWh7 z&&$;wTc%&%=$f=zuA4ii9Pz-V zi%fg_u>zDPSpu$DDCdq&Ko#iOb6n@BlXqz6p3`P_oRgej=lNBwIrJM%PCJc9N|NRX z&SbYYJ4^h&qo+02_>aLFidkwJ+IVf2+QvANX`zt3P_tkuEy}h@$6Dt#N3*+mZX?m5 zw(|mnNL{wafB_>U7uQdl5Uk!X4np_^ub$*Nb zh>dV&b8*1PoMYv~J9-|4`%>w8XNbHT@f%t37lBt>(S_)?nWl+a1ZZuP4f*A8#E?b~ zdK&oCRsR5jcl>nl&%&((;=jT9f8i*DQ_>;6)O;tnCGDq}yZ->uU5+MDrGIwVZo_qb zP5W4QlUmY0W^WMB&87U>H`1{-uzdvYM&Cm5B|@72GbA4 zUma=M6^*U57FN%FA>A{_7+sOZpuCNR&J+_|xZHL#EulqF-)_33lKk#^`I>lo7r;IX)|rPwwOEw7^KT5l^)#r%4 zy@!r`Cwlgpour6xVrf7OcAl`#jJ*BcYXjnk?4$cm_zU)5z4#B{Jr396rj2C^w})j*YrtK@~c)cFJTk3pnWeH0+ z!ei*d)2Aya0b54XJ3lb9^HfJ^|Kk@8DZ?K7F0S z%CfF^J{buaHTp$tW*-#@DsxRqtx@`>TH#eXk!h=UJd4L46FgHj`Ig&TxP&n*Ryi;i zCxAZ;S2cI>cj6_20dM0g@G)#lBe|5ai;#pkhtEfNDyvN616})3T$h41Kmn7wig&)ebHO);DCT45^ zG7Wj`;^SA<$c8fQBjG@=3l`K>qIsB#vgO>_vD8yU@W!d}L&o}quVtlMJc_bNaO?*_ zJM(}(wZ{A~*L-87_)k&PH7!!&8=n>3jYC+1IZB4eK2%MElBkCm>5_P>e;@TbU0+o2 z_rPr;be7)OE~Vl>ErIz^<~W-j3qLsY6%ED686AgjO8E@q7cXk4!TdY=zw$m)H^s>{ zMi0~`yUSrz01p6EYjShPdal!C^XtK(To>SeRp2*cz-Z7uUEQ44vJaM6nF#gBBD(K} zC;NZ)OVDQ+XVPYt)=yOlBQN+@8E5klKRWs^_A>p9v>%JVv8CU{p9y?V)jT_|TKJn$ zvDPhSj9zKiN|T+X?amrD+R8UZ!1u*^S!O<#3Z$J)q^x?mP9_+6OPgn7=bP9^n}9G+ zdWzgC$`y_TIDi2Ex{kwxDN&0Sn4Ss_I9Ihah^ej;a@+IuKP{XucO4DW1F8y z&3zsoUgEPo3U}ys{vYu#iLHj^@g|@$K3rZu_}5e6-xqk2ElwMT*RNTFjE1^$59Vu& z(Cu%gw?NjCwGn#>bJMi!3Bh*m*ut`4|??}NhPW1MKqD`eg^o9sLi5kiFq%X z_9X^6PB&*^Bp=GYZ1{v2 zz3uWss=F`-U4?iayW`IXc!R`pX`c+5_r=%S1XlJw7t3WCW_Vm>dXq}^WcS@h z3uKu{Y7*4sTobJ&m7j`YRO{5@&-E9-xF$|+uE#}lSAe2L|wbH z_fRii!m;ixw=c-QiL4_^npdz(6Iy%8nL32i-AdONGPI6Zv1u5RPdPb1k3RKIK-tGn zVOr8!jP&NK+*trJ6n%YbRMIl$xtc*LOY!MXkq%dm)nXkw=JG<8wz5i9OE8Wy*iqE+ z&7C0m6 zPH2e)5!Ce}y#vJm01JF8;7j;z^?w*gJ0yf$!4}^z?U!SK5Vqb1<;fYz^aS0D;!nfx z2&hko-URa|Q*b59Q@^*DsI&{Mk((Z^^D*S)@mUOv#}$0FryqIS)BMh;y)Jc{ zhs0en+fTXCydB}FZ6LW1%K(eTbvh4(XPJc8`HC})FGpeh zD`f4tQCAHINx8n1MGScW{LFeU*ZeEcd^x4t>Cl9SEX|Gbk;{L8_Np3PlvdF!Ht>W+ z=%bmDEv^02M%MH{l}Zjc+Gr(v)g~8YY6#?y z&X+v!IH?BH$fRI1oZ_YG#k(1t^rSf?dwW!b3~(t5kM3fG8VTj8=-;IqGIAQ9fk;$V zjCNqExaX5f0^@0Hi`fhTFRw z6ZQOS?4S4~maLHcUDxynkPq!24N0+a)W;&XAAvRTzlA(WbEl|+XrrDipWVze$rvX! z^uPQQuf{qjhQDb~4$hA7z4?~TWs$yL-bO&d9;6N{uL|61qteQ1I(Yhg-R<}2eHRyo zW!3y$b73?2R<_S1m-h#xFrhn}>(~zDfmih3A8Qcn_qW!tLp7E9h^{p2$jp$u3xJBl zIR!$DGLS(W8sGT4rXLF3Uig1Vfz}(_zqH=m5&pF2DH$X9S2@A{E_({%bi&ZxuD}Tg zc5&0t*V3k|E6Hil`ix4CKPO|N)ODM}ZwsYMiWi9Sv$atl}T&r-(bLonu z@lNMP*FGZZmsgV9!6IFli(5vm84-aPz~ci8(z{<7d}`MIIDB95?zyk&*4CoJD@#Oz z)DVFp`HAHBJB~UG;8&E{z4wXyWfhtoA_LCNbRpI>?~vaLQ@)?Bht)hByj&*V%K zP`lje{C(lgTU0t9hqMwVOC1U%khlt0b3E?M21o!tN%pQv_fpgR9WKuc%(r);er;Lf z!ouCeDt$Xh-1ER%>7>>5-GAa%uDTQ@;vRHOFPFQ@wTx|I82!<;0MET<*y(yr`lM0a zn`@SBp)LS;+a*Bfu{rNrLJyU5YuE4BQfkUE*=wQ1TwPvWPWP9$Z7h;8W_cTNcI%Q) ze|q(cj{*2o!oD@|CcWV8Qs(PU)|T32d)JT6^U$i#@}qo-<`NVT56XLEJV%SXFLkDB z(%WjWTSp{LV|TTgSP}u)JrXYsHdT=zZ5 zsFSHG(3Bmct@}MRIH2lO#lyArK1crmf`DsV{{U;h8YzAw)FVOHv?wdcE%moTKYyQN zUV-~^-`q|6SNP&%#&wc1SFR&L-n^PEM&dT&P<^(F`D|XblQxI**N`)uU1OHLW(@>m$&!rtj9g zBIjDW7fcdv1~54Eq)mS4AN9YWt`BCV(DLeGXw}wGPvm_wrvBX1+^Am{{2uVt=l7-4 zX1KAC{{Y`C>Hh$Z*FmIy)c*hgHMJvH@b8IqSr0=D&|gMB^jl-_HS%13Ai4XdW9`7H zqwxjSM%lkXN3n`Io?S=m-Fi3w01SPfVdL+C`t)krFU5Np%MK&d?Pa!M^~#a@)|?ia z72;=2@rH$RI~X;XGJgWXz9o;u*UR^AdU}CYpz(e9Tr|U-> zMe^=$JjO$}b}~=D;a@g>!JqJ1J%8XXoPHbpWYV8h(56t3YL?{vrpSTvk~8zDARk^U z+B{|B%@^Z?&EwC9ek!!rY$syKZKmAut^f*Gs^AWWt1Q9dWuH~Vzk#J~ciX2!>?p=K zijJ*4SL5?X3MZ3K(k|I}#natdrJL#2 zyJHF$5#WG5{qR33`eeD2=2pJ?lTwtf^g54>KNs!1VlK3=3Fx-^R+XsU3%l()((UGq z+$YGg0>MfGnE6Ks>t1JlXo`yJKKE5P$g8vJTiETfgk`co1pXBT#oU(*HsmuXuja(mVeE62o+@0%A+Mb(Tr|*)!aP>DveCUKRUu_=Ckd zZ|#_u+LIVR;*7K&0=c>XKqWFu_Qs62%jSpKyZE++>_vP30E{gn@fYoj;Jqf(#y1-6 zkA}P{b9rlbaKW|@C`PEbBn4KuDnH$?ptR3}e++&vcqn*l;<3N(vL$JNQ)1dP0FZH-0lwD02$pwUM3b`3PE>1;#u?dz5M9@gc!0^a$ zYZKxB0D-(2@ZZBWo(=eY;T<+EDf_+>RNnP3OMPqMIiU+ zE5JN`{{RHJ{{Vt|c&}8KTF|~3>s|oV@Eq>v*m;)#X0SXj{{RHU_#NT+@BB;gd-jC!rmLiB7LwiS zji<$Lq(UH#3%M;Ac>uzmj4SQ`0FD~8S9eiqh!^)`bB}Y-SH-{ZRnLnu{5$=g^#1^W zejU8>_0NfS-c9I@u<+?HkoPa_8|93Dylc{iYnkGry`@&$`3`ujM3d%|J_OKgJQpst zuXyvo{v0|^o3HB9+-VjsaPvGC0gBtJJCe-B0A?ieSk=pk6bzOb?afMN`y?xGYKt6k zC`iyK!l)PmsoY=Aaq|KH009-!-*dr~r8=YSouY&=@m5g|PU_L0e-`{@mQFK>Bt zI1i6}3i}8C2(zt~__Og_!4pZwp`+<~fL~mB(6Jw-Wjm-dTrK^jkE1+0cO|~FZWV|P zGH%CgoMy9r21y6}Dg8IYdajXUsM*7De2r(RSw}p7@%uhT*92CCR^rE9@{a}^t-q7h zKM1WB(?z?}2qWy?Bh{hjw%hsYYgwbzM7JNZN9;lI8tfk%{>Ppens+}v&W)#=Ni*yI zj#9c!2mT2w{{RI2*3oV>kA_|=(lRr$$HuyqxL=_2Yt#M+>FEvZ_c7x*0N`_8y=mr1 z2R-@byo&hDMjm{$rzrYKmF*_BZ|^>Dw*LTvKmNw?&AuIf;+zYQ^-z3EyxaPdT}Oq# z;F9055?rhoS{KAEVc6hDsQgIOU0?7?P#X7`Vot?lRk z0D(XAGnH!6UmN?`$9Nb138(uR{5?O~ULO6Ayeji2%x^qFZZGBfmbrNibUK!&9*1P! zAMk#Yr)ahjhX82C>c0#f+I3L{=`c?5}c~6RV1NU^> zF^1>$XQ9r7qUTozNeJB~uJ zz!~{^Rf&ESU+L4a@gBKjWehTGyOKFP(-6vxfxv!PebR6_I2B^+#Xcy%lYF}6-tiiy z=;bKPst8a@;~)Y@J&ED+N;XZ|P z1b!ayy~XrtcJfax-W1A!sf`rA;-F!J0x;ZU4r+UE2zUca)!Hu;__{ZO*77vDOGG5x z##3`NY@4%&`FncjwJbg(_*rQqey+w=spJ0u6l)fu-^1P?OGs83q0OziX7Z+EzC7arPUQe&ji6?s)Aa9% zI{Y?TO!t>xW|jl?NaeUI5sp3y3Og@727Ljoi?1AbpH`Km(R5L6g7D25wuPLrjsac-K~j(i_wkv9;=1KrWm|vE*Mge50MDFgf=hN;xP&XpG%z&EBUhe;@KP z^)Cr%+Dz?ls`!%Gq)0=N18xPp&4xU#?m~jyx&eWjSv*PMJqqZrhI9`*ZNoG(UxA&a zc7!T2SPoAl9Q#(ZKM%YYrWcdNUOJCEt|E>bRKssY9$bexK3kK)3Qx*-!K%7$z2V;v z*{!~vpe@DA+B|nRF1Y#F0thv5>_D5bSn)v5!5 zX_l7w9Rc+is@^>Kk>EdsdJ*v#kF~h%B4B>aX%G)_bi?>!OyvInyl|trs!6tMWfgOB z5f#0>mp6083=l?Sp5k{297qOBvo`@psjrlOYoFT|KO9-yd@T53c*)`Y+jTumWnWYv z{m?q30P};(^*mr#7yCzk*Iq98%3DCw(CGdhNjFEN!!BBKIS)IJ^^N||-5r7JUKOfq znFJ%}J-uqxqH@*5U2?>qTGmSfq4H~$)b7i%0sGXNhNR9;4;kjNZf?w!Ugr~JH!@_M zr|VUi4u`EvBYfC32_qy_y-3_^B9V|yHI;zK8K?B3q*|gd8@Qk=Q~Gd4CJu5If!;%M z!>BZ5=dC{{sajkE&L|7AIx$ZGXBhn`+J~+`I(8Z(#yAu9_ z??7ylM_Nz;;EGz&?k+|!l16`80Bm~Zo~xcfrP_K5L2EGpphb5a{Si#OSG9ykOr=yTGk?E$#;{Od-;RJe{Ox0o!8Dur_Y0DA+_ z(@CNnWf!KU?J2TA73jYSJXdqz-x_INDYB84O*;M?gCxQjlbEkyXr3kk(ea%6R`jK5CXFc8TvS}jT%A8S_CoNC8uB+Ht9o}L}?LR|f} z8Il`WBuQX=gZqXfuOQ^(9@Urdj@6;~k*8kH2l^?JPe&6YiV zYir?Jk?{{l1abD%u9n?}Mk(SV{L%6k?V)>f5A8AISc?LqTYRgaVfRg{nYW26Fq~kq4ga-#d_E6(;t)jQuy5m{RZEU_yX69$j2BxE9G&2$Cs({nUp`< zD%<&==2X5}ZSGzRh|0)}Lm}!2s66%uJ%1X|@q-7|gu@%8wuRf=nAbIrxT)c*l_QQG z{1Yv$$2|TysTSw9O;FrH`A-z_DbGq{aWnr^TtbP*wi`4Axzo4zJ>wQ<>plzdMdq7wr~PfD<#Y8N7=3GnOS6n;ik2I**FXJw))ggAT+w!Bk(}wvnl8-x zzrbJcQO_1cW3l+Z;M-3SKsamLP$0Pj)Z^u2`B$;{Pxg)dm;NP0i=})-eG2ja0OiYF zh{gf*4t`Vl*XN{nrAA9*+N?!-@;E?qlZ;oTPd3CwT4_u0e}U>!&M`4osFatU{{VVF zP3^oFbE(2E{5!1a`jjj(vRU0Lh97~brSMmYV#*yBQ|q?}HTlJ)c$ZtyrxIxz#nqgP z{8qOQ86Wq_71`*Yw5P{i9x)D^@o!&^SD^mVcE|non(KyZhHKt~UXJHd&9E=xrq`oS z^FHc_!2TV7r&fG*Rx3(nZ*wSB|?00i5x@kO8PRq&5c@ZW=@*RQor zXI0my(=HiRZV{)PmSTA0a<}1J_`F^p66wYX**m+tUdg@sZNEcIJ|hi|#?gf+y(M>l zBTvB6_*cZgwMUISP2imj>H0pI;j3FMGRVqb$VskQf^H<|<%iVQ)SnID`#z0qxaGOW z)SCH!_GY#kmaF5>70GU~HlgCgwg`iTSRyBA_6Ad5S9nU)Lu~{~HtdXlD)~MnX=CLc z)>`WHOQ{*PPidG`Yq{3hl2kDtgt-#^r0=``Go3gp+ z8btAEUKom55Bj)wkb9F}e|vKGR}XV564EhdC%HV=TjOi!;nSnAL-(?@0z{$hPN z2D7HGciiN|C(7jdklY-M9tUc{@fM*Cjik3SxCuZ zY4gSx_oPWC+ARkw+&r2;f&qLDS(X{CHLu(4f8jnCRUr&Xm*S+#a6{l$O z_ZfF95=P$rGgPI1Gmf=pb|B!Irz>FBX1CPkj8F!0a7I1qc=Nj?eQLUpc_yqw*p9uZ zv0R9`Y!?9I@HO;T{1SV_jp6UxvP~VNDw^-Z`;^*$iJ-WR$LPksc7S~29+m2!u)elD zGy8jdE!3Fdbs4oSDcJP=?W3}f`{@M|ig9D4tuyo!#}I#K_|H*@3}W$^ekV0w!2Vo1 zmx$p({;$Tmxcu&!{{Z%C)A+=TZ{w??qXt-`lc%bW>sdYw4!$GtVjkWwyK(e*;gkOW zX0B;#1JO+P{{VtWo?Pc{4p;eCs%gpqIQ6e9_$mt9O`q=y^*uO^ppKcZp2hzF6_DAb z5CZ@n-D=c45wslUs~`XjRjVQz;yCfufX#5WXL}pp5w3P<))oNZ72baUE6#Ok2;4j4 z0Q%QuJLZ_FDxO%a3B{=kc#5x*(J9Utfi%?6A>~kr~_iAJ(75OJ6{Y8DdVpd`d{%{GTtzhANlgM{{Yfx>+@H_=oyFM{VVjl;)-X* z^5lQZrLRx=_l}9JSI_i1seBSSh$XRx-dOHTd1e^h4&q0#?O8HH9Fe!LO5B1gXt#^V z8TY8#5=?jR`gE-4wkR{Vk7~&$a_myE^I*CX#6EN<85!+Yw7nYJRF)|5);~S4G1QsFa_1{@goF3hgZr*Rcq{t zxCWA@-uF4)1=RTF-_GBVkMUPnx3{s=ygy+Lq&7CQKs@2+8InmvnQ%Z-RH}jU=8#q@o-QCP1jeW9upJB$YY2fRE19F{{VrJ z^sk(LB>ZIYr^J0v`&-6dCb+(pN%E3L#BmQqjA2vo6s)diDzS>wQ|PaYAGdyoqO7_< z!aXr>Cc$6rtwJKPA@zzbtbGgMf3j=l4<7tX@fVEsiTpw0jc(%p_H+Bm?j+pD*mh7m zD8L?-&FWg_a#gvkEA366c3YoXdmObY%3GGTZE(qzAC+QWUc8O-Jx6nNPYl`3RGLl4 z0RA;EQyy&w+}$2mIW<+E_aGI5@VnvI4yPCaScL0k_VMKysvsl*(Pgm=$s08l&B z*m@Dsnv*&0O)3sZ9MTIRhaZ&$fyt+U2<2%4D-02eVzAIb=}S)Xz#Mg;$Hxbn8W7$A zqmHyZXZS{GJh>^SWLnq{Ja_t2TPMFx)bB1q#yO_#VS$57b~051(w%|nO-_{I(zJV# z)G^Lz{3z?d6rot01**{%pqcJzLC>`>mifloJhD2}-8LEr%4rQdU>t&I+P;)s3sf?S ze#M}o_Y|&}HpX+FhJdHg{#5aS+Lo5AodyJMyGf)f$D(8LsY4J52d`Rb=clbbM`9v` z^~d2)i_DB+Sc(Xzfq)3_Kn!SNE-HTdv%$D;k@kmwB0ER`uN9@Ixd34PHLNXtLBcY3X!~#e37PSe!=4uSuRp_|wz<>%Q>Uyi4C4e{ASHhm zUBf>0^nZ$cCY}@V4aJ4@ijj#6KiBr8;MxwNmL! z5;I#!`!{@>hB)J?>FHMIwjyLxgNTP9*1R=yd*rrh}JN^3T7 z>w6hwZ%q2ut>=sUB$x9=uY5I)W=EA}Hdn6!EYbpUFhIb_0C0QfitD}VxhzqmXys`kUi(;$ z*zQyu*U#fFtF!0xUU=p;mHyNG%s&{$S5=H3`SOGR07Z(&D0>>)@rWl?O!l+4A79-q zW1o8FMm0Tks{>S;h`>-?$0JauXd-(gUk=NYLu{`V)S#WhCHo7S8gBOp@? zBuN1vzx{G*glRa=&9m^(?ZIlg&*W z#YRA?YS`pd@w0Kbb*7lCi_u&&V3YXO+i4F^%Cl|G*2w8s?McT@wMnxkiS@g|XW`rX z)BgZ0Y^8t64SuNp%ib07M}|LSPlT)B{{R(RX!kx7_%|M_cJ~j0;$+;&qtu2q`90(L z9XtoB#Sr=CvuL9OyFbbY>tCw=J+|;ChW-z08dkrnJX(i^J|5`$W|wCin{vX?7bFg# zX1cI6y-XYX3*^gERe=HyT za=xJF-s5&okQ7dTGhYvW-~Rx#P5%JEui4iA z9}QjX@pq2y8%6Nw4Tptf2hX@3(Z)|ctSi^R(Qn@Gv9Ckq58ChJe1EjR?L)5k^Gdn% zd^O>1-XHLm4pu~ozkhH)-;+H0D6crZI0e5dmrBuW{57Il=r$kftsxQa{_!1H`;(JZ zuH^?g9M{oCJh4$n6yH3qQi!Mtui?+yGvQZ>)=w|&e-NwzZ1hQ1_<|PN`ge(R%cUc>+o!#f4a<19#Sxay?QRKq z{{U{n*|S@J8ZJfdvu8SxL)iux@1AQHz_>mz@ew`b(&WxRy{E)K_FClEknN7gy?h)k z98xGfPeWd(rnktxro5}+Qv`#Kyz(p5bg4&_zTG*m2N=J2!J5j)Ja+V}HZlQu8DcOT zejL?U+j8gHow%v_dXDwyv}E-gtX{R<#Mdv?n3G%|#b1dUf5H!k9xeF2sXDAa6!4~@ zs^yL{@j%Rj@qMH5uEO?Q2Ir@}d}IFr1)cbzb>P3)t6%WFg~5)080vN!7y#gyt6h{aZr9ml;{m4gDrbgJ)@oKdngv#|kI z&T&eLTL5;a~<^fiY81lGQsSs4og19Ax+YLQVp-wRHE zD*a3NgW=e2JW4KPxo@7r(3J`}{{TmhzbX7Va3eVfj92Sdi`C}W72E!NZ9xA3zZK#2F5=rPQ+dL8D z4+!X<6N1U_Qqo9Jo0!U-&iUoI``P~hJuA(xJX7{x9zTlo?L9&**_J2@;A8OVnmJ!% z)x}`xP{P8bW}BBo2gZI7@h^t0lIK;@Wq}y&S1JI{ryc3u9{7KKtZ0p`OLUQCS>lb9 zo$52mBd1E}{CV*TZyMM~r8SD%TSq6DP`>7O$5MFUWbs&ZzmskmzXKqzF!9Iy6bIvurRLiBXI0buF9U=7 zuMXHOFN6O8O`mk9>$;kY=)Up$YEj*vR%$*Ty3q9h0PT+xPiv#tsL8z6uH&?e9>!7& zeE>D)o;UrgKWEPaZi3Uq*PbT2Oy(^P*vAHaJo&Ma?y76!9~OK}*Zfs}`oD>F`^`q_ zi7jz`bn{4m;7&=dcI(7uOp^Zqoi!fp%2yWjF!~G)5!xpE5HL)|@ zxaX|W3@`ivSL=5YUV;fZcjr=yKG@9vX-+c3`Vy{KA{ z+b&#Q`Jmv{3yHTA^JMoF$CZ!M+M;JX=Za0(&5f0}F(dV;93G0)_B*-}O~xujMpTRp zQwp&;#Y{ndn4~IknrcHL+I=ZI;-sSk(0)|zJyIgir6D-(Dnh6ZcGE~+%SkOngdVgE zb)#qRQF%fn&1v`%?cwNs)MNdQ^KcEDU{^tRMwA=mlSAbhw3CjM7=hy_+|sjAyCWyJN>*T&g9-)X zoQf3S^&pOdl=5j0(xt17lR)QiF`N)`J*!&FQb}NFHFalFz=<$76OQEb=~&359ysTy zt!)QWwX<0*^&2rXGOie4WE^qyq|!5~A9nV+at{sOT-c@MlPu+d-5gE1fCK@N>+MXo zR*kkgkb8nKL92FlGFlORq24nU4p=Gqf~g#y+=4hgF;>?8#^&vy*6l)*vNx7B89=!N zk8{VjTD2l$Qc3q;i2X7^6b$s}Ywq9pCBMcy9}xI^Tm6}QS9$_nE8l1Z+XOy8eHx#^ zWCOk)z9rK9Aua8&yV5lXrd^xLvts}PNY6~*9yqS2;dhI?OYpbGnpcgqS9@FiKH@v8 zQrvFGKL9;J!oG^I$UdOgZXTlMwa-f~#>R#rO=`Al$?krJc!N&9&@N}v^%q#Aw~(QO zc1}mXp%sSupqAnX)sgOChvg8oq+vNbROf+`O?2KP@n(zhlfZgc#-9oRW7I9yJwh1I zHuDauM{IP-?X>r=PTJC*hp&EYbdUW)A7g8-cNuvf`z^D#ofStVb z;QYh#HLIp+HwmsLsioK>+m`_z%X0)`y)m4r_CDgN_6!6#1>1LEiRH}QPK9C;Qd8Dd#0hQT|cdkT9;Sl*U6c_ zH`3>O;}y+|CBt35@h(Gijt=kAv$T6F-7CZvQcDbxGF`;OdXdKAjzxM$#cvQ;{9^IN z*Na`?wrxsn_VCRv&n*`S2I4S!dJ5&!!uQs95Kb=4(51?Jtoye7%iVY!dyXkms^?BF zYR<~*v+QD}8hD7uXQrQV@n`%LcwXthwV#NtVx87W7TQ&CPyF#gpZs>b1+}a3Z7qw) z3P}5$5y>G)9Q`ZmPxvYYm7baVPTyQzY2I(zbXz$iX5eRO8TaIL_OF_?weY>`OFoxx zJTpA3QYQZZR;jd~Ub(La8rQdacKngxX3j4(s+#*oKa2)-WXJsZ!Jqr|wUByaBkF5c z#aq9%((=^r;MM??_ct5&r-z+xb*#039hY zFNux$NWcT~rBDgRagOGcfX8kr!Z0XWlsN`??dw({Q;(Q*=}^X`V*{;Pi4R(#Jqh4L zz)|(B8#vK^X22Naw*Xa3SlO^JbM&cvN*>F>y0zN;rY$md2j9y${b`xKPgDJmzh&Ev6n+D4by9kEL}Vv!}z`-w^)KIzPjRJZT4q^bZ7h14}ostYLoH zGg_;%uu=gf$sC@faC+B2p{IyGI{ZM%gfOU>G%QOD~ z6W{`NoO;=M`lUJIrz=@4Jyp{4=Izk>+@^xdFtWx~l1;^^uDa}fG4NwXgH47VHr;=9 zV*)`QI&F;t571YzXmMcy^y&4lE%-MybEKHg><`!f0M}lnq`54pJ$W_x<~6rf-0&6m zHlYLzG3KS-6a~#mrD?Yp(g@;Sed(9bHKeTsqYS46d)EVIO|EMYPaqjJi>=*=;b+bc zIjtM1fKp3hxQ~f`BTXAdxV>lERSw>9$>3I$pyecsjCDM-;-`p@h_%yU9KgW|E1dDi zQfu;`_NMs8pR`}?L9O`0%Ja?e?}QQbFAIR#M`b_h7X#g&jQRmzV1L0vK0az61wUtP zTK@pU*I4lfiDnuugNDO-^W-#x{lwhgg?@kddqcMHzJ+h0T0Z5sfFsCy%Kcf-sV64B zs|ibyU$LE8+CHQ1=FU!f)@8(-i3Y7)w^AyE(jKJqU8V_WMK01g8TP20VX{9eb1^Jf zAHtdxv*emrMUq6IoRP&@fkOas-@QQ!ZT=#2S73~iFnac;1-m%10LOZ7h%^mj#pg`; zf8lLDOU+|m)@?0x%SeV=NAr=Hy_HlFPe3^}J)+=r;CHWk{{VtsP2hz6vo$IFX<;1q zmi`Db_-)jr*6Z>w8*QbT7PMRUO_xFmY? zrSMQi@ou~2k^ZAc&)4jXKcKFPO>@E@5bmyY{dZ1XF6z-(#UxiFX;Gd|?!XM6t#UsK z*6Urr*1jNELnzU;eMT{%-8^Mh?$X`_g)SF3`5l$nPB~H!dgGLp?DRWQe5bv9H=IWr zuTPe}H$%7*Mljv0#(W*A{hX_-5sshd>0X`Son8yo@>Ry=T`sM z^U&AlAN&`0T`%Eph29vBOr__I=bhAd5Zau6vRCM?sxTk@Z^D0BjF~3 z7#+77wb5Td78K9(uTL(G@d(!b;qm^e!0kECb6B@+?OM95xhh9Ye_F=7lX2p{jVmLP z3dpM_bAW0V2X0TTNh5UkG}7E;(@fIrW;5D_Uz<1-;K7go09u;N3C8Z8)H!M!lS0ZH z9eu@U*iW^_dB;v`E+)?hr_!!jFu)&`36H7TL#XMp+_=)MrG^5SRGMAvIOik*-#xq6 z=r6$U+vngnj(h>8TKsL-?|e7nT{lmP((djZE=#LjNn%imDDv{rxljWs7|uvM;C^pu zw`M0NJrMI+wp!~(pey5wB?!$eb~L40ROHXn-Bt*?xHQ>zu=Er*8|6+iGP zq+EVg#CV(j3Kj5%#n^a5;J&AO!Ni*0zOjSuQUw11*{k7*yiY0tiGLi@P2yP}to5UP{6eBkPD=t00QdE(R~nh*hg|x1 zt8%i?&zkxj4bO<*k-zCwbq#pF9i$|c89A)fx3S1eJ4CWw~RV8m}zON8YHupsreX{Vcc3QMEf$27inK0sDRQL{aH$Q?6I+{~J@_Hc4(yLMN9Bw@(RIvS76 zAo6Rd_?@MEJJa>p;P_$UEk{$+KE)F~#;Ibq(uv6Z;sjS=v>X6)&2r5fXCP8)!lzYL zrlylu?yUZ4>|0Gd%cZ`kFCYOX5&11B^B9qwh3>p{s%${#y#Y$DcZ}zzITY3e{b)v- z4Q@_xP1~Notvh35IHV!E{!~~lgu&a4f$dE@faU)H3UJ1H((%%cR0$?CYaXJFrySG4 zqb8n!)ySN2_|pi*Sim&jEkVc>50Q}plZti$euIw~1Cz@PSmL$)9DZx~8kItDKtc(g!3Fs#c+Y#d~F6}M}vX(8jAZz0uil05?}-S96F(4K zoxvFAh28g0xl`J{fbn;OE_^AiOJOpfCp(~-hu=bYJpR6iHTmVHY1(D(`<)w65wgsD zq<1@t@)1Lho~Jkgk7mK|UcL_z z?G>Kr`fQ&U^_m}7llPvnclH5~~A&_&P+~EFo-1xKNW{IeHb4AfK9|Gz26It3MI)&|v0@rL99mg2#GINp`oE&jj z*LN1SA8fYrZFLxKEt=X3jYiA&s~WZvR6G@IW54HB^uG&QH;3;$PBq9PYiWc_Walq7 zanqhV^z=TIszwzh8O?0;->;YXoasVUBPsN1$IU&$v&NTY|IB4S4SVC`q#ye zZdW*7)#hTOdawHOI5UbdS(QiU`XYylsnpS{;32u@fLGu017+&f|FnPdE&ngS|#4IpxoQVFZ#J9gs4A>t`)K4!ckF*Zr3^8 zkdChZ0D{u(w>j{gAn%|1Bo3HhXd`{Yv=A~Tbm)UjKPf+(=<=1mM1kI|q0`Kxx;*4!0$_xVLf zZ)^S0&1l6f>t4s> zc(j`>P-(s&u!_%4vQY(L#jQ)hzZh0$Cl_hkP zbxrKuwE08P8!{ zwz+oK8iezP$Y~A^ds8&O6I@y7?O_GzQqE4&Is?}RvuD2rPFQE_USweTn>ph}s;*C| z4ySmL!gn|0itvxx55?wfLfgZZ@_pal&H?IA-5=JyH^=@fw$XIy@9rhr6i8Y5{XV9? zIsK!4K53r{`~%`&jov75(CJpMED-0J?haNMSJaj&KRWfY`Zt$5=yY1h`0xG-1@R$1 zDSp&;o(8j=Nu~Tf(@M#;@~x>{09^f2JP%?`c~st7?#uKBv%D-dySqD&6!@y;rPeju zxiuYDRy>)dC5Ze11MsVJkV_H4*lXzEtrU(r-u$%aQMq^z1=~OF)hSE|g72RE3bAwn z=hmu94)gr;O^GX^k_&b`Nc?)OEOEiRK4X91G{TF&BcEYXEPcy$>57z6US!avxjZ|6 z-!*>KEy);n=lzlbucBN-?@W%64x@iGVv&DUH z9!p6R%!=uNIuOKj)MOsD@1G33b!&+HC*wO

$CkQkVo`K;cxV;YI)h99M$;d(v(0 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

C+%LCHBVC#Wv^3BnptHN4#{`TlPHf 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 diff --git a/web/components/plugin-catalog.js b/web/components/plugin-catalog.js new file mode 100644 index 0000000..8eccaf9 --- /dev/null +++ b/web/components/plugin-catalog.js @@ -0,0 +1,160 @@ +import { html, nothing } from 'lit'; +import { LightElement } from '../lib/base.js'; +import { t } from '../lib/i18n.js'; +import { jf, hasSchema, pluginHealth } from './shared/plugin-common.js'; + +// Plugin catalog (`#plugin-catalog`) — the admin board of every registered +// plugin. +// +// One card per plugin: an enable/disable toggle, a health dot (green = +// enabled, running and fully configured; red = enabled but broken; grey = +// off) and a Configure button opening the plugin's own detail page +// (`#plugin-detail?id=…`). Instance config and user-access grants live on the +// detail page, not here — the catalog stays a quick status board. +// +// Styling reuses the connectors card grid (`web/css/connectors.css`). + +const PAGE_ID = 'plugin-catalog'; + +export class PluginCatalogPage extends LightElement { + + static get properties() { + return { + _open: { state: true }, + _all: { state: true }, // PluginInfo[] + _error: { state: true }, + _status: { state: true }, // { [pluginId]: { err?: string } } — toggle feedback + }; + } + + constructor() { + super(); + this._open = false; + this._reset(); + } + + _reset() { + this._all = null; + this._error = null; + this._status = {}; + } + + connectedCallback() { + super.connectedCallback(); + this.__onLocaleChanged = () => this.requestUpdate(); + window.addEventListener('locale-changed', this.__onLocaleChanged); + window.addEventListener('llm-page-change', (e) => { + this._open = e.detail.page === PAGE_ID; + this.style.display = this._open ? 'flex' : 'none'; + if (this._open) this._load(); + }); + } + + disconnectedCallback() { + window.removeEventListener('locale-changed', this.__onLocaleChanged); + super.disconnectedCallback(); + } + + async _load() { + this._error = null; + try { + this._all = await jf('/api/plugins'); + } catch (e) { + this._error = e.message; + } + } + + /// The toggle flips `enabled` only — the persisted config travels back + /// unchanged so a flip never clobbers what the detail page saved. + async _toggle(p, enabled) { + this._status = { ...this._status, [p.id]: {} }; + try { + await jf(`/api/plugins/${encodeURIComponent(p.id)}`, { + method: 'PUT', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ enabled, config: p.config || {} }), + }); + this._all = await jf('/api/plugins'); + window.dispatchEvent(new CustomEvent('plugins-changed')); + } catch (e) { + this._status = { ...this._status, [p.id]: { err: e.message } }; + } + } + + _configure(p) { + history.pushState({ page: 'plugin-detail' }, '', `#plugin-detail?id=${encodeURIComponent(p.id)}`); + window.dispatchEvent(new CustomEvent('llm-page-change', { detail: { page: 'plugin-detail' } })); + } + + // ── Render ───────────────────────────────────────────────────────────────── + + render() { + if (!this._open) return nothing; + const loading = this._all === null && !this._error; + + return html` +

+
+

${t('plugins.catalog.title')}

+
+ + ${this._error ? html` +
${this._error}
` : nothing} + + ${loading + ? html`
${t('plugins.loading')}
` + : html` +
+ ${(this._all ?? []).length === 0 + ? html`

${t('plugins.empty.manage')}

` + : html`
${this._all.map(p => this._renderCard(p))}
`} +
`} +
`; + } + + _renderHealth(p) { + const h = pluginHealth(p); + const cls = h === 'ok' ? 'ok' : (h === 'off' ? 'off' : 'err'); + return html` + + + ${t(`plugins.health.${h}`)} + `; + } + + _renderCard(p) { + const status = this._status[p.id] || {}; + return html` +
+
+
+
+
${p.name}
+
${p.id}
+
+ ${this._renderHealth(p)} +
+ ${p.description ? html`
${p.description}
` : nothing} + +
+ ${hasSchema(p.config_schema) ? html` + ${t('plugins.badge.instance_config')}` : nothing} + ${hasSchema(p.user_config_schema) ? html` + ${t('plugins.badge.user_config')}` : nothing} +
+ +
+
+ this._toggle(p, e.target.checked)} /> + +
+ +
+ + ${status.err ? html`
${status.err}
` : nothing} +
`; + } +} diff --git a/web/components/plugin-detail.js b/web/components/plugin-detail.js new file mode 100644 index 0000000..3a11758 --- /dev/null +++ b/web/components/plugin-detail.js @@ -0,0 +1,312 @@ +import { html, nothing } from 'lit'; +import { LightElement } from '../lib/base.js'; +import { t } from '../lib/i18n.js'; +import { jf, schemaFields, hasSchema, pluginHealth } from './shared/plugin-common.js'; + +// One plugin's admin page (`#plugin-detail?id=`), reached from the +// Configure button on `#plugin-catalog` — the plugin counterpart of +// `connector-detail.js`. +// +// Hosts what was squeezed into the old combined page: the instance-wide +// config form (`config_schema`, saved via `PUT /api/plugins/{id}`) and the +// per-user access checklist (`GET/PUT /api/plugins/{id}/access`). The enable +// toggle is repeated in the summary card so a full setup round-trip happens +// on one page. + +const PAGE_ID = 'plugin-detail'; + +function idFromHash() { + const m = location.hash.match(/^#plugin-detail\?id=(.*)$/); + if (!m) return null; + try { return decodeURIComponent(m[1]); } catch { return null; } +} + +export class PluginDetailPage extends LightElement { + + static get properties() { + return { + _open: { state: true }, + _id: { state: true }, + _plugin: { state: true }, // PluginInfo + _error: { state: true }, + _draft: { state: true }, // config form draft + _status: { state: true }, // { ok?: string, err?: string } + _access: { state: true }, // AccessEntry[] + _accessSel: { state: true }, // Set of granted user ids + _accessErr: { state: true }, + _accessSaved: { state: true }, + }; + } + + constructor() { + super(); + this._open = false; + this._reset(); + } + + _reset() { + this._id = null; + this._plugin = null; + this._error = null; + this._draft = null; + this._status = {}; + this._access = null; + this._accessSel = new Set(); + this._accessErr = null; + this._accessSaved = false; + } + + connectedCallback() { + super.connectedCallback(); + this.__onLocaleChanged = () => this.requestUpdate(); + window.addEventListener('locale-changed', this.__onLocaleChanged); + window.addEventListener('llm-page-change', (e) => { + this._open = e.detail.page === PAGE_ID; + this.style.display = this._open ? 'flex' : 'none'; + if (this._open) this._loadFromHash(); + }); + window.addEventListener('hashchange', () => { + if (this._open) this._loadFromHash(); + }); + } + + disconnectedCallback() { + window.removeEventListener('locale-changed', this.__onLocaleChanged); + super.disconnectedCallback(); + } + + async _loadFromHash() { + const id = idFromHash(); + if (!id) return; + // A different plugin must not inherit the previous one's typed config. + if (id !== this._id) this._reset(); + this._id = id; + await this._load(); + } + + async _load() { + this._error = null; + try { + const all = await jf('/api/plugins'); + const p = (all ?? []).find(x => x.id === this._id) ?? null; + if (!p) { + this._plugin = null; + this._error = t('plugins.detail.not_found', { id: this._id }); + return; + } + this._plugin = p; + // Keep whatever the admin has already typed across a reload triggered by a save. + this._draft = { ...(p.config || {}), ...(this._draft || {}) }; + // Binding-managed plugins (e.g. mobile-connector) gate access through + // their own pairing lifecycle — the generic checklist controls nothing. + if (!p.manages_own_access) await this._loadAccess(); + } catch (e) { + this._error = e.message; + } + } + + async _loadAccess() { + try { + const entries = await jf(`/api/plugins/${encodeURIComponent(this._id)}/access`); + this._access = entries; + this._accessSel = new Set(entries.filter(e => e.granted).map(e => e.user_id)); + } catch (e) { + this._accessErr = e.message; + } + } + + _back() { + // Prefer real history so the browser's own Back stays consistent; fall back + // to the catalog when this page was opened straight from a pasted URL. + if (history.length > 1) { history.back(); return; } + history.pushState({ page: 'plugin-catalog' }, '', '#plugin-catalog'); + window.dispatchEvent(new CustomEvent('llm-page-change', { detail: { page: 'plugin-catalog' } })); + } + + _setDraft(key, value) { + this._draft = { ...this._draft, [key]: value }; + } + + async _save(enabled) { + this._status = {}; + const fields = schemaFields(this._plugin.config_schema); + for (const f of fields) { + if (f.required && !this._draft[f.key]) { + this._status = { err: t('plugins.error.required', { field: f.label }) }; + return; + } + } + try { + await jf(`/api/plugins/${encodeURIComponent(this._id)}`, { + method: 'PUT', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ enabled, config: this._draft || {} }), + }); + this._status = { ok: t('plugins.saved') }; + this._draft = null; // re-seed from the persisted config + await this._load(); + window.dispatchEvent(new CustomEvent('plugins-changed')); + } catch (e) { + this._status = { err: e.message }; + } + } + + _toggleAccessUser(userId, on) { + const next = new Set(this._accessSel); + if (on) next.add(userId); else next.delete(userId); + this._accessSel = next; + this._accessSaved = false; + } + + async _saveAccess() { + this._accessErr = null; + this._accessSaved = false; + try { + await jf(`/api/plugins/${encodeURIComponent(this._id)}/access`, { + method: 'PUT', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ user_ids: [...this._accessSel] }), + }); + this._accessSaved = true; + } catch (e) { + this._accessErr = e.message; + } + } + + // ── Render ───────────────────────────────────────────────────────────────── + + render() { + if (!this._open) return nothing; + if (this._error && !this._plugin) { + return html` +
+ ${this._renderHeader()} +
${this._error}
+
`; + } + if (!this._plugin) { + return html`
${this._renderHeader()} +
${t('plugins.loading')}
`; + } + return html` +
+ ${this._renderHeader()} +
+ ${this._renderSummary()} + ${this._renderConfig()} + ${this._plugin.manages_own_access ? nothing : this._renderAccess()} +
+
`; + } + + _renderHeader() { + return html` +
+
+ +

+ ${this._plugin?.name || this._id || 'Plugin'} +

+
+
`; + } + + _renderSummary() { + const p = this._plugin; + const h = pluginHealth(p); + const cls = h === 'ok' ? 'ok' : (h === 'off' ? 'off' : 'err'); + return html` +
+
+
+ +
+
+
${p.name}
+
${p.id}
+
+ + + ${t(`plugins.health.${h}`)} + +
+ ${p.description ? html`
${p.description}
` : nothing} +
+ ${hasSchema(p.user_config_schema) ? html` + ${t('plugins.badge.user_config')}` : nothing} +
+
+ this._save(e.target.checked)} /> + +
+
`; + } + + _renderConfig() { + const p = this._plugin; + const fields = schemaFields(p.config_schema); + const draft = this._draft || {}; + return html` +
+
+

${t('plugins.detail.config.title')}

+
+ ${fields.length === 0 ? html` +
${t('plugins.detail.config.empty')}
` : html` + ${fields.map(f => html` +
+ + ${f.type === 'boolean' ? html` +
+ this._setDraft(f.key, e.target.checked)} /> +
` : html` + this._setDraft(f.key, f.type === 'number' ? Number(e.target.value) : e.target.value)} />`} + ${f.description ? html`
${f.description}
` : nothing} +
`)} + ${this._status.err ? html`
${this._status.err}
` : nothing} + ${this._status.ok ? html`
${this._status.ok}
` : nothing} + `} +
`; + } + + _renderAccess() { + return html` +
+
+

${t('plugins.detail.access.title')}

+
+
${t('plugins.access.desc')}
+ ${this._accessErr ? html` +
${this._accessErr}
` : nothing} + ${this._accessSaved ? html` +
${t('plugins.saved')}
` : nothing} + ${this._access === null + ? html`
` + : this._access.length === 0 + ? html`

${t('plugins.access.empty')}

` + : html` +
+ ${this._access.map(u => html` +
+ this._toggleAccessUser(u.user_id, e.target.checked)} /> + +
`)} +
+ `} +
`; + } +} diff --git a/web/components/plugin-page-host.js b/web/components/plugin-page-host.js new file mode 100644 index 0000000..7625797 --- /dev/null +++ b/web/components/plugin-page-host.js @@ -0,0 +1,98 @@ +import { html, nothing } from 'lit'; +import { LightElement } from '../lib/base.js'; +import { t } from '../lib/i18n.js'; + +// Host for plugin-contributed pages (`#plugin//`). +// +// The frontend knows nothing about what a plugin page does: on navigation it +// dynamic-imports the fragment ES module the plugin serves from its own router +// (`/api/plugin//`), registers its default-exported HTMLElement +// class as a custom element, and mounts it with the `plugin-id` attribute set. +// The fragment talks to its backend only through `/api/plugin//…` and runs +// with the full session privileges — plugins are trusted (they ship in the +// binary). See `Plugin::web_pages` in core-api for the fragment contract. +export class PluginPageHost extends LightElement { + + static get properties() { + return { + _open: { state: true }, + _route: { state: true }, // "plugin//" while open + _error: { state: true }, + _loading: { state: true }, + }; + } + + constructor() { + super(); + this._open = false; + this._route = null; + this._error = null; + this._loading = false; + this._mounted = null; // currently mounted fragment element + } + + connectedCallback() { + super.connectedCallback(); + this.style.display = 'none'; + window.addEventListener('llm-page-change', (e) => { + const page = e.detail.page || ''; + if (page.startsWith('plugin/')) { + this._openPage(page); + } else { + this._open = false; + this._route = null; + this.style.display = 'none'; + } + }); + } + + async _openPage(route) { + this._open = true; + this.style.display = 'flex'; + if (route === this._route) return; + this._route = route; + this._error = null; + this._loading = true; + + const [, pluginId, pageId] = route.split('/'); + const tag = `skald-plugin-${pluginId}-${pageId}`; + try { + if (!customElements.get(tag)) { + const entry_url = await this._resolveEntry(pluginId, pageId); + const mod = await import(/* @vite-ignore */ entry_url); + const cls = mod.default; + if (!cls || !(cls.prototype instanceof HTMLElement)) { + throw new Error('fragment must default-export an HTMLElement class'); + } + customElements.define(tag, cls); + } + const el = document.createElement(tag); + el.setAttribute('plugin-id', pluginId); + if (this._mounted) this._mounted.remove(); + this._mounted = el; + } catch (e) { + this._error = e.message || String(e); + if (this._mounted) { this._mounted.remove(); this._mounted = null; } + } finally { + this._loading = false; + } + } + + async _resolveEntry(pluginId, pageId) { + const res = await fetch('/api/plugins/pages'); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const pages = await res.json(); + const page = pages.find(p => p.plugin_id === pluginId && p.page_id === pageId); + if (!page) throw new Error(t('plugin_page.unavailable')); + return page.entry_url; + } + + render() { + if (!this._open) return nothing; + return html` + ${this._loading ? html`
${t('plugin_page.loading')}
` : nothing} + ${this._error ? html`
${this._error}
` : nothing} + ${this._mounted && !this._error ? this._mounted : nothing} + `; + } +} diff --git a/web/components/plugins-page.js b/web/components/plugins-page.js new file mode 100644 index 0000000..2dd2289 --- /dev/null +++ b/web/components/plugins-page.js @@ -0,0 +1,202 @@ +import { html, nothing } from 'lit'; +import { LightElement } from '../lib/base.js'; +import { t } from '../lib/i18n.js'; +import { jf, schemaFields } from './shared/plugin-common.js'; + +// Plugins page (`#plugins`) — the user-facing half of the plugin split. +// +// Shows the plugins the caller has been granted (`plugin_access`, admin-granted). +// When a plugin declares a `user_config_schema` the card carries a small +// schema-driven form — e.g. Telegram's pairing code — saved via +// `PUT /api/plugins/{id}/my-config`. +// +// The admin half (enable/disable, instance config, access grants) lives on +// `#plugin-catalog` + `#plugin-detail` — see `plugin-catalog.js`. +// +// Styling reuses the connectors card grid (`web/css/connectors.css`). + +export class PluginsPage extends LightElement { + + static get properties() { + return { + _open: { state: true }, + _mine: { state: true }, // UserPluginView[] — granted + enabled plugins + _error: { state: true }, + _uDrafts: { state: true }, // user config drafts: { [pluginId]: {key: value} } + _uStatus: { state: true }, // { [pluginId]: { ok?: string, err?: string } } + }; + } + + constructor() { + super(); + this._open = false; + this._reset(); + } + + _reset() { + this._mine = null; + this._error = null; + this._uDrafts = {}; + this._uStatus = {}; + } + + connectedCallback() { + super.connectedCallback(); + this.__onLocaleChanged = () => this.requestUpdate(); + window.addEventListener('locale-changed', this.__onLocaleChanged); + window.addEventListener('llm-page-change', (e) => { + this._open = e.detail.page === 'plugins'; + this.style.display = this._open ? 'flex' : 'none'; + if (this._open) this._load(); + }); + } + + disconnectedCallback() { + window.removeEventListener('locale-changed', this.__onLocaleChanged); + super.disconnectedCallback(); + } + + async _load() { + this._error = null; + try { + this._mine = await jf('/api/plugins/mine'); + } catch (e) { + this._error = e.message; + } + } + + _uDraft(p) { + if (!this._uDrafts[p.id]) { + // Seed the form from the stored config for keys the schema knows. + const draft = {}; + for (const f of schemaFields(p.user_config_schema)) { + const v = p.user_config?.[f.key]; + draft[f.key] = v ?? (f.type === 'boolean' ? false : ''); + } + this._uDrafts = { ...this._uDrafts, [p.id]: draft }; + } + return this._uDrafts[p.id]; + } + + _setUDraft(id, key, value) { + this._uDrafts = { ...this._uDrafts, [id]: { ...this._uDrafts[id], [key]: value } }; + } + + async _saveUserConfig(p) { + const draft = this._uDraft(p); + for (const f of schemaFields(p.user_config_schema)) { + if (f.required && !draft[f.key]) { + this._uStatus = { ...this._uStatus, [p.id]: { err: t('plugins.error.required', { field: f.label }) } }; + return; + } + } + this._uStatus = { ...this._uStatus, [p.id]: {} }; + try { + await jf(`/api/plugins/${encodeURIComponent(p.id)}/my-config`, { + method: 'PUT', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(draft), + }); + this._uStatus = { ...this._uStatus, [p.id]: { ok: t('plugins.saved') } }; + // Drop the draft so the reloaded status blob re-seeds the form. + const drafts = { ...this._uDrafts }; + delete drafts[p.id]; + this._uDrafts = drafts; + this._mine = await jf('/api/plugins/mine'); + } catch (e) { + this._uStatus = { ...this._uStatus, [p.id]: { err: e.message } }; + } + } + + // ── Render ───────────────────────────────────────────────────────────────── + + render() { + if (!this._open) return nothing; + const loading = this._mine === null && !this._error; + + return html` +
+
+

${t('plugins.title')}

+
+ + ${this._error ? html` +
${this._error}
` : nothing} + + ${loading + ? html`
${t('plugins.loading')}
` + : html` +
+ ${this._renderMine()} +
`} +
`; + } + + _renderMine() { + const rows = this._mine ?? []; + if (rows.length === 0) { + return html` +
+

${t('plugins.empty.mine')}

+

${t('plugins.empty.ask_admin')}

+
`; + } + return html`
${rows.map(p => this._renderUserCard(p))}
`; + } + + /// Stored config entries the schema does not cover (e.g. Telegram's + /// `{linked, chat_id}` status blob) rendered as a small status list. + _renderUserStatus(p) { + const covered = new Set(schemaFields(p.user_config_schema).map(f => f.key)); + const extra = Object.entries(p.user_config || {}).filter(([k]) => !covered.has(k)); + if (!extra.length) return nothing; + return html` +
+ ${extra.map(([k, v]) => html` +
+ ${k} + ${typeof v === 'boolean' ? (v ? t('plugins.yes') : t('plugins.no')) : String(v)} +
`)} +
`; + } + + _renderUserCard(p) { + const fields = schemaFields(p.user_config_schema); + const status = this._uStatus[p.id] || {}; + const draft = fields.length ? this._uDraft(p) : {}; + return html` +
+
+
+
+
${p.name}
+
${p.id}
+
+ ${t('plugins.status.active')} +
+ ${p.description ? html`
${p.description}
` : nothing} + ${this._renderUserStatus(p)} + ${fields.length ? html` +
+ ${fields.map(f => html` +
+ + ${f.type === 'boolean' ? html` +
+ this._setUDraft(p.id, f.key, e.target.checked)} /> +
` : html` + this._setUDraft(p.id, f.key, f.type === 'number' ? Number(e.target.value) : e.target.value)} />`} + ${f.description ? html`
${f.description}
` : nothing} +
`)} + ${status.err ? html`
${status.err}
` : nothing} + ${status.ok ? html`
${status.ok}
` : nothing} + +
` : nothing} +
`; + } +} diff --git a/web/components/shared/plugin-common.js b/web/components/shared/plugin-common.js new file mode 100644 index 0000000..4956efe --- /dev/null +++ b/web/components/shared/plugin-common.js @@ -0,0 +1,44 @@ +// Shared helpers for the plugin pages (`plugins-page`, `plugin-catalog`, +// `plugin-detail`). Kept separate from `connector-common.js` on purpose: the +// plugin model (JSON-Schema config blobs, `plugin_access`) is not the +// connector model (env/api_key manifests). + +export async function jf(url, opts) { + const res = await fetch(url, opts); + if (!res.ok) throw new Error(await res.text() || `HTTP ${res.status}`); + const ct = res.headers.get('content-type') || ''; + return ct.includes('application/json') ? res.json() : null; +} + +/// Normalizes a plugin JSON Schema (`{properties, required}`) into flat field +/// descriptors. Only the scalar types a form can render are supported. +export function schemaFields(schema) { + const props = schema?.properties; + if (!props || typeof props !== 'object') return []; + const required = new Set(Array.isArray(schema.required) ? schema.required : []); + return Object.entries(props).map(([key, p]) => ({ + key, + label: p.title || key, + description: p.description || '', + type: p.type === 'boolean' ? 'boolean' : (p.type === 'integer' || p.type === 'number') ? 'number' : 'string', + required: required.has(key), + sensitive: !!p.sensitive, + })); +} + +export const hasSchema = (schema) => schemaFields(schema).length > 0; + +/// Required config keys with no persisted value yet. +export function missingRequired(p) { + return schemaFields(p.config_schema) + .filter(f => f.required && (p.config?.[f.key] === undefined || p.config?.[f.key] === null || p.config?.[f.key] === '')); +} + +/// Admin-catalog health of a plugin: 'off' | 'needs_config' | 'not_running' | 'ok'. +/// Green only when enabled, running and every required config key is set. +export function pluginHealth(p) { + if (!p.enabled) return 'off'; + if (missingRequired(p).length) return 'needs_config'; + if (!p.running) return 'not_running'; + return 'ok'; +} diff --git a/web/components/sidebar.js b/web/components/sidebar.js index 5a92c04..4a026a9 100644 --- a/web/components/sidebar.js +++ b/web/components/sidebar.js @@ -11,6 +11,7 @@ export class AppSidebar extends I18nMixin(LightElement) { _debugMode: { state: true }, _recentProjects: { state: true }, _me: { state: true }, + _pluginPages: { state: true }, }; constructor() { @@ -22,6 +23,7 @@ export class AppSidebar extends I18nMixin(LightElement) { this._debugMode = false; this._recentProjects = []; this._me = null; + this._pluginPages = []; } connectedCallback() { @@ -54,6 +56,8 @@ export class AppSidebar extends I18nMixin(LightElement) { this._loadDebugMode(); this._loadRecentProjects(); this._loadMe(); + this._loadPluginPages(); + window.addEventListener('plugins-changed', () => this._loadPluginPages()); window.addEventListener('project-updated', () => this._loadRecentProjects()); } @@ -114,14 +118,31 @@ export class AppSidebar extends I18nMixin(LightElement) { } catch { /* ignore */ } } + // Plugin-contributed menu entries (`GET /api/plugins/pages`, per-user). + // Refetched on `plugins-changed` (fired by the plugins admin pages after an + // enable/disable) so entries appear/disappear without a reload. + async _loadPluginPages() { + try { + const res = await fetch('/api/plugins/pages'); + if (res.ok) this._pluginPages = await res.json(); + } catch { /* ignore */ } + } + _pageFromHash() { const hash = location.hash.slice(1); if (!hash) return 'home'; // Segment ends at the first `/` (e.g. `#session/123`) or `?` (e.g. `#file_viewer?path=...`). const match = hash.match(/^([^/?]+)/); const segment = match ? match[1] : ''; + // Plugin pages: `#plugin//` — the route is accepted by + // shape (deep links must survive the async `/api/plugins/pages` load); the + // host reports an error if the page turns out not to exist for this user. + if (segment === 'plugin') { + const m = hash.match(/^plugin\/([^/?]+)\/([^/?]+)/); + return m ? `plugin/${m[1]}/${m[2]}` : 'home'; + } // `connector` (singular) is the per-connector detail page, `connectors` the list. - return ['inbox', 'dashboard', 'tasks', 'projects', 'models', 'providers', 'approval', 'agents', 'users', 'roles', 'shared-folders', 'connectors', 'connector', 'catalog', 'marketplace', 'profile', 'config', 'llm-requests', 'session', 'tic', 'file_viewer'].includes(segment) ? segment : 'home'; + return ['inbox', 'dashboard', 'tasks', 'projects', 'models', 'providers', 'approval', 'agents', 'users', 'roles', 'shared-folders', 'connectors', 'connector', 'plugins', 'plugin-catalog', 'plugin-detail', 'catalog', 'marketplace', 'profile', 'config', 'llm-requests', 'session', 'tic', 'file_viewer'].includes(segment) ? segment : 'home'; } _tasksSectionFromHash() { @@ -214,6 +235,23 @@ export class AppSidebar extends I18nMixin(LightElement) { `; } + _renderPluginPages() { + if (!this._pluginPages.length) return nothing; + return html` + + ${this._pluginPages.map(p => { + const route = `plugin/${p.plugin_id}/${p.page_id}`; + return html` +
this._togglePage(route, e)}> + + ${p.title} + `; + })} + `; + } + _renderRecentProjects() { if (!this._recentProjects.length) return nothing; return html` @@ -324,6 +362,17 @@ export class AppSidebar extends I18nMixin(LightElement) { ${t('nav.connectors')} + this._togglePage('plugins', e)}> + + ${t('nav.plugins')} + + ${this._me?.role_id === 'admin' ? html` + this._togglePage('plugin-catalog', e)}> + + ${t('nav.plugin_catalog')} + ` : nothing} ${this._me?.role_id === 'admin' ? html` this._togglePage('catalog', e)}> @@ -336,6 +385,8 @@ export class AppSidebar extends I18nMixin(LightElement) { ${t('nav.config')} + ${this._renderPluginPages()} + ${this._debugMode ? html` [plugin-id] { + display: flex; + flex-direction: column; + flex: 1; + min-width: 0; + min-height: 0; + overflow: hidden; +} + /* ── Workspace placeholder ──────────────────────────────────────────────────── */ .app-workspace { diff --git a/web/i18n/en.js b/web/i18n/en.js index 15985c6..4e00ded 100644 --- a/web/i18n/en.js +++ b/web/i18n/en.js @@ -16,6 +16,8 @@ export default { 'nav.users': 'Users', 'nav.roles': 'Roles', 'nav.connectors': 'Connectors', + 'nav.plugins': 'Plugins', + 'nav.plugin_catalog': 'Plugin Catalog', 'nav.catalog': 'Catalog', 'nav.config': 'Settings', 'nav.llm_requests': 'LLM Requests', @@ -839,6 +841,45 @@ export default { 'connectors.error.no_connector': 'No connector named "{name}" is available to you.', + // ── Plugins ───────────────────────────────────────────────────────────────── + 'plugins.title': 'Plugins', + 'plugins.loading': 'Loading…', + 'plugins.section.mine': 'My plugins', + 'plugins.section.manage': 'Manage plugins', + 'plugins.empty.mine': 'No plugins available to you yet.', + 'plugins.empty.ask_admin': 'Ask an admin to grant you access.', + 'plugins.empty.manage': 'No plugins registered.', + 'plugins.status.active': 'active', + 'plugins.status.running': 'running', + 'plugins.status.enabled': 'enabled', + 'plugins.status.off': 'off', + 'plugins.enabled': 'Enabled', + 'plugins.save': 'Save', + 'plugins.save_config': 'Save config', + 'plugins.saved': 'Saved.', + 'plugin_page.loading': 'Loading…', + 'plugin_page.unavailable': 'This page is not available (plugin disabled or page not granted).', + 'plugins.yes': 'yes', + 'plugins.no': 'no', + 'plugins.badge.user_config': 'per-user settings', + 'plugins.access.btn': 'User access', + 'plugins.access.desc': 'Tick a box to let that person see and configure this plugin. Saving replaces the whole list.', + 'plugins.access.empty': 'No users.', + 'plugins.access.save': 'Save access', + 'plugins.error.required': '"{field}" is required.', + 'plugins.catalog.title': 'Plugin Catalog', + 'plugins.catalog.configure': 'Configure', + 'plugins.health.ok': 'active', + 'plugins.health.off': 'off', + 'plugins.health.needs_config': 'needs configuration', + 'plugins.health.not_running': 'not running', + 'plugins.badge.instance_config': 'instance settings', + 'plugins.detail.back': 'Back to catalog', + 'plugins.detail.config.title': 'Instance configuration', + 'plugins.detail.config.empty': 'This plugin has no instance settings.', + 'plugins.detail.access.title': 'User access', + 'plugins.detail.not_found': 'No plugin named "{id}".', + // ── Providers ─────────────────────────────────────────────────────────────── 'providers.title': 'Providers', 'providers.add': 'Add', diff --git a/web/i18n/fr.js b/web/i18n/fr.js index f8ed101..fed084b 100644 --- a/web/i18n/fr.js +++ b/web/i18n/fr.js @@ -16,6 +16,8 @@ export default { 'nav.users': 'Utilisateurs', 'nav.roles': 'Rôles', 'nav.connectors': 'Connecteurs', + 'nav.plugins': 'Plugins', + 'nav.plugin_catalog': 'Catalogue des plugins', 'nav.catalog': 'Catalogue', 'nav.config': 'Paramètres', 'nav.llm_requests': 'Requêtes LLM', @@ -829,6 +831,45 @@ export default { 'connectors.error.no_connector': 'Aucun connecteur nommé "{name}" ne vous est disponible.', + // ── Plugins ───────────────────────────────────────────────────────────────── + 'plugins.title': 'Plugins', + 'plugins.loading': 'Chargement…', + 'plugins.section.mine': 'Mes plugins', + 'plugins.section.manage': 'Gérer les plugins', + 'plugins.empty.mine': 'Aucun plugin disponible pour vous.', + 'plugins.empty.ask_admin': "Demandez à un administrateur de vous accorder l'accès.", + 'plugins.empty.manage': 'Aucun plugin enregistré.', + 'plugins.status.active': 'actif', + 'plugins.status.running': 'en cours', + 'plugins.status.enabled': 'activé', + 'plugins.status.off': 'arrêté', + 'plugins.enabled': 'Activé', + 'plugins.save': 'Enregistrer', + 'plugins.save_config': 'Enregistrer la config', + 'plugins.saved': 'Enregistré.', + 'plugin_page.loading': 'Chargement…', + 'plugin_page.unavailable': 'Page non disponible (plugin désactivé ou page non accordée).', + 'plugins.yes': 'oui', + 'plugins.no': 'non', + 'plugins.badge.user_config': 'réglages par utilisateur', + 'plugins.access.btn': 'Accès utilisateurs', + 'plugins.access.desc': "Cochez qui peut voir et configurer ce plugin. L'enregistrement remplace toute la liste.", + 'plugins.access.empty': 'Aucun utilisateur.', + 'plugins.access.save': 'Enregistrer les accès', + 'plugins.error.required': '« {field} » est requis.', + 'plugins.catalog.title': 'Catalogue des plugins', + 'plugins.catalog.configure': 'Configurer', + 'plugins.health.ok': 'actif', + 'plugins.health.off': 'arrêté', + 'plugins.health.needs_config': 'à configurer', + 'plugins.health.not_running': 'non démarré', + 'plugins.badge.instance_config': 'réglages d’instance', + 'plugins.detail.back': 'Retour au catalogue', + 'plugins.detail.config.title': 'Configuration de l’instance', + 'plugins.detail.config.empty': 'Ce plugin n’a aucun réglage d’instance.', + 'plugins.detail.access.title': 'Accès utilisateurs', + 'plugins.detail.not_found': 'Aucun plugin nommé « {id} ».', + // ── Providers ─────────────────────────────────────────────────────────────── 'providers.title': 'Fournisseurs', 'providers.add': 'Ajouter', diff --git a/web/i18n/it.js b/web/i18n/it.js index f5a1f48..42c9228 100644 --- a/web/i18n/it.js +++ b/web/i18n/it.js @@ -16,6 +16,8 @@ export default { 'nav.users': 'Utenti', 'nav.roles': 'Ruoli', 'nav.connectors': 'Connettori', + 'nav.plugins': 'Plugin', + 'nav.plugin_catalog': 'Catalogo plugin', 'nav.catalog': 'Catalogo', 'nav.config': 'Impostazioni', 'nav.llm_requests': 'Richieste LLM', @@ -829,6 +831,45 @@ export default { 'connectors.error.no_connector': 'Nessun connettore chiamato "{name}" è disponibile per te.', + // ── Plugin ────────────────────────────────────────────────────────────────── + 'plugins.title': 'Plugin', + 'plugins.loading': 'Caricamento…', + 'plugins.section.mine': 'I miei plugin', + 'plugins.section.manage': 'Gestione plugin', + 'plugins.empty.mine': 'Nessun plugin disponibile per te.', + 'plugins.empty.ask_admin': "Chiedi a un amministratore di concederti l'accesso.", + 'plugins.empty.manage': 'Nessun plugin registrato.', + 'plugins.status.active': 'attivo', + 'plugins.status.running': 'in esecuzione', + 'plugins.status.enabled': 'abilitato', + 'plugins.status.off': 'spento', + 'plugins.enabled': 'Abilitato', + 'plugins.save': 'Salva', + 'plugins.save_config': 'Salva configurazione', + 'plugins.saved': 'Salvato.', + 'plugin_page.loading': 'Caricamento…', + 'plugin_page.unavailable': 'Pagina non disponibile (plugin disabilitato o pagina non concessa).', + 'plugins.yes': 'sì', + 'plugins.no': 'no', + 'plugins.badge.user_config': 'impostazioni per utente', + 'plugins.access.btn': 'Accesso utenti', + 'plugins.access.desc': "Seleziona chi può vedere e configurare questo plugin. Il salvataggio sostituisce l'intera lista.", + 'plugins.access.empty': 'Nessun utente.', + 'plugins.access.save': 'Salva accesso', + 'plugins.error.required': '"{field}" è obbligatorio.', + 'plugins.catalog.title': 'Catalogo plugin', + 'plugins.catalog.configure': 'Configura', + 'plugins.health.ok': 'attivo', + 'plugins.health.off': 'spento', + 'plugins.health.needs_config': 'da configurare', + 'plugins.health.not_running': 'non in esecuzione', + 'plugins.badge.instance_config': 'impostazioni istanza', + 'plugins.detail.back': 'Torna al catalogo', + 'plugins.detail.config.title': 'Configurazione istanza', + 'plugins.detail.config.empty': 'Questo plugin non ha impostazioni di istanza.', + 'plugins.detail.access.title': 'Accesso utenti', + 'plugins.detail.not_found': 'Nessun plugin chiamato "{id}".', + // ── Provider ──────────────────────────────────────────────────────────────── 'providers.title': 'Provider', 'providers.add': 'Aggiungi', diff --git a/web/index.html b/web/index.html index 8e4bb94..b798a29 100644 --- a/web/index.html +++ b/web/index.html @@ -96,6 +96,10 @@ + + + +