Plugins
A plugin is an object with a name and an apply function. A
scope owns every registration it makes. One unload therefore undoes all of them.
The shape
plugins.use checks for
{ name, apply } and throws on anything else. It runs
apply under a fresh child scope and hands it a context and
your config value. A throw inside apply disposes the partial
scope, so a plugin that fails halfway leaves nothing behind.
// index.js
import { plugins } from "yuke";
const watcher = {
name: "watcher",
apply(ctx, config) {
ctx.on("engine.drained", () => console.log(config.message));
},
};
const dispose = plugins.use(watcher, { message: "idle" });
use returns a disposer. A second call with the same name
disposes the previous instance first, so a reload replaces it and does not stack.
plugins.get(name),
plugins.dispose(name) and
plugins.names() round it out.
The context
Every registration goes through ctx rather than a global, which
is what makes disposal complete.
| effect(fn) | Run now; the returned function undoes it on disposal. |
| on(name, fn, opts) | Subscribe to an event. |
| once(name, fn) | Subscribe for one delivery. |
| advise(obj, prop, where, fn, opts) | Wrap an existing function in place. where is before, after, around, filterArgs or filterReturn. |
| provide(name, value) | Publish a service other plugins may resolve. |
| inject(names, apply) | Wait for services, then run. This is how the built-in modules wire to each other. |
| hook(point, fn) | Handle a hook point; see below. |
yuke:ext also exports advice
and services directly, for use outside a plugin. A
registration made that way is yours to undo.
Hooks
A hook point is a named place in a turn. A handler there may inspect the call, alter it, or refuse it. Handlers run at the end of the chain, in the order of their registration. yuke publishes the set of points that hold a handler. A turn therefore never submits a call that no handler wants.
The input gate covers session.send_input and
session.create — the two points where content reaches a
model. Its params carry session_id and
input. input is a union tagged
by type. content holds an
array of content parts. skill holds a skill reference. A
content part carries its own type tag:
text, image,
audio or file.
const guard = {
name: "guard",
apply(ctx) {
ctx.hook("session.send_input", async (params) => {
if (params.input.type !== "content") return params;
for (const part of params.input.content) {
if (part.type !== "text") continue;
if (part.text.includes("BEGIN PRIVATE KEY")) throw new Error("refused");
}
return params;
});
},
};
Scopes
A scope owns disposers, and it can carry children. A parent disposes its children first.
One plugins.dispose(name) therefore unwinds every event
subscription, service, advice and hook that the plugin created, in reverse order. A plugin
never tracks its own registrations.
Events you can subscribe to today include
engine.drained,
activity.changed,
composer.changed,
clipboard.copied,
pane.focused, pane.closed,
region.focused and
ext.error.