All notable changes to the YourBot SDK are documented here. This project follows Keep a Changelog and Semantic Versioning.
[0.10.1]
Added
- Rejected calls leave a trace in your plugin log. When the platform
refuses an SDK call, the SDK now writes a
warningrow to your plugin's log (tagssdk,rejected) with the method, the error code and the host's message, before raising the usual typed exception. Throttled to three per method per hour. Atry/exceptthat swallows the exception no longer hides the reason; it shows up in the dev portal's Logs and, grouped by reason, on the Analytics page.
[0.10.0]
Added
-
ctx.http.allow_host(host)/ctx.http.revoke_host(host). Yourproxy_domains_requestedis fixed when you publish, so it can never name a destination only the installing server knows: their own game-server panel, their self-hosted API, their home lab. Plugins for that kind of upstream were effectively unbuildable, because no static manifest can list a domain that differs for every customer. Ask for the address in a setup command and pass it here: the platform verifies the caller is a server admin (Manage Server) and that the host resolves to a public address, then authorizes it for that server alone.Approved hosts are matched exactly, so approving
example.dedoes not authorizepanel.example.de(approve each host you actually call). This is deliberately stricter than the manifest allowlist, where a declared apex also covers its subdomains: a manifest entry is a reviewed declaration by the author, whileallow_hostis an admin naming one machine.This is the same per-install host list
ctx.ws.allow_host()already wrote to, so a host approved through either call now works for both HTTP and WebSocket. Each transport still requires its own capability, andallow_hostitself is gated onproxy:httpso an HTTP-only plugin no longer has to requestproxy:websocketjust to let an admin name an address.
[0.9.0]
Added
ctx.sql.query()now reports truncation. It returns aQueryResult— a plainlistof row dicts that also carries.truncated, True when the host clipped the result at the requestedlimit. The host has always computed the flag; the SDK dropped it, so a query that silently lost rows was indistinguishable from a complete one.QueryResultsubclasseslist, so iteration, indexing,len()and==against a plain list are unchanged and no existing code needs to be touched. Exported asyourbot_sdk.QueryResult.ctx.kv.list(start_after=...)for pagination. The host capslist()at 100 keys per call, and the cursor that pages past that was implemented in the platform but never forwarded through the RPC layer, so keys 101+ were unreachable from inside a plugin. Pass the last key you received to get the next page.allowed_mentionsonctx.discord.send_messageandedit_message. The host has always accepted and sanitized it on both actions, but the SDK did not expose the parameter, so passing it raisedTypeError: unexpected keyword argument 'allowed_mentions'. Omit it and nothing pings (the host default is{"parse": []});everyone/hereare always stripped host-side and cannot be triggered from a plugin.
Fixed
ctx.kv.listdocumented and mocked its real cap. The docstring promised "up to 1000 results" while the host clamps to 100, so plugins that swept a prefix silently processed only the first 100 keys and reported success.MockContextnow clamps to 100 as well, so a plugin that only works because the mock returned more keys fails in tests rather than in production.
[0.8.5]
Added
RpcError. Host errors the SDK cannot map to a more specific exception (including "transport closed" failures) are now raised asyourbot_sdk.RpcErrorinstead of bareRuntimeError, soexcept SdkErroris a true catch-all as documented.RpcErroralso subclassesRuntimeError, so existingexcept RuntimeErrorhandlers keep working unchanged. Note: code with BOTH anexcept SdkError:and anexcept RuntimeError:clause will now route these errors into whichever clause appears first — previously they could only matchRuntimeError.SdkPermissionError.permissionis now populated. Newer hosts ship the missing permission name in the structured error payload; against older hosts the SDK best-effort parses it from the error message. Empty string when unknown (previously it was always empty).
Fixed
- Reserved command names resynced with the platform.
yourbot validatenow refuses the platform commandshelpandyourbotlocally, matching the publish gate (previously the platform rejectedyourbotat publish while local validation passed it, andhelpis newly reserved for the platform's/helpcommand). yourbot_sdk.responsesimportable through every shim. Theresponsesmodule (typed return shapes) was missing from the compat-shim submodule lists, sofrom mmo_maid_sdk.responses import Member(and the monorepo dev-tree import) failed withModuleNotFoundErroreven though the module ships in the wheel.ctx.metrics.recorddocuments the enforced name rule. The docstring claimed names up to 128 chars with dots; the platform actually enforces 1-64 chars of[a-zA-Z_][a-zA-Z0-9_]*(no dots). Dotted names now also fail fast at the RPC layer with a clear message instead of a generic storage error.- No more leaked pending entries on a failed request write. If serializing an RPC request failed (e.g. a non-JSON-serializable param), the request stayed in the pending table forever; it is now cleaned up and the original exception propagates unchanged.
Changed
- Testing mocks enforce production signatures.
MockContextsub-APIs now reject positional arguments exactly where production does:ctx.interaction.respond/defer/followup/send_modal,ctx.http.requestoptions,ctx.logoptions, andctx.sql.query'slimitare keyword-only;ctx.http.get/postno longer acceptsecret_auth/auth(production only accepts those onctx.http.request), andctx.http.postrequiresbody. Tests that passed these positionally were already broken in production — the mock now catches it before you ship. - Reserved names are grandfathered on the platform. If your plugin
published a command before its name became reserved, the platform keeps
accepting your uploads and registers the command on each server under the
alias
/yourpluginid-name; events still arrive under your manifest name. Localyourbot validatecannot see your publish history, so it may still flag such a name as reserved — the platform's publish gate is the authority.
[0.8.4]
Added
- Server-side cron for pooled plugins.
@plugin.crontasks now run in production: declare each task inmanifest.json—"cron": [{"spec": "0 9 * * *", "name": "daily_summary"}](name= the decorated function's name) — and the platform fires the schedule server-side, once per enabled server, delivering a normal plugin event withevent_type: "cron". The SDK routes it to the matching@plugin.cronfunction with a tenant-scopedctx(same per-tenant Context andon_ready-before-first-event guarantees as any event); you can also consume the raw event with@plugin.on_event("cron"). Limits: max 5 entries, nothing more often than every 5 minutes, at-most-once per (server, schedule, minute), missed ticks not replayed. - Cron consistency checks in
yourbot validate. The manifest"cron"array is validated (shape, entry cap, 5-minute frequency floor, identifier names, duplicates, spec syntax — same 5-field UTC dialect as the decorator), and drift between the manifest and your code is surfaced: a@plugin.crontask with no manifest entry never runs in production (warning), and@plugin.scheduletasks never run in production at all (warning). - The
cronstarter template (yourbot new) now ships a manifest with matching"cron"entries.
Fixed
- Lifecycle hooks get a per-tenant context in pool mode.
@on_install/@on_enable/@on_disable/@on_uninstallhandlers now receive a Context scoped to the server the signal is for (previously the blank boot ctx in pool mode), and their RPCs carry the host-supplied correlation id so tenant resolution is exact even after the install row is gone.
[0.8.3]
Added
- Slash-command consistency checks in
yourbot validate. The local validator now cross-checksmanifest.jsonslash_commandsagainst your@plugin.on_slash_commanddecorators, exactly like the platform does at upload and in the Plugin Builder preview: a declared command with no matching handler is a blocking error (it would appear in Discord and hang on "thinking…" forever), an uppercase decorator name is a blocking error (registration lowercases the name, dispatch matches exactly), reserved names owned by built-in YourBot plugins are refused, and command/option names must be 1-32 chars of lowercase letters, digits,-or_with a valid optiontype. A handler with no manifest entry warns (it registers with no description and no options). Fix mismatches locally instead of discovering them after a failed upload. proxy:websocketcapability detection.yourbot validateand capability auto-detection now recognizectx.ws.*usage, so WebSocket plugins no longer validate green locally while missing the capability at upload.
Fixed
- Pool-mode tenant resolution hardened (now actually shipped). Every outbound RPC carries the correlation ID of the event that triggered it, so the host resolves the RPC's tenant from that trusted ID instead of "most recent event". This fix was documented for 0.7.1 but the code did not make it into the published wheel; 0.8.3 ships it. No public API change.
- Dashboard handler error logs name the right handler. With multiple
@plugin.on_dashboardhandlers, an error log previously always reported the last-registered method name instead of the one that failed.
[0.8.2]
Added
- Wildcard WebSocket handlers.
@plugin.on_ws_message("name:*")(andon_ws_open/on_ws_close) now match any concrete connection whose name shares the prefix — e.g."rustplus:*"handlesrustplus:eu1,rustplus:us-west, etc. Lets a plugin manage many connections (one socket per game server) with a single handler set; the concretenameis in the frame so you can route per-connection. Exact-name registrations still take precedence.
[0.8.1]
Added
ctx.ws.allow_host(host)/ctx.ws.revoke_host(host)— authorize a WebSocket destination the SERVER ADMIN supplies at setup time (e.g. their own game server's IP), which a staticproxy_domains_requestedallowlist can't express. Must be called from inside a slash-command handler run by a server admin (Manage Server); the platform verifies the invoking member is an admin and that the host is public, then remembers it for that server only. After approval,ctx.ws.ensure(name, "wss://<host>:...")to that host succeeds.MockContext.wsrecordsallowed_hosts/revoked_hosts.
[0.8.0]
Added
- Persistent WebSocket connections (
ctx.ws). A newproxy:websocketcapability lets a plugin open and maintain a live two-way connection to a declared host. The platform's broker holds the socket (the sandbox still has no raw network) and reconnects automatically.ctx.ws.ensure(name, url, *, secret_auth=None, auth=None, subscribe=None, binary=False)— idempotent; safe to call on every event or inon_ready.ctx.ws.send(name, data)—strsends a text frame,bytesa binary frame.ctx.ws.close(name).- Inbound frames are delivered to
@plugin.on_ws_message(name)((ctx, msg)wheremsg = {"name", "conn_id", "data", "binary"}; binarydatais base64), with@plugin.on_ws_open(name)and@plugin.on_ws_close(name)for lifecycle. Frames for one connection are serialized in order. Suitable for game-server feeds and the Rust+ companion protocol (bundle pure-Python protobuf in your ZIP).
- Secret-backed auth injection for
ctx.httpandctx.ws. Passsecret_auth="SECRET_NAME"(orauth={"scheme": "bearer"|"basic"|"token", "secret": "NAME"}) and the platform injects theAuthorizationheader from a domain-bound secret — the plugin never sees the value and cannot setAuthorizationitself. This unblocks Bearer-token APIs that were previously unreachable becauseAuthorizationis stripped. Requiresstorage:secrets. quarterdashboard widget width alongsidefull/half/third/two_thirds.MockContext.wsin the test harness recordsensure/send/closecalls and is capability-gated likectx.http, so WebSocket plugins are unit-testable.
Notes
proxy:websocketis a dangerous-tier capability (staff-reviewed) and requires the exact host inproxy_domains_requested(no subdomain wildcard, unlike HTTP).
[0.7.1]
Fixed
ctx.kv.increment(key, amount)acceptsamountpositionally. It was keyword-only (increment(key, *, amount=1)), so the natural positional call — matchingctx.kv.decrement(key, amount)and RedisINCRBY— raisedTypeError. The signature is nowincrement(key, amount=1, *, path=""); existingamount=/path=keyword calls are unchanged.MockContextmirrors it.
Added
- Accurate
RateLimitError.retry_after. When the host sends structured error metadata (code,retry_after) the SDK now surfaces the precise retry delay, falling back to parsingretry in <N>sfrom the message, then the legacyremaining=/minparse.retry_afteris now a float to support sub-second and hour-scale limiter windows.
[0.7.0]
Added
- In-place message updates from component handlers.
ctx.interaction.respond(update_message=True)edits the message the button/select menu is attached to (DiscordUPDATE_MESSAGE) instead of sending a new reply — game boards, pagination, and live dashboards can now update in place. Component interactions only;ephemeralis ignored; the fields you pass replace the message's current content/embeds/components; may be called repeatedly within the 15-minute interaction window. On platform versions without support the flag is ignored and a normal reply is sent, so it degrades gracefully.MockContextrecords the new flag ininteraction.responsesfor assertions.
[0.6.1]
Added
- PEP 561 typing marker. The wheel now ships
py.typed, so type checkers (mypy, pyright) and IDEs pick up the SDK's inline type hints when it's installed from PyPI — previously the hints were ignored for installed (non-editable) users. MockContextenforces capabilities by default. Calling a gated method (e.g.ctx.discord.send_message) without the matching capability now raisesCapabilityError, matching production — so a passing test means a working manifest. PassMockContext(strict_capabilities=False)for the old behaviour. (MockContext(capabilities=[])now means "no capabilities" rather than "all".)- Typed Discord responses. New
yourbot_sdk.responsesmodule withMember,Role,Channel,Guild, andMessageTypedDicts; thectx.discordread methods (get_member,get_channel,get_guild,list_roles,list_channels,list_members,search_members,get_messages) are now annotated with them for IDE autocomplete. ctx.discord.iter_messages(...)— a generator that pages through a channel's full history automatically (walks newest→oldest by default, or oldest→newest withafter=), so you no longer managebefore/aftercursors by hand. The testing harness supports it viactx.discord.set_messages([...]).- Machine-readable error codes. Every SDK exception now carries a stable
.code(e.g.CAPABILITY_DENIED,RATE_LIMITED,QUOTA_EXCEEDED,DISCORD_API_ERROR,BOT_MISSING_PERMISSION,KV_QUOTA_EXCEEDED,VALIDATION_ERROR,RPC_TIMEOUT) so you can branch on failures without string-matching.CapabilityErrormessages now include a manifest hint.
Fixed
- KV-quota errors now raise
KvQuotaError(codeKV_QUOTA_EXCEEDED) instead of being misclassified as a genericRateLimitError.
Fixed — testing harness fidelity
MockContext.kv.incrementnow takes the keyword-onlypathargument, matching the real API (JSON-object increments).ctx.kv.increment("k", 5)becomesctx.kv.increment("k", amount=5).ctx.ephemeral.counterin the mock is now a real sliding-window counter (it was monotonic and never reset, making rate-limit tests false-pass).- The HTTP mock now records the
paramsquery-string argument; the SQL mockqueryacceptslimit;metrics.queryacceptsaggregate— all matching the real signatures. yourbot devnow reports log lines and KV writes (it read attributes that didn't exist onMockContext, so those counters were always zero).MockContextgainedkv_writesandlog_linesaccessors.
[0.6.0]
Changed — package rename (mmo-maid-sdk → yourbot-sdk)
- The distribution is now
yourbot-sdk(pip install yourbot-sdk) and the import package isyourbot_sdk(from yourbot_sdk import Plugin, Context). - The CLI command is now
yourbot(yourbot new,yourbot dev,yourbot validate). - The dispatch-thread env var is now
YOURBOT_SDK_DISPATCH_THREADS(the oldMMO_SDK_DISPATCH_THREADSis still honored as a fallback).
Backward compatibility (nothing breaks)
- The
yourbot-sdkwheel still ships ammo_maid_sdkcompatibility package, so existing plugins thatimport mmo_maid_sdk(including submodule and legacy nested imports) keep working. Importing it emits aDeprecationWarningpointing toyourbot_sdk. pip install mmo-maid-sdkcontinues to resolve via a thin alias meta-package that depends onyourbot-sdkof the same version.- No public API changed: class names, exceptions (incl. the
PermissionError/TimeoutErroraliases), decorators, andContextsub-APIs are identical.
Prior releases (0.5.x and earlier) were published under the mmo-maid-sdk name; their history
lives in that line.