Development chronicle β mb592 to mb597 Β· Examples, events, observability and final guards
The Plugin Gallery no longer contains only contracts and empty frames.
With this development arc, Mediabotβs plugin v2 architecture gains working examples in Perl, Python and Tcl, real EventBus subscriptions for external scripts, an event-driven channel greeter, live visibility into asynchronous jobs, and stronger partyline flood protection.
A final pre-commit review then closed the remaining edge cases around event contexts, transactionality, diagnostics and session shutdown.
No database schema change was required.
The v2 contract already knew how to describe, mount, authorize and reload external scripts. It now comes with examples that can be loaded and executed directly from the repository:
plugins/scripts/examples-v2/
βββ coin.py
βββ coin.py.manifest.json
βββ fortune.pl
βββ fortune.pl.manifest.json
βββ lart.tcl
βββ lart.tcl.manifest.json
Each language demonstrates a different part of the contract.
fortune.pl β commands and authorization.plugins loadscript examples-v2/fortune.pl
The sidecar exposes both a public command and a privileged command. The privileged command uses the named Master level, so Mediabot rejects an unauthorized user before the script process is even started.
coin.py β bounded user inputThe Python example accepts a limited number of coin tosses and rejects abusive values. It demonstrates that a friendly command still needs a strict input boundary.
lart.tcl β dependency-free TclThe Tcl example uses only the core interpreter and follows the same JSON action contract as Perl and Python.
It also carries a little piece of IRC history: the traditional trout has found a new home.
The cookbook now explains the v2 model through practical rules:
0;USER_LEVEL;.plugins;Most importantly, the examples are not decorative. The CI suite loads their real sidecars and executes all three interpreters.
The events field was previously descriptive for sidecar plugins. It is now operational.
A sidecar may declare:
{
"api": 2,
"name": "greeter",
"version": "1.0",
"events": [
"channel_join_observed"
]
}
When the plugin loads, Mediabot registers an exact EventBus listener. When the event occurs, the external script receives a structured envelope containing the event name and its useful context.
The supported routing surface is deliberately allow-listed:
public_command_observed
channel_join_observed
channel_part_observed
channel_topic_observed
channel_kick_observed
An invented event is refused during loading.
The event lifecycle follows the same transactional rules as command mounting:
Event-triggered scripts use the same ScriptRunner and ScriptActionRunner gates as command-triggered scripts. IRC output must still pass the explicit apply and IRC permissions.
The event contract now has a complete, working example:
plugins/scripts/examples-v2/greeter.tcl
plugins/scripts/examples-v2/greeter.tcl.manifest.json
It declares no public command. Its entire life is driven by:
channel_join_observed
Load it from the partyline:
.plugins loadscript examples-v2/greeter.tcl
A normal user joining a channel receives a welcome message:
Welcome aboard, SlaY! Pull up a chair.
When the bot itself joins, the script stays silent through its is_self guard.
The CI suite executes both paths with the real Tcl interpreter and verifies that unloading the plugin removes its listener completely.
This is the familiar Eggdrop binding model rebuilt around a validated manifest, a controlled process boundary and a transactional lifecycle.
.status sees asynchronous workersLarge rankings have been able to run through forked CommandAsync workers since mb583. Operators can now see those jobs from the partyline.
Example:
.status
DB: up
Loop: no stall detected
Async: 1 running (since start: 7 spawned, 5 completed, 1 timeout(s), 0 sync fallback(s), 2 lock refusal(s))
- [leaderboard] #quebec pid=48123 running 12.4s
The status output exposes:
This remains a read-only, memory-only view. .status does not query the database, wait for children, kill workers or mutate their lifecycle.
The snapshot functions return detached data so inspection cannot accidentally modify the live registry.
The partyline already limited authenticated sessions to ten lines in five seconds.
The old behavior had one weakness: every excess line produced another warning. A paste of one thousand lines could therefore generate hundreds of responses from the protection mechanism itself.
The new behavior is quiet and progressive:
lines 1β10 β processed normally
line 11 β one βRate limit exceeded. Slow down.β warning
lines 12β29 β silently dropped and counted
line 30 β flood warning and session disconnect
The limit itself has not changed.
The throttle now keeps memory-only counters visible in .status:
Throttle: 3 rate hit(s), 41 silent drop(s), 1 flood boot(s)
After the rate window expires, the session starts cleanly again and may receive a new single warning if it floods once more.
The flood disconnect uses the existing idempotent session cleanup path and closes the underlying stream after the final notice has been flushed.
The final review found several edge cases where the implementation did not yet fully match the guarantees documented by mb592βmb596.
They are now closed.
public_command_observed carries a Mediabot::Context object rather than a plain hash.
The event bridge now extracts its useful values correctly, so an external script receives the channel, nickname, command and arguments instead of an empty context.
A manifest declaring events can no longer load into a manager without an EventBus and remain silently inert.
The load fails before registration.
Repeated event names are normalized and subscribed only once. A single emitted event therefore runs a script once.
Errors returned by ScriptRunner and ScriptActionRunner are preserved when safe instead of being replaced unconditionally by a generic failure.
Nested or malformed diagnostics still receive a safe fallback and can never crash dispatch.
The CommandAsync counters now include the fallback used when the event loop cannot watch the new worker process.
The lifetime statistics therefore cover every documented fallback path.
A partyline flood boot no longer stops at removing the session from Mediabotβs registry. The underlying stream is explicitly closed after the final warning is sent.
Outdated counts and wording were aligned with the actual implementation and tested examples.
The full local suite completed successfully:
11841/11841 PASSED
The arc includes dedicated coverage for:
A plugin author can now choose between two complete styles.
Declare commands in the sidecar and implement their behavior in Perl, Python or Tcl. Mediabot handles discovery, authorization, mounting, dispatch and removal.
Declare supported events and let the script react to joins, parts, topics, kicks or observed public commands. No manual EventBus wiring is needed.
Both styles receive:
The portraits can now do more than announce the spells painted beneath them.
They listen to the corridors. They greet arriving students. They report which house-elves are still working in the kitchens. And when someone hurls a thousand sheets of parchment through the partyline, the castle gives one warning, falls silent, and eventually closes the door.
The plugin v2 gallery is no longer merely open.
It is alive, observable and guarded.
You must be logged in to reply.