@clankie/game-extension
A typed composition contract for game extensions. Games keep their own motors, actions and observations; core supplies durable authority and approved host ports. See ADR 0234.
An extension declares contractVersion: 1, id, connector, skill,
settings (existing key and parser), activity (surface or null), and
create(host). Its runtime implements start(request, control, onRunning),
stop(sessionId), status() and health(). Await status/health; a connector
may implement a read-only remote check, while the shared wrapper returns local
projections immediately.
createGameExtensionRuntime supplies the shared local lifecycle. It checks the
host guard before entering execution and before onRunning. The connector
must check the same guard at every effect and call confirmStopped only after
exact departure or a proven no-join refusal. A settled promise, timeout, stop
request or health result cannot release the host's play lease. Unconfirmed
cleanup retains uncertain and rejects another start. Core owns restart
recovery; this helper is deliberately not a durable lease store.
ready is local lifecycle health. It does not probe credentials, worlds,
media or models. Request and result remain extension-typed, so the host can
preserve existing validated game APIs and refusal receipts.
Pokémon is the first implementation.
Minecraft also implements it. GameExtensionRegistry validates descriptors,
creates trusted factories with approved host ports, returns their typed runtime,
and projects local state through a bounded catalog. Registering metadata does
not import code, install packages or grant authority. Factories stay inert until
execution or explicit activation.
A host can associate its typed tool/route projection with a registration.
projections() returns only current registrations. unregister(id) fences new
starts, verifies idle state, quiesces optional capture/polling and removes the
entry; held or uncertain state refuses. Native entry points bind the same
registration guard so a stale reference cannot join after removal.
reconcileStopped(id, proof) is host-only. Use it inside the existing durable,
incarnation-fenced recovery operation after exact connector termination. A
failed proof or still-running executor cannot clear uncertainty. It does not
release the host ledger.
External extensions
A game can live outside this repository and be installed by the owner, with no
change here: clankie games extensions add MODULE [KEY=VALUE...] records the
absolute module path in gameplay.extensions, and the service loads it at start
(apps/clankie/src/game-extension-modules.ts). The path comes from owner
settings, never a tool argument or model output. The module is trusted,
in-process code; a module that fails to load is logged and skipped.
The module imports only types from this package (src/module.ts) and exports:
gameExtensionApis, includingGAME_EXTENSION_MODULE_API(1);gameExtension, aGameExtensionwhosecreate(host)receives aGameExtensionModuleHost;projectGameExtension(runtime), called with the registered runtime (so starts keep the registry's admission fence), returning its captain tools (JSON Schema parameters), exact operator paths with afetchhandler, optionalplayrecovery (owns,recover) andclose.
The host supplies the owner's non-secret options, a private stateDirectory
(<state>/game-extensions/<id>), the core's durable play ledger (leases),
createRuntime (the lifecycle wrapper above), credential(name) for broker API
keys in the extension's own namespace (ID or ID-*), and goLive for a
read-only /frame.png?key= snapshot capability through the active Discord
user-session body. The host binds tools and routes to the authenticated
conversation, refuses unauthenticated requests, and runs play.recover inside
its incarnation-fenced recovery before reconcileStopped; only that outer
operation releases the ledger claim.
Rivals (clankie-extension/) is
the first external extension (ADR 0175).
