Skip to content
Moku Labs
@moku-labs/editor pages

Events

The global and per-plugin events of the agent, server and tools cores, with payloads and listeners.

On this page

How events work

Each core has its own events. An event is global only when its listener cannot depend on its emitter. Every other event is per-plugin: it is declared by its emitter with register<T>(), and a plugin hooks it by listing the emitter in depends.

Two events are global for that reason. The recorder hooks overlay:changed, but it is a dependency of the overlay, so the reverse edge would be a cycle. The bridge is opt-in, so no default plugin may depend on it.

Global events

CoreEventPayloadEmitted byHooked by
agentbridge:status{ status: LinkStatus; session?: string; link?: DeviceLink }; link in device mode onlybridgeoverlay
agentoverlay:changedOverlayChanged = { mode: "hidden" | "pill" | "card"; tab }overlayrecorder
agentoverlay:connect{ address: string }overlay, the Link tabbridge
serverhub:sessionHubSession = { id, game, open, reason?: "bye" | "game_reloaded" }hubyour plugins
serverfiles:writtenFilesWritten = { path, bytes, kind: WrittenKind }filesnone yet
serverfiles:projectProjectStatefileshub, published as editor.project
toolslink:status{ status: LinkStatus; session?: string }linkworkspace, panels, all six views
toolslink:project{ state: ProjectState; delta: ProjectDelta }linkflowView, gameView, renderView, filesView
toolsworkspace:changed{ ws: WorkspaceId }workspacepanels, flowView, gameView, renderView, filesView
toolsworkspace:ranRanEvent = { id, input, origin, at } and { ok: true, result } or { ok: false, error }workspace, panelsstateView, consoleView
toolsworkspace:density{ density: "compact" | "comfortable" }workspaceflowView
toolsworkspace:open-file{ path: string; line?: number }flowView, gameViewfilesView
toolsworkspace:select-node{ id: string }filesViewflowView
toolsworkspace:focus-frame{ frame: number }consoleViewflowView
toolsworkspace:reveal{ ref: ElementRef }gameViewrenderView
toolsworkspace:inspect{ ref: ElementRef }renderViewgameView
toolsworkspace:open-sheet{ index: string }filesViewgameView

Per-plugin events

CoreEventPayloadEmitterHooked by
agentregistry:commandRegistryCommandEvent = { id, input, via, ok }; via is "overlay", "tools", "mcp", "bridge" or "local"registryrecorder
agentrecorder:statusRecorderStatusEvent = { state, lines, bytes, sent, pending }recorderoverlay, bridge
serverhub:deviceHubDeviceEvent: listening, connected, lines, closed, stoppedhubcollector, connect
serverconnect:telemetryDeviceTelemetry = { v: 1, device, serial, at, wired, gpu, cpu, memory, heap }connectcollector
servercollector:sessionCollectorSessionEvent = { device, file, state, lines, lost, last }collectorpages
toolsactiveSource:changedSourceChange = { from, to, reason, at }; reason is "connected", "user", "lost", "rejoined", "stopped" or "adopted"activeSourceworkspace, renderView
toolsactiveSource:connect-requested{ tab: "wire" | "network" | undefined }activeSourcerenderView

LinkStatus

StatusMeans
connectingThe socket is opening.
live { frame }The game runs and beats.
paused { frame }The game is paused.
silent { since, lastFrame }No heartbeat for hub.silentAfterMs. Tools side only.
lost { reason, lastFrame, retryInMs }The link dropped.
emptyNo session. Tools side only.

The bridge only ever reports connecting, live, paused and lost.

Hook an event

A global event is open to any plugin of its core. A per-plugin event needs the emitter in depends, which also types the payload.

src/watch.ts
import { createPlugin } from "@moku-labs/editor/tools";

export const watchPlugin = createPlugin("watch", {
  hooks: ctx => ({
    "workspace:changed": ({ ws }) => ctx.log.info("workspace", { ws }),
    "link:status": ({ status }) => ctx.log.info("link", { kind: status.kind })
  })
});
src/audit.ts
import { createPlugin, hubPlugin } from "@moku-labs/editor/server";

export const auditPlugin = createPlugin("audit", {
  depends: [hubPlugin],
  hooks: ctx => ({
    "hub:session": session => ctx.log.info("audit:session", { id: session.id, open: session.open })
  })
});
  • Events are notifications. Ask a plugin for a value with ctx.require(plugin) and its API.
  • No view depends on another view. Views talk through the global workspace:* events.