Source: packages/runtime-core/src/plugin.ts
A PetPlugin has an id, a displayName, and a setup(host) method. The host parameter is a PetHostApi — the plugin host — through which the plugin registers its capabilities:
Naming note: "plugin host" (
PetHostApi) and "Host Bridge" are different things.PetHostApiis the registry interface that receives plugin registrations. "Host Bridge" is the architecture-layer term for connecting an upstream tool to the runtime; at the code level, that'sRuntimeSnapshotSourceFactory/RuntimeSnapshotSource.
interface PetPlugin {
id: string;
displayName: string;
setup(host: PetHostApi): void;
}
interface PetHostApi {
registerPetProvider(provider: PetProvider): void;
registerAnimationResolver(resolver: AnimationResolver): void;
registerNotificationResolver(resolver: NotificationResolver): void;
registerLayoutResolver(resolver: LayoutResolver): void;
registerActionCatalog(catalog: ActionCatalog): void;
registerPackageAdapter(adapter: CompatiblePetPackageAdapter): void;
registerSnapshotSourceFactory(factory: RuntimeSnapshotSourceFactory): void;
registerDiscoverySource(source: PetDiscoverySource): void;
}PetRuntimeRegistry implements PetHostApi and holds the registered instances. Pass it to createDefaultPlugin to wire up the built-in defaults.
interface PetProvider {
id: string;
listPets(): PetDefinition[];
}Returns a list of available pets. The default provider returns the built-in pets merged with any custom pets from loaded packages.
interface AnimationResolver {
id: string;
supports(state: string): boolean;
getSequence(state: PetAnimationState, reducedMotion: boolean): AnimationSequence;
}Maps an action state to a frame sequence. Multiple resolvers can be registered; the first one that supports the requested state is used.
interface ActionCatalog {
id: string;
listActions(): PetActionDefinition[];
}
interface PetActionDefinition {
state: PetAnimationState;
displayName: string;
implemented: boolean;
fallbackState?: DefaultPetAnimationState;
category: 'core' | 'extension';
}Declares the full action vocabulary. Extension actions that aren't yet implemented declare a fallbackState so the runtime can degrade gracefully.
interface CompatiblePetPackageAdapter {
id: string;
displayName: string;
formatId: CompatiblePetFormatId;
detect(manifest: Record<string, unknown>): boolean;
load(input: {
petDir: string;
manifest: Record<string, unknown>;
manifestPath?: string;
resolveFileUrl?: (filePath: string) => string;
}): CompatiblePetPackage;
}Converts a pet package directory into an internal CompatiblePetPackage. The detect method identifies whether the adapter can handle a given manifest.
interface NotificationResolver {
id: string;
project(input: { sessions: SessionProjection[]; ... }): NotificationProjectionResult;
levelForStatus(status: SessionStatus): NotificationLevel;
visualForNotification(notification: OverlayNotification | null): NotificationVisualState;
}Maps active sessions to the notifications shown in the tray and determines the visual state of the mascot badge.
interface PetDiscoverySource {
id: string;
displayName: string;
listPets(): DiscoveredPet[];
}
interface DiscoveredPet {
id: string;
displayName: string;
petDir: string;
spritesheetUrl?: string;
formatId?: string;
}Provides a list of available pet packages for UI selection. A plugin registers a discovery source so the shell can populate a pet picker without knowing where pets come from. The codex-pet-adapter registers a source that scans ~/.codex/pets; other adapters can register their own.
In apps/desktop, the shell reads discoverySources (an array of directory paths) from config.json and scans each one directly — no bundler required. Adding a path to that array is the desktop equivalent of calling registerDiscoverySource.
interface RuntimeSnapshotSource {
id: string;
displayName: string;
start(emit: RuntimeSnapshotEmitter): void | Promise<void>;
stop?(): void | Promise<void>;
}
interface RuntimeSnapshotSourceFactory {
id: string;
displayName: string;
create(options?: Record<string, unknown>): RuntimeSnapshotSource;
}Connects an external state source (file, HTTP, WebSocket, plugin) to the runtime. Call emit with a RuntimeSnapshot whenever the state changes.
interface LayoutResolver {
id: string;
defaults(): OverlayViewportDefaults;
measure(input: { mascot: Element | null; tray: Element | null; ... }): OverlayMeasuredElements | null;
equals(previous: OverlayMeasuredElements | null | undefined, next: OverlayMeasuredElements): boolean;
}Controls where the pet and tray are positioned and how they're sized.
The minimal structure any external system needs to produce to drive the pet:
interface RuntimeSnapshot {
state: PetAnimationState;
title: string;
body?: string;
metadata?: Record<string, unknown>;
}