Plugin config forms can now ask the plugin while they are filled in:
- x-options {name, dependsOn, search} loads a field's choices from
/v1/options/{name}. POST /api/v1/plugins/:id/options merges the form's
values with the stored secrets (system, tenant or instance scope) so a
redacted token still works. Go and Python SDKs, OpenAPI and conformance
cover the endpoint.
- x-oauth turns a string field into an account connection. WeKnora runs
the authorization code flow (PKCE, client credentials from the plugin's
system config), keeps the tokens sealed in plugin_oauth_connections and
the field holds "oauth:<id>". The invoker swaps references for fresh
access tokens at call time, refreshing near expiry. Callback:
/api/v1/plugin-oauth/callback, one-time states in Redis or memory.
- SchemaForm gains an options widget (dependsOn reload, remote search)
and an OAuth connect widget; connector, web search, workspace and
platform config forms provide the plugin behind them.
Host plugins are capped per runtime.resources on Linux: RLIMIT_DATA for
memory (WEKNORA_PLUGIN_MEMORY_DEFAULT when undeclared) and, with a
delegated cgroup v2 (WEKNORA_PLUGIN_CGROUP), memory.max and cpu.max.
Events:
- A plugin lists what it wants in permissions.events: knowledge.ingested,
knowledge.failed, knowledge.deleted or chat.answered. The admin reviews
the list at install; a plugin that only listens needs no other
contribution.
- A publish enqueues one plugin:event task per subscribed plugin the
workspace has on (maintenance queue, Redis or Lite).
- The task calls the plugin's new /v1/events at least once. Retryable
failures retry up to 10 times, keeping the event ID; other failures drop
the event.
- The plugin host refuses a process that subscribes without serving
events.
Where events come from:
- A document reaches completed or failed through a dozen paths but a few
repository writes, so a knowledge repository wrapper raises
knowledge.ingested and knowledge.failed on the transition only:
UpdateKnowledge, UpdateKnowledgeForTransfer, UpdateKnowledgeColumns,
FinalizeSubtask, CompleteProcessingWithoutSubtasks.
- knowledge.deleted comes from the shared delete path.
- chat.answered comes from where RAG and agent answers are persisted. The
stop button's early completion, which has no question, is skipped.
Webhooks:
- contributes.webhooks gives each workspace a secret URL per webhook:
/api/v1/plugin-callbacks/{plugin}/{webhook}/{tenant}.{mac}. The MAC is
keyed from SYSTEM_AES_KEY/JWT_SECRET.
- WeKnora answers 404 unless the URL is genuine, the webhook exists and the
workspace has the plugin on. Calls are rate-limited per URL, bodies are
capped at 1 MB and cookies are stripped.
- The call is relayed to the plugin's /v1/webhooks/{id} with that
workspace's envelope; the plugin verifies the sender.
- Admins get the URLs from GET /plugins/:id/webhooks, shown with copy
buttons in the plugin center's configure drawer. Plugins get them in
context.webhooks when APP_EXTERNAL_URL is set.
SDKs:
- Go: OnEvent and Webhook.
- Python: @plugin.on_event and @plugin.webhook.
- Conformance, OpenAPI and READMEs are updated.
Also: examples/plugins/activity (Python) keeps a workspace activity feed
from events and a secret-checked note webhook, and shows it on a toolbox
page.
Plugins can add pages to the app. Each is an HTML entry under ui/ in the
package:
| Point | Shown as | Default minRole |
| ------------------ | ----------------------------- | --------------- |
| `pages` | toolbox tabs | viewer |
| `settingsSections` | a section under Plugins in settings | admin |
| `kbTabs` | a tab on each knowledge base (gets knowledgeBaseId) | viewer |
Declarative plugins may have pages too.
Isolation:
- A page runs in `<iframe sandbox="allow-scripts allow-forms allow-popups
allow-popups-to-escape-sandbox">` without allow-same-origin, so it has an
opaque origin and cannot read the app's storage.
- Its files come from /api/v1/plugin-ui/assets/{id}/{version}/ui/...
(served without login, only files under ui/ of the loaded version).
- They carry a CSP with connect-src 'none', so the page cannot reach the
network.
Bridge:
- The page talks to the app only over postMessage: `api.request`,
`ui.toast`, `ui.confirm`, `ui.navigate`, `ui.resize`, `ui.close`.
- The app answers only its frame's window, rate-limited, and validates
paths and sizes.
- It sends init (plugin, mount, locale, role, theme tokens, context) and
theme/locale changes.
- api.request goes to POST /api/v1/plugins/:id/ui-request. The app checks
the workspace has the plugin on and the caller meets the page's minRole
(same rules as the route guards), then calls the plugin's new
/v1/ui/request endpoint with the caller's role.
SDKs:
- Go: `p.UI` and `UIJSON`.
- Python: `@plugin.ui` and `UIResponse`.
- Both list "ui" in their manifest; conformance checks the endpoint; the
OpenAPI spec documents it.
- packages/plugin-ui is the page side: a dependency-free ES module with
types that connects, applies the theme as --wk-* CSS variables and keeps
the frame sized.
Also:
- Fix the Python SDK reading Go timestamps with fewer than 6 fraction
digits on Python < 3.11. Key-value entries tripped on it.
- examples/plugins/links: a Python plugin with all three kinds of page,
storing links in the Host API key-value store.
pluginsdk/python is the extension protocol for Python 3.9+, standard
library only. It has the same shape as the Go SDK:
- web search, parsers, connectors (NDJSON streaming with checkpoints) and
a config check;
- the host handshake over a unix socket with the host's token, or remote
mode with HMAC-signed requests;
- errors mapped to protocol codes;
- a Host API client (kv);
- graceful drain on SIGTERM.
The Go conformance suite now also runs the Python SDK's test plugin in both
modes, and plugin-sdk.yml runs the Python tests on 3.9 and 3.13.
The embedded host runs runtime.kind python:
- WeKnora's python3 (or WEKNORA_PLUGIN_PYTHON) runs the entry, with
vendor/ and the package root on PYTHONPATH.
- No bytecode is written into the shared package.
- On Windows, where Python has no unix sockets, the plugin listens on a
loopback port.
- Installing checks the entry and the interpreter.
examples/plugins/notebooks is a Jupyter notebook parser written with it:
code, text outputs and charts, with a workspace setting for outputs.