# Cordis Events & Services Catalog An index reference to the **wiring** a plugin author works against: every cordis event you can listen to (exact signature + dispatch mode) and every `ctx.` service you can call (exact public interface). It complements [core-data-structures/](../core-data-structures/core.md), which catalogs the *data structures* these signatures move around — this page is the verbs, that page is the nouns. This file is GENERATED from source (`scripts/gen-cordis-catalog.ts`) and verified fresh by `pnpm run verify-cordis-catalog` (part of `doc-sync`) — do not edit it by hand. Signature blocks use a `ts cordis-catalog` fence (skipped by doc-typecheck, since a bare signature is not standalone-compilable). Type names in a signature link to the page that documents them. The **harness tier** below (the `@deepseek-ai/dsh-*` packages) is the vocabulary this repo owns. The **inherited tier** at the end is the cordis-core + loader/hmr/timer surface a plugin also sees — pinned vendor source, summarized tersely. ## Events Dispatch modes: **emit** (fire-and-forget), **waterfall** (each listener gets `next()` and may transform or veto — see [waterfall semantics](../architecture.md#cordis-waterfall-semantics-important)), **parallel** (awaited fan-out, no veto). The harness declares 22 events across 5 scopes. ### `agent/*` #### `agent/created` — emit An agent was registered in the AgentRegistry and is ready to receive messages. ```ts cordis-catalog 'agent/created'(agent: Agent): void ``` Types: [Agent](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:136`](../../packages/core/agent/src/types.ts) #### `agent/disposed` — emit An agent was disposed and removed from the registry; its fiber and any in-flight turn have been torn down. ```ts cordis-catalog 'agent/disposed'(agent: Agent): void ``` Types: [Agent](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:142`](../../packages/core/agent/src/types.ts) #### `agent/error` — emit A step or turn errored. The loop reports a failure here (plus the logger) even when the error has no in-turn position for a session `error` event. ```ts cordis-catalog 'agent/error'(agent: Agent, turn: number, step: number, error: Error): void ``` Types: [Agent](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:219`](../../packages/core/agent/src/types.ts) #### `agent/queued` — emit A message entered the agent's inbox (queued or steering). `source` is the resolved source (defaults applied), not the caller's raw options. ```ts cordis-catalog 'agent/queued'(agent: Agent, content: ContentBlock[], info: { source: MessageSource; steering: boolean }): void ``` Types: [Agent](../core-data-structures/core.md) · [ContentBlock](../core-data-structures/core.md) · [MessageSource](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:155`](../../packages/core/agent/src/types.ts) #### `agent/request` — waterfall Waterfall: mutate the fully-assembled GenerateOptions before the model call (hooks, compaction, model switching, tool filtering, …). Call `next()` to delegate, or return without it to short-circuit. ```ts cordis-catalog 'agent/request'(agent: Agent, turn: number, step: number, options: GenerateOptions, next: () => Promise): Promise ``` Types: [Agent](../core-data-structures/core.md) · [GenerateOptions](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:188`](../../packages/core/agent/src/types.ts) #### `agent/status` — emit Agent status changed (`idle` ⇄ `running`, or → `disposed`). Drive lifecycle off this transition, never off a status you just requested — `send()` does not flip status to `running` before it returns. ```ts cordis-catalog 'agent/status'(agent: Agent, status: AgentStatus): void ``` Types: [Agent](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:149`](../../packages/core/agent/src/types.ts) #### `agent/steering` — emit Steering content was injected into a running turn. ```ts cordis-catalog 'agent/steering'(agent: Agent, turn: number, content: ContentBlock[], source: MessageSource): void ``` Types: [Agent](../core-data-structures/core.md) · [ContentBlock](../core-data-structures/core.md) · [MessageSource](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:213`](../../packages/core/agent/src/types.ts) #### `agent/step-end` — emit A step ended. ```ts cordis-catalog 'agent/step-end'(agent: Agent, turn: number, step: number): void ``` Types: [Agent](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:179`](../../packages/core/agent/src/types.ts) #### `agent/step-result` — waterfall Waterfall: post-process the assembled assistant Message before tool dispatch (validation, content rewriting, …). ```ts cordis-catalog 'agent/step-result'(agent: Agent, turn: number, step: number, message: Message, next: () => Promise): Promise ``` Types: [Agent](../core-data-structures/core.md) · [Message](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:194`](../../packages/core/agent/src/types.ts) #### `agent/step-start` — emit A step (one model call plus its tool dispatch) began. `step` is 1-based within the turn; a turn runs one or more steps. ```ts cordis-catalog 'agent/step-start'(agent: Agent, turn: number, step: number): void ``` Types: [Agent](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:174`](../../packages/core/agent/src/types.ts) #### `agent/stream-chunk` — emit A raw StreamChunk arrived from the model (token-level UI/log feed). ```ts cordis-catalog 'agent/stream-chunk'(agent: Agent, turn: number, step: number, chunk: StreamChunk): void ``` Types: [Agent](../core-data-structures/core.md) · [StreamChunk](../core-data-structures/llm-streaming.md) Source: [`packages/core/agent/src/types.ts:208`](../../packages/core/agent/src/types.ts) #### `agent/turn-continuation` — waterfall Waterfall: override the turn-continuation decision. The default (computed by the loop) is `hadToolCalls || steeringInjected`. Listeners can force-continue (/goal, /loop) or force-stop (budget guards). ```ts cordis-catalog 'agent/turn-continuation'(agent: Agent, turn: number, defaultDecision: boolean, next: () => Promise): Promise ``` Types: [Agent](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:201`](../../packages/core/agent/src/types.ts) #### `agent/turn-end` — emit A turn ended. `reason` distinguishes a clean stop from a truncated or aborted one (`completed` | `aborted` | `error` | `disposed` | `max-tokens`). ```ts cordis-catalog 'agent/turn-end'(agent: Agent, turn: number, reason: TurnEndReason): void ``` Types: [Agent](../core-data-structures/core.md) · [TurnEndReason](../core-data-structures/session.md) Source: [`packages/core/agent/src/types.ts:168`](../../packages/core/agent/src/types.ts) #### `agent/turn-start` — emit A turn began. `turn` is the 1-based turn number within the session. ```ts cordis-catalog 'agent/turn-start'(agent: Agent, turn: number): void ``` Types: [Agent](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:162`](../../packages/core/agent/src/types.ts) ### `llm/*` #### `llm/stream` — waterfall Waterfall around every streaming model call (retry, caching, routing). Bound to the LlmService; call `next()` to reach the resolved adapter's stream, or yield your own chunks to short-circuit. ```ts cordis-catalog 'llm/stream'(this: LlmService, options: GenerateOptions, next: () => AsyncIterable): AsyncIterable ``` Types: [GenerateOptions](../core-data-structures/core.md) · [StreamChunk](../core-data-structures/llm-streaming.md) Source: [`packages/llm/llm/src/index.ts:31`](../../packages/llm/llm/src/index.ts) ### `session/*` #### `session/created` — emit A session was created in the store. ```ts cordis-catalog 'session/created'(session: Session): void ``` Source: [`packages/core/session/src/index.ts:30`](../../packages/core/session/src/index.ts) #### `session/event` — emit An event was appended to a session log (sync, fire-and-forget). This is the per-append feed a UI or invariant plugin tails. ```ts cordis-catalog 'session/event'(session: Session, event: SessionEvent): void ``` Types: [SessionEvent](../core-data-structures/core.md) Source: [`packages/core/session/src/index.ts:36`](../../packages/core/session/src/index.ts) #### `session/flush` — parallel Awaited durability checkpoint. The agent loop awaits `ctx.parallel('session/flush', session)` at every turn end; persistence plugins (JSONL, SQLite) drain their write-behind buffers here and on fiber dispose. Awaited (parallel), not a waterfall: every listener runs and the loop waits for all of them, but none can veto. ```ts cordis-catalog 'session/flush'(session: Session): Promise | void ``` Source: [`packages/core/session/src/index.ts:45`](../../packages/core/session/src/index.ts) ### `system-prompt/*` #### `system-prompt/assemble` — waterfall Waterfall around prompt assembly — mutate or extend the PromptAssembly (sections + tool schemas) before it is rendered. Bound to the SystemPrompt service; call `next()` to delegate. ```ts cordis-catalog 'system-prompt/assemble'(this: SystemPrompt, assembly: PromptAssembly, next: () => Promise): Promise ``` Source: [`packages/core/system-prompt/src/index.ts:24`](../../packages/core/system-prompt/src/index.ts) #### `system-prompt/change` — emit A section or tool provider was registered or unregistered (the assembly inputs changed). ```ts cordis-catalog 'system-prompt/change'(): void ``` Source: [`packages/core/system-prompt/src/index.ts:30`](../../packages/core/system-prompt/src/index.ts) ### `tools/*` #### `tools/change` — emit A tool was registered or unregistered (the available tool set changed). ```ts cordis-catalog 'tools/change'(): void ``` Source: [`packages/core/tools/src/index.ts:48`](../../packages/core/tools/src/index.ts) #### `tools/execute` — waterfall Waterfall around every tool execution — the single seam where sandbox, permission, hook, and plan-mode plugins wrap or veto a call. Listeners receive `(exec, next)`: call `next()` to proceed (possibly around your own logic), or return a ToolExecutionResult without calling `next()` to short-circuit (veto). ```ts cordis-catalog 'tools/execute'(this: ToolRegistry, exec: ToolExecution, next: () => Promise): Promise ``` Types: [ToolExecution](../core-data-structures/tools.md) · [ToolExecutionResult](../core-data-structures/tools.md) Source: [`packages/core/tools/src/index.ts:43`](../../packages/core/tools/src/index.ts) ## Services The 10 `ctx.` services the harness provides. An abstract seam (e.g. `ctx.bash`) is implemented by a separate package; the interface is what consumers code against. ### `ctx.agentLoop` — `AgentLoop` The agent-loop plugin (`ctx.agentLoop`): creates ReactLoopAgents, runs their loops, and registers them in `ctx.agents`. Also implements the AgentFactory seam, so plugins create/resume agents through `ctx.agents` (the interface) without depending on this concrete package. The loop itself is deliberately thin — every behavior beyond "call the model, run the tools, repeat" belongs to plugins listening on the event taxonomy declared in @deepseek-ai/dsh-agent. ```ts cordis-catalog create(id: AgentId, options: AgentOptions = {}): ReactLoopAgent createAgent(options: CreateAgentOptions): AgentHandle async resume(options: ResumeAgentOptions): Promise ``` Source: [`packages/core/agent-loop/src/index.ts:63`](../../packages/core/agent-loop/src/index.ts) ### `ctx.agents` — `AgentRegistry` Agent registry (`ctx.agents`): tracks live agents so UI, hook, and orchestrator plugins can find them without depending on the concrete loop package. Agent *creation* is provided by whichever plugin implements the AgentFactory (phase 1: `@deepseek-ai/dsh-agent-loop`), registered via setFactory. ```ts cordis-catalog setFactory(factory: AgentFactory): () => void create(options: CreateAgentOptions): AgentHandle async resume(options: ResumeAgentOptions): Promise register(agent: Agent): () => void get(id: AgentId): Agent | undefined list(): Agent[] ``` Types: [Agent](../core-data-structures/core.md) Source: [`packages/core/agent/src/index.ts:105`](../../packages/core/agent/src/index.ts) ### `ctx.bash` — `BashExecutor` (abstract seam) Abstract bash execution service. Subclass, implement the abstract methods, and load the subclass as a plugin — it registers as `ctx.bash` (one implementation per context; loading a second throws, which is cordis' standard duplicate-service behavior). Semantics every implementation must honor: - run REJECTS only for infrastructure failures (unusable workdir, missing shell, pre-aborted signal). Nonzero exits, timeout kills, and abort kills RESOLVE with a descriptive BashRunResult — reporting a failed command is the tool layer's job, not an exception. - start returns immediately; no timeout applies to background tasks (callers stop them via kill or the spec's AbortSignal). Completion must fire the onTaskDone listeners exactly once per task, and must NOT fire after the service is disposed. - readOutput is incremental: consecutive reads never re-deliver output. Implementations bound their buffers; reads that lost data flag `lossy` and point at full-stream spill files when available. - Disposal kills every running task and awaits their exit (no orphan processes survive `fiber.dispose()`). ```ts cordis-catalog abstract resolve(request: BashExecRequest): BashExecSpec abstract run(spec: BashExecSpec): Promise abstract start(spec: BashExecSpec): BashTask abstract get(id: BashTaskId): BashTask | undefined abstract ownerOf(id: BashTaskId): OwnerToken | undefined abstract list(): BashTask[] abstract readOutput(id: BashTaskId): BashTaskRead abstract kill(id: BashTaskId): boolean onTaskDone(listener: BashTaskListener): () => void ``` Types: [BashExecRequest](../core-data-structures/bash.md) · [BashExecSpec](../core-data-structures/bash.md) · [BashRunResult](../core-data-structures/bash.md) · [BashTask](../core-data-structures/bash.md) · [BashTaskRead](../core-data-structures/bash.md) Source: [`packages/bash/bash/src/index.ts:59`](../../packages/bash/bash/src/index.ts) ### `ctx.fileContext` — `FileContext` The file-context policy service. Injects `fs`, registers as `ctx.fileContext`, and is the only read/write/edit path the model-facing tools use. ```ts cordis-catalog owner(exec?: FileContextExec): object | undefined async resolve(path: string): Promise async read(target: FsTarget, request: FileReadRequest, exec?: FileContextExec, signal?: AbortSignal): Promise async write(target: FsTarget, content: string, exec?: FileContextExec, signal?: AbortSignal): Promise async edit(target: FsTarget, edit: FsEditRequest, exec?: FileContextExec, signal?: AbortSignal): Promise ``` Types: [FsEditOutcome](../core-data-structures/filesystem.md) · [FsEditRequest](../core-data-structures/filesystem.md) · [FsTarget](../core-data-structures/filesystem.md) · [FsWriteOutcome](../core-data-structures/filesystem.md) Source: [`packages/fs/file-context/src/index.ts:65`](../../packages/fs/file-context/src/index.ts) ### `ctx.fs` — `FileSystem` (abstract seam) Abstract filesystem provider service. Subclass, implement the six text-storage primitives, and load the subclass as a plugin — it registers as `ctx.fs` (one implementation per context; loading a second throws, cordis' standard duplicate-service behavior). Semantics every backend must honor: - resolve returns a stable FsTarget; the same underlying file reached by different input paths must yield the same `targetKey` so stale guards and target lookup agree across paths (e.g. through symlinks). - stat returns FsInfo metadata (never content) or `undefined` when the target is absent. - readText/streamText read the whole regular text file (the stream for large files); both own regular-file checks, UTF-8 decoding, binary/NUL rejection, and `FS_NOT_TEXT`. - writeText is atomic temp-file + rename honoring the FsWriteExpectation. - editText verifies `expected.version` BEFORE literal matching (so a stale edit reports `FS_STALE_VERSION`, not `FS_EDIT_NOT_FOUND`/ `FS_AMBIGUOUS_EDIT` against newer content), then applies literal replacement and writes atomically — all inside one mutation critical section. ```ts cordis-catalog abstract resolve(path: string): Promise abstract stat(target: FsTarget, signal?: AbortSignal): Promise abstract readText(target: FsTarget, signal?: AbortSignal): Promise abstract streamText(target: FsTarget, signal?: AbortSignal): Promise> abstract writeText(target: FsTarget, content: string, expected: FsWriteExpectation, signal?: AbortSignal): Promise abstract editText(target: FsTarget, edit: FsEditRequest, expected: { version: FsVersion }, signal?: AbortSignal): Promise ``` Types: [FsEditOutcome](../core-data-structures/filesystem.md) · [FsEditRequest](../core-data-structures/filesystem.md) · [FsTarget](../core-data-structures/filesystem.md) · [FsVersion](../core-data-structures/filesystem.md) · [FsWriteOutcome](../core-data-structures/filesystem.md) Source: [`packages/fs/fs/src/index.ts:90`](../../packages/fs/fs/src/index.ts) ### `ctx.llm` — `LlmService` The abstract `llm` service: an adapter registry plus a streaming model-call surface, interceptable via the `llm/stream` waterfall. ```ts cordis-catalog registerAdapter(models: string[], adapter: LlmAdapter): () => void models(): string[] stream(options: GenerateOptions): AsyncIterable ``` Types: [GenerateOptions](../core-data-structures/core.md) · [StreamChunk](../core-data-structures/llm-streaming.md) Source: [`packages/llm/llm/src/index.ts:69`](../../packages/llm/llm/src/index.ts) ### `ctx.sessionPersistence` — `SessionPersistence` (abstract seam) Abstract durable session-persistence service. Subclass, implement the abstract methods, and load the subclass as a plugin — it registers as `ctx.sessionPersistence` (one implementation per context; loading a second throws, cordis' standard duplicate-service behavior). Contracts every implementation MUST honor (a DB backend asserts them inside a transaction; a file backend appends at EOF): - **Append-only; a crashed turn is closed, not truncated.** Committed events — those at or below a flushed `turn/end` — are never rewritten. A crash can leave an unclosed final turn whose events are real (and possibly large); load preserves them and closes the orphaned turn with synthetic boundary events (see load). Only a never-fully-written torn tail fragment is discarded. - **Contiguous seq.** A persisted log is contiguous: `events[i].seq === i`. load rejects a parse error or a `seq` gap in the COMMITTED region (unloadable); append's first event `seq` MUST equal the backend's stored next-seq (after `load` has balanced any interrupted turn). - **JSON-serializable data.** `SessionEventMap` is merge-extensible and `event.data` is typed only as `SessionEventMap[K]`, so append REJECTS non-JSON-serializable data with an error naming the offending event type. A backend snapshots (serializes/clones) each event when it buffers, since `session.events` hands out the live mutable object. - **Durability.** append returns only once the batch is durable (the file backend fsyncs; a DB commits). create MAY defer the physical write until the first append (lazy materialization). ```ts cordis-catalog abstract create(meta: SessionHeader): Promise abstract append(id: SessionId, events: readonly SessionEvent[]): Promise abstract load(id: SessionId): Promise<{ meta: SessionHeader; events: SessionEvent[] }> abstract list(): Promise ``` Types: [SessionEvent](../core-data-structures/core.md) Source: [`packages/session-persistence/session-persistence/src/index.ts:98`](../../packages/session-persistence/session-persistence/src/index.ts) ### `ctx.sessions` — `SessionStore` In-memory session store (`ctx.sessions`). Persistence is intentionally not implemented here — persistence plugins subscribe to `session/event` and flush on `session/flush` / dispose. ```ts cordis-catalog create(id?: SessionId, options?: CreateSessionOptions): Session prepare(id?: SessionId, options?: CreateSessionOptions): Session enter(session: Session): () => void announce(session: Session): void get(id: SessionId): Session | undefined list(): Session[] ``` Source: [`packages/core/session/src/index.ts:229`](../../packages/core/session/src/index.ts) ### `ctx.systemPrompt` — `SystemPrompt` Registry service (`ctx.systemPrompt`): plugins contribute ordered text sections and tool-schema providers; the agent loop calls `assemble()` once per step. ```ts cordis-catalog section(section: PromptSection): () => void tools(provider: () => ToolSchema[]): () => void assemble(): Promise ``` Source: [`packages/core/system-prompt/src/index.ts:71`](../../packages/core/system-prompt/src/index.ts) ### `ctx.tools` — `ToolRegistry` Tool registry (`ctx.tools`): tool plugins register definitions; the agent loop executes calls through the `tools/execute` waterfall. The registry contributes its schemas into the system-prompt assembly. ```ts cordis-catalog register(definition: ToolDefinition): () => void get(name: string): ToolDefinition | undefined schemas(): ToolSchema[] async execute(exec: ToolExecution): Promise ``` Types: [ToolDefinition](../core-data-structures/tools.md) · [ToolExecution](../core-data-structures/tools.md) · [ToolExecutionResult](../core-data-structures/tools.md) Source: [`packages/core/tools/src/index.ts:277`](../../packages/core/tools/src/index.ts) ## Inherited tier (cordis core + loader/hmr/timer) The framework surface every plugin inherits, beyond the harness vocabulary above. This is pinned vendor source ([vendoring policy](../../vendor/README.md)); it is summarized here so the catalog is a complete picture of what `ctx` and the event bus offer, without elevating framework internals to the harness tier's prominence. ### Inherited events - `internal/plugin` — A plugin fiber was created. ([`vendor/cordis/src/events.ts:197`](../../vendor/cordis/src/events.ts)) - `internal/status` — A fiber changed lifecycle state. ([`vendor/cordis/src/events.ts:198`](../../vendor/cordis/src/events.ts)) - `internal/service` — Interception hook for a service binding (no core producer). ([`vendor/cordis/src/events.ts:199`](../../vendor/cordis/src/events.ts)) - `internal/update` — Waterfall: a fiber config update is being applied. ([`vendor/cordis/src/events.ts:200`](../../vendor/cordis/src/events.ts)) - `internal/get` — Waterfall: a service is being read from the store. ([`vendor/cordis/src/events.ts:201`](../../vendor/cordis/src/events.ts)) - `internal/set` — Waterfall: a service is being written to the store. ([`vendor/cordis/src/events.ts:202`](../../vendor/cordis/src/events.ts)) - `internal/listener` — A listener was registered. ([`vendor/cordis/src/events.ts:203`](../../vendor/cordis/src/events.ts)) - `internal/dispatch` — An event is being dispatched to listeners. ([`vendor/cordis/src/events.ts:204`](../../vendor/cordis/src/events.ts)) - `hmr/change` — A watched source file changed on disk. ([`vendor/hmr/src/index.ts:20`](../../vendor/hmr/src/index.ts)) - `hmr/reload` — Plugins are being reloaded after a change. ([`vendor/hmr/src/index.ts:21`](../../vendor/hmr/src/index.ts)) - `exit` — The process is exiting on a signal. ([`vendor/loader/src/index.ts:23`](../../vendor/loader/src/index.ts)) - `loader/config-update` — The loader config tree changed. ([`vendor/loader/src/index.ts:24`](../../vendor/loader/src/index.ts)) - `loader/entry-init` — A config entry is being initialized. ([`vendor/loader/src/index.ts:25`](../../vendor/loader/src/index.ts)) - `loader/partial-dispose` — An entry is being partially disposed on reload. ([`vendor/loader/src/index.ts:26`](../../vendor/loader/src/index.ts)) - `loader/patch-context` — A context is being patched during a reload. ([`vendor/loader/src/index.ts:27`](../../vendor/loader/src/index.ts)) ### Inherited `ctx` members - `ctx.on / ctx.once` — Register an event listener (disposable). ([`vendor/cordis/src/events.ts:29`](../../vendor/cordis/src/events.ts)) - `ctx.emit / ctx.parallel / ctx.serial / ctx.bail / ctx.waterfall` — Dispatch an event (sync / awaited / first-non-nullish / veto-chain). ([`vendor/cordis/src/events.ts:29`](../../vendor/cordis/src/events.ts)) - `ctx.plugin / ctx.inject` — Load a plugin / declare required services. ([`vendor/cordis/src/registry.ts:144`](../../vendor/cordis/src/registry.ts)) - `ctx.effect` — Register a disposable side effect tied to the fiber. ([`vendor/cordis/src/fiber.ts:9`](../../vendor/cordis/src/fiber.ts)) - `ctx.get / ctx.set / ctx.provide / ctx.accessor / ctx.mixin` — Low-level service-store access and binding. ([`vendor/cordis/src/reflect.ts:7`](../../vendor/cordis/src/reflect.ts)) - `ctx.extend / ctx.isolate / ctx.intercept` — Derive a child context (scoped services / isolation / interception). ([`vendor/cordis/src/context.ts:35`](../../vendor/cordis/src/context.ts)) - `ctx.root / ctx.scope / ctx.fiber / ctx.registry / ctx.reflect / ctx.events / ctx.logger` — Ambient handles onto the running context graph. ([`vendor/cordis/src/context.ts:16`](../../vendor/cordis/src/context.ts)) - `ctx.timer (+ interval / timeout / throttle / debounce / setTimeout / setInterval)` — Disposable timer helpers. The `timer` key is provided at runtime; the six helpers are mixed onto ctx directly (declared via Pick). ([`vendor/timer/src/index.ts:4`](../../vendor/timer/src/index.ts)) - `ctx.loader` — The config Loader that booted the app (present under the loader). ([`vendor/loader/src/index.ts:30`](../../vendor/loader/src/index.ts)) - `ctx.hmr` — The hot-module-reload watcher (present under the hmr plugin). ([`vendor/hmr/src/index.ts:15`](../../vendor/hmr/src/index.ts))